UNPKG

mat-drupal-mcp

Version:

MatDrupal MCP - AI Assistant for Drupal Projects with bilingual FR/EN support

624 lines 25.7 kB
#!/usr/bin/env node "use strict"; 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