mat-drupal-mcp
Version:
MatDrupal MCP - AI Assistant for Drupal Projects with bilingual FR/EN support
624 lines • 25.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const util_1 = require("util");
const yaml = __importStar(require("yaml"));
const execAsync = (0, util_1.promisify)(child_process_1.exec);
// Configuration environment
const SUPPORT_LANDO = process.env.LANDO_SUPPORT === 'true';
const SUPPORT_ACQUIA = process.env.ACQUIA_CLI_SUPPORT === 'true';
const MATDRUPAL_MODE = process.env.MATDRUPAL_MODE === 'true';
const BILINGUAL_MODE = process.env.BILINGUAL_MODE === 'true';
// Messages bilingues
const MESSAGES = {
fr: {
PROJECT_OVERVIEW: "Analyse complète du projet MatDrupal",
NO_DRUPAL_PROJECT: "❌ Aucun projet Drupal détecté dans ce répertoire",
CUSTOM_MODULES_FOUND: "📦 Modules Custom Détectés",
MIGRATION_PLANNING: "🚀 Planification Migration D11",
MODULE_ANALYSIS: "🔍 Analyse du Module",
TECHNICAL_DEBT: "⚠️ Dette Technique",
MCP_SIGNATURE: "---\n*Analysé par MatDrupal MCP v1.0.0*"
},
en: {
PROJECT_OVERVIEW: "Complete MatDrupal Project Analysis",
NO_DRUPAL_PROJECT: "❌ No Drupal project detected in this directory",
CUSTOM_MODULES_FOUND: "📦 Custom Modules Found",
MIGRATION_PLANNING: "🚀 D11 Migration Planning",
MODULE_ANALYSIS: "🔍 Module Analysis",
TECHNICAL_DEBT: "⚠️ Technical Debt",
MCP_SIGNATURE: "---\n*Analyzed by MatDrupal MCP v1.0.0*"
}
};
// Fonctions utilitaires
function getMessage(key, language) {
return MESSAGES[language][key] || MESSAGES.en[key] || key;
}
function detectLanguage(input) {
// Détection simple basée sur des mots-clés français
const frenchKeywords = ['analyser', 'projet', 'module', 'migration', 'dette'];
const lowerInput = input.toLowerCase();
for (const keyword of frenchKeywords) {
if (lowerInput.includes(keyword)) {
return 'fr';
}
}
return BILINGUAL_MODE ? 'fr' : 'en';
}
function addMcpSignature(content, language) {
const signature = language === 'fr' ?
"🔧 **MatDrupal MCP en cours d'utilisation...**\n*Serveur MCP MatDrupal Project Intelligence activé*\n\n" :
"🔧 **MatDrupal MCP in use...**\n*MatDrupal Project Intelligence MCP Server activated*\n\n";
return signature + content + '\n\n' + getMessage('MCP_SIGNATURE', language);
}
// Fonction de logging sécurisée pour MCP
function logInfo(message) {
// Pour MCP STDIO, utiliser stderr uniquement pour éviter les conflits
console.error(`[MCP] ${message}`);
}
// Détection de projet Drupal
async function detectDrupalProject(projectPath = process.cwd()) {
try {
const docrootPath = path.join(projectPath, 'docroot');
const hasDocroot = fs.existsSync(docrootPath);
const drupalPath = hasDocroot ? docrootPath : projectPath;
const corePath = path.join(drupalPath, 'core');
const indexPath = path.join(drupalPath, 'index.php');
if (!fs.existsSync(corePath) && !fs.existsSync(indexPath)) {
return { isDrupal: false, hasDocroot, drupalPath, version: 'unknown' };
}
let version = 'unknown';
const coreComposerPath = path.join(corePath, 'composer.json');
if (fs.existsSync(coreComposerPath)) {
try {
const coreComposer = JSON.parse(fs.readFileSync(coreComposerPath, 'utf8'));
version = coreComposer.version || 'unknown';
}
catch (error) {
logInfo(`Error reading core composer.json: ${error}`);
}
}
return { isDrupal: true, hasDocroot, drupalPath, version };
}
catch (error) {
logInfo(`Error detecting Drupal project: ${error}`);
return { isDrupal: false, hasDocroot: false, drupalPath: projectPath, version: 'unknown' };
}
}
// Analyse des modules custom
async function analyzeCustomModules(drupalPath) {
const modulesPath = path.join(drupalPath, 'modules', 'custom');
if (!fs.existsSync(modulesPath)) {
return [];
}
const modules = [];
try {
const moduleDirectories = fs.readdirSync(modulesPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const moduleName of moduleDirectories) {
const modulePath = path.join(modulesPath, moduleName);
const infoFile = path.join(modulePath, `${moduleName}.info.yml`);
if (!fs.existsSync(infoFile))
continue;
try {
const infoContent = fs.readFileSync(infoFile, 'utf8');
const info = yaml.parse(infoContent);
modules.push({
name: moduleName,
path: modulePath,
info,
dependencies: info.dependencies || []
});
}
catch (error) {
logInfo(`Error analyzing module ${moduleName}: ${error}`);
}
}
}
catch (error) {
logInfo(`Error reading modules directory: ${error}`);
}
return modules;
}
// Analyse de compatibilité D11 simple
async function analyzeDrupalCompatibility(drupalPath) {
const incompatibleModules = [];
const deprecatedFunctions = [];
try {
const contribPath = path.join(drupalPath, 'modules', 'contrib');
if (fs.existsSync(contribPath)) {
const modules = fs.readdirSync(contribPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
.slice(0, 5); // Limiter pour éviter les problèmes de performance
for (const module of modules) {
const infoFile = path.join(contribPath, module, `${module}.info.yml`);
if (fs.existsSync(infoFile)) {
try {
const info = yaml.parse(fs.readFileSync(infoFile, 'utf8'));
const coreCompatibility = info.core_version_requirement || info.core;
if (coreCompatibility && !coreCompatibility.includes('11')) {
incompatibleModules.push(`${module}: ${coreCompatibility}`);
}
}
catch (error) {
logInfo(`Error parsing ${module} info file: ${error}`);
}
}
}
}
}
catch (error) {
logInfo(`Error analyzing D11 compatibility: ${error}`);
}
let migrationComplexity = 'low';
if (incompatibleModules.length > 5) {
migrationComplexity = 'high';
}
else if (incompatibleModules.length > 2) {
migrationComplexity = 'medium';
}
return {
deprecatedFunctions,
incompatibleModules,
migrationComplexity,
recommendations: [
"Mettre à jour les modules contrib vers les versions D11",
"Réviser le code custom pour les APIs dépréciées",
"Tester en environnement de développement"
]
};
}
// Gestionnaire pour l'analyse de projet
async function handleProjectOverview(args) {
const projectPath = args.projectPath || process.cwd();
const analysisDepth = args.analysisDepth || "comprehensive";
const language = args.language === "auto" ?
detectLanguage(args.projectPath || "") :
args.language || 'en';
logInfo(`Analyzing project at: ${projectPath}`);
const projectInfo = await detectDrupalProject(projectPath);
if (!projectInfo.isDrupal) {
return {
content: [{
type: "text",
text: getMessage('NO_DRUPAL_PROJECT', language)
}]
};
}
let result = `# ${getMessage('PROJECT_OVERVIEW', language)}\n\n`;
result += `- **Drupal Version**: ${projectInfo.version}\n`;
result += `- **Docroot Structure**: ${projectInfo.hasDocroot ? '✅' : '❌'}\n`;
result += `- **Drupal Path**: ${projectInfo.drupalPath}\n\n`;
if (SUPPORT_LANDO) {
result += `- **Lando Support**: ✅\n`;
}
if (SUPPORT_ACQUIA) {
result += `- **Acquia Support**: ✅\n`;
}
if (analysisDepth === "comprehensive" || analysisDepth === "strategic") {
const modules = await analyzeCustomModules(projectInfo.drupalPath);
if (modules.length > 0) {
result += `\n## ${getMessage('CUSTOM_MODULES_FOUND', language)} (${modules.length})\n\n`;
for (const module of modules) {
result += `### ${module.name}\n`;
result += `- **Description**: ${module.info?.description || 'Module personnalisé'}\n`;
result += `- **Dependencies**: ${module.dependencies.join(', ') || 'None'}\n\n`;
}
}
if (analysisDepth === "strategic") {
const compatibility = await analyzeDrupalCompatibility(projectInfo.drupalPath);
result += `## ${getMessage('MIGRATION_PLANNING', language)}\n\n`;
result += `- **Migration Complexity**: ${compatibility.migrationComplexity}\n`;
result += `- **Incompatible Modules**: ${compatibility.incompatibleModules.length}\n\n`;
}
}
return {
content: [{
type: "text",
text: addMcpSignature(result, language)
}]
};
}
// Gestionnaire d'exploration de modules
async function handleModuleExploration(args) {
const { moduleName, projectPath = process.cwd(), language = 'auto' } = args;
const lang = detectLanguage(language);
logInfo(`Exploring module: ${moduleName}`);
const projectInfo = await detectDrupalProject(projectPath);
if (!projectInfo.isDrupal) {
return {
content: [{
type: "text",
text: addMcpSignature("❌ Ce projet ne semble pas être un projet Drupal valide.", lang)
}]
};
}
const modules = await analyzeCustomModules(projectInfo.drupalPath);
const targetModule = modules.find(m => m.name === moduleName);
if (!targetModule) {
return {
content: [{
type: "text",
text: addMcpSignature(`❌ Module "${moduleName}" non trouvé dans ce projet.`, lang)
}]
};
}
let result = `# 📦 Exploration du Module: ${targetModule.name}\n\n`;
result += `**Chemin**: \`${targetModule.path}\`\n\n`;
result += `**Description**: ${targetModule.info?.description || 'Module personnalisé'}\n\n`;
result += `**Dépendances**: ${targetModule.dependencies.join(', ') || 'Aucune'}\n\n`;
return {
content: [{
type: "text",
text: addMcpSignature(result, lang)
}]
};
}
// Gestionnaire d'évaluation de dette technique
async function handleTechnicalDebtAssessment(args) {
const { projectPath = process.cwd(), targetVersion = '11', language = 'auto' } = args;
const lang = detectLanguage(language);
logInfo(`Assessing technical debt for Drupal ${targetVersion}`);
const projectInfo = await detectDrupalProject(projectPath);
if (!projectInfo.isDrupal) {
return {
content: [{
type: "text",
text: addMcpSignature("❌ Ce projet ne semble pas être un projet Drupal valide.", lang)
}]
};
}
const compatibility = await analyzeDrupalCompatibility(projectInfo.drupalPath);
let result = `# 🔧 Évaluation de la Dette Technique\n\n`;
result += `**Version actuelle**: ${projectInfo.version}\n`;
result += `**Version cible**: Drupal ${targetVersion}\n\n`;
result += `## Complexité de Migration: ${compatibility.migrationComplexity}\n\n`;
result += `**Modules incompatibles**: ${compatibility.incompatibleModules.length}\n`;
result += `**Fonctions dépréciées**: ${compatibility.deprecatedFunctions.length}\n\n`;
if (compatibility.recommendations.length > 0) {
result += `## Recommandations\n\n`;
compatibility.recommendations.forEach(rec => {
result += `- ${rec}\n`;
});
}
return {
content: [{
type: "text",
text: addMcpSignature(result, lang)
}]
};
}
// Gestionnaire d'environnement Lando
async function handleLandoEnvironment(args) {
const { action, projectPath = process.cwd(), language = 'auto' } = args;
const lang = detectLanguage(language);
if (!SUPPORT_LANDO) {
return {
content: [{
type: "text",
text: addMcpSignature("❌ Support Lando non activé dans cette configuration.", lang)
}]
};
}
logInfo(`Lando action: ${action}`);
let result = `# 🐳 Gestion Environnement Lando\n\n`;
result += `**Action**: ${action}\n`;
result += `**Projet**: ${projectPath}\n\n`;
try {
switch (action) {
case 'analyze':
result += `## Analyse de l'environnement\n`;
result += `- Configuration Lando détectée\n`;
result += `- Services disponibles: web, database\n`;
break;
case 'start':
result += `## Démarrage de l'environnement\n`;
result += `Commande suggérée: \`lando start\`\n`;
break;
case 'stop':
result += `## Arrêt de l'environnement\n`;
result += `Commande suggérée: \`lando stop\`\n`;
break;
case 'rebuild':
result += `## Reconstruction de l'environnement\n`;
result += `Commande suggérée: \`lando rebuild\`\n`;
break;
}
}
catch (error) {
result += `❌ Erreur: ${error}\n`;
}
return {
content: [{
type: "text",
text: addMcpSignature(result, lang)
}]
};
}
// Gestionnaire Acquia Intelligence
async function handleAcquiaIntelligence(args) {
const { operation, environment, projectPath = process.cwd(), language = 'auto' } = args;
const lang = detectLanguage(language);
if (!SUPPORT_ACQUIA) {
return {
content: [{
type: "text",
text: addMcpSignature("❌ Support Acquia non activé dans cette configuration.", lang)
}]
};
}
logInfo(`Acquia operation: ${operation}`);
let result = `# ☁️ Acquia Intelligence Helper\n\n`;
result += `**Opération**: ${operation}\n`;
if (environment)
result += `**Environnement**: ${environment}\n`;
result += `**Projet**: ${projectPath}\n\n`;
switch (operation) {
case 'analyze':
result += `## Analyse de compatibilité Acquia Cloud\n`;
result += `- Vérification des requirements\n`;
result += `- Analyse des modules compatibles\n`;
break;
case 'deploy':
result += `## Préparation de déploiement\n`;
result += `Commande suggérée: \`acli push:artifact\`\n`;
break;
case 'sync':
result += `## Synchronisation\n`;
result += `Commande suggérée: \`acli pull:database\`\n`;
break;
case 'status':
result += `## Statut des environnements\n`;
result += `Commande suggérée: \`acli app:list\`\n`;
break;
}
return {
content: [{
type: "text",
text: addMcpSignature(result, lang)
}]
};
}
// Créer le serveur MCP
const server = new index_js_1.Server({
name: "matdrupal",
version: "1.2.7",
}, {
capabilities: {
tools: {},
},
});
// Définition des outils
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "analyze_project_overview",
description: "Analyse complète d'un projet MatDrupal avec détection automatique de structure docroot et support bilingue",
inputSchema: {
type: "object",
properties: {
projectPath: {
type: "string",
description: "Chemin vers le projet (optionnel, utilise le répertoire courant par défaut)"
},
analysisDepth: {
type: "string",
enum: ["quick", "comprehensive", "strategic"],
description: "Profondeur d'analyse: quick, comprehensive, ou strategic"
},
language: {
type: "string",
enum: ["fr", "en", "auto"],
description: "Langue de la réponse: fr, en, ou auto pour détection automatique"
}
}
}
},
{
name: "explore_custom_module",
description: "Exploration détaillée d'un module custom Drupal avec analyse des dépendances, hooks et logique métier",
inputSchema: {
type: "object",
properties: {
moduleName: {
type: "string",
description: "Nom du module à explorer"
},
projectPath: {
type: "string",
description: "Chemin vers le projet (optionnel)"
},
language: {
type: "string",
enum: ["fr", "en", "auto"],
description: "Langue de la réponse"
}
},
required: ["moduleName"]
}
},
{
name: "assess_technical_debt",
description: "Évaluation de la dette technique et génération d'un plan de migration vers Drupal 11",
inputSchema: {
type: "object",
properties: {
projectPath: {
type: "string",
description: "Chemin vers le projet (optionnel)"
},
targetVersion: {
type: "string",
enum: ["11", "10", "latest"],
description: "Version Drupal cible pour la migration"
},
language: {
type: "string",
enum: ["fr", "en", "auto"],
description: "Langue de la réponse"
}
}
}
},
{
name: "manage_lando_environment",
description: "Gestion et analyse de l'environnement Lando pour le développement local",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["start", "stop", "analyze", "rebuild"],
description: "Action à effectuer sur l'environnement Lando"
},
projectPath: {
type: "string",
description: "Chemin vers le projet (optionnel)"
},
language: {
type: "string",
enum: ["fr", "en", "auto"],
description: "Langue de la réponse"
}
},
required: ["action"]
}
},
{
name: "acquia_intelligence_helper",
description: "Assistant intelligent pour les workflows Acquia CLI et vérifications de compatibilité cloud",
inputSchema: {
type: "object",
properties: {
operation: {
type: "string",
enum: ["analyze", "deploy", "sync", "status"],
description: "Opération Acquia à effectuer"
},
environment: {
type: "string",
description: "Environnement Acquia (dev, test, prod)"
},
projectPath: {
type: "string",
description: "Chemin vers le projet (optionnel)"
},
language: {
type: "string",
enum: ["fr", "en", "auto"],
description: "Langue de la réponse"
}
},
required: ["operation"]
}
}
]
};
});
// Gestionnaire d'outils
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!args) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, "Arguments are required");
}
try {
if (name === "analyze_project_overview") {
return await handleProjectOverview(args);
}
else if (name === "explore_custom_module") {
return await handleModuleExploration(args);
}
else if (name === "assess_technical_debt") {
return await handleTechnicalDebtAssessment(args);
}
else if (name === "manage_lando_environment") {
return await handleLandoEnvironment(args);
}
else if (name === "acquia_intelligence_helper") {
return await handleAcquiaIntelligence(args);
}
else {
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
logInfo(`Error in tool ${name}: ${error}`);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Error executing tool ${name}: ${error}`);
}
});
// Point d'entrée principal
async function main() {
try {
// Mode test
if (process.argv.includes('--test')) {
logInfo('MatDrupal MCP Server - Test Mode');
logInfo(`Lando Support: ${SUPPORT_LANDO}`);
logInfo(`Acquia Support: ${SUPPORT_ACQUIA}`);
logInfo(`MatDrupal Mode: ${MATDRUPAL_MODE}`);
logInfo(`Bilingual Mode: ${BILINGUAL_MODE}`);
logInfo('Test completed successfully');
process.exit(0);
}
// Vérifier si on est en mode installation
if (process.argv.includes('--install')) {
logInfo('Installation mode detected - this should run the installer script separately');
process.exit(0);
}
// Démarrer le serveur MCP
const transport = new stdio_js_1.StdioServerTransport();
await server.connect(transport);
logInfo("MatDrupal Project Intelligence MCP Server started successfully");
}
catch (error) {
logInfo(`Fatal error starting server: ${error}`);
process.exit(1);
}
}
// Lancer le serveur
main().catch((error) => {
logInfo(`Fatal error: ${error}`);
process.exit(1);
});
//# sourceMappingURL=index.js.map