UNPKG

backend-mcp

Version:

Generador automático de backends con Node.js, Express, Prisma y módulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.

579 lines (497 loc) 18.5 kB
#!/usr/bin/env node /** * Backend MCP Server - Advanced Version * Servidor MCP avanzado para el Backend Generator con herramientas granulares */ const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const { MCP_TOOLS, executeTool, projectConfig } = require('./mcp-tools'); const { authenticateToolCall, extractToken, AUTH_CONFIG } = require('./auth/jwt-auth'); // Configurar directorio de trabajo del usuario (para npx) // Detectar automáticamente el directorio desde donde se ejecuta npx // INIT_CWD es el directorio original desde donde se ejecutó npm/npx const userWorkingDirectory = process.env.INIT_CWD || process.cwd(); console.log(`📁 Directorio de trabajo detectado automáticamente: ${userWorkingDirectory}`); console.log(`💡 Los proyectos se generarán en este directorio automáticamente`); // Configurar DATABASE_URL desde argumentos de línea de comandos (patrón npx) const databaseUrl = process.argv[2]; if (databaseUrl && !databaseUrl.startsWith('-')) { console.log('🔗 DATABASE_URL detectada desde argumentos de línea de comandos'); // Configurar automáticamente la base de datos en projectConfig const provider = detectProviderFromUrl(databaseUrl); projectConfig.database = { ...projectConfig.database, provider: provider, url: databaseUrl, configuredFromAgent: true }; // Configurar directorio de trabajo del usuario en projectConfig projectConfig.userWorkingDirectory = userWorkingDirectory; console.log(`📊 Proveedor detectado: ${provider}`); console.log(`🔗 URL configurada: ${databaseUrl.replace(/:\/\/[^:]+:[^@]+@/, '://***:***@')}`); } /** * Detecta el proveedor de base de datos desde la URL */ function detectProviderFromUrl(url) { if (url.startsWith('postgresql://') || url.startsWith('postgres://')) return 'postgresql'; if (url.startsWith('mysql://')) return 'mysql'; if (url.startsWith('mongodb://') || url.startsWith('mongodb+srv://')) return 'mongodb'; if (url.startsWith('file:') || url.includes('.db')) return 'sqlite'; return 'postgresql'; // default } /** * Valida si una cadena es una URL de base de datos válida * @param {string} url - La URL a validar * @returns {boolean} - true si es válida, false en caso contrario */ function isValidDatabaseUrl(url) { if (!url || typeof url !== 'string') { return false; } // Patrones más estrictos para diferentes proveedores const patterns = [ // PostgreSQL: postgresql://user:pass@host:port/database /^postgresql:\/\/[^\s\/]+:[^\s\/]+@[^\s\/]+:\d+\/[^\s\/]+$/, /^postgres:\/\/[^\s\/]+:[^\s\/]+@[^\s\/]+:\d+\/[^\s\/]+$/, // MySQL: mysql://user:pass@host:port/database /^mysql:\/\/[^\s\/]+:[^\s\/]+@[^\s\/]+:\d+\/[^\s\/]+$/, // MongoDB: mongodb://user:pass@host:port/database /^mongodb:\/\/[^\s\/]+:[^\s\/]+@[^\s\/]+:\d+\/[^\s\/]+$/, /^mongodb\+srv:\/\/[^\s\/]+:[^\s\/]+@[^\s\/]+\/[^\s\/]+$/, // SQLite: file:./path/to/database.db /^file:\.\/.*\.db$/, /^sqlite:\.\/.*\.db$/ ]; return patterns.some(pattern => pattern.test(url)); } /** * Detecta el proveedor de base de datos desde la URL * @param {string} url - La URL de la base de datos * @returns {string} - El nombre del proveedor */ function detectDatabaseProvider(url) { if (url.startsWith('postgresql://') || url.startsWith('postgres://')) { return 'PostgreSQL'; } if (url.startsWith('mysql://')) { return 'MySQL'; } if (url.startsWith('mongodb://') || url.startsWith('mongodb+srv://')) { return 'MongoDB'; } if (url.startsWith('file:') || url.startsWith('sqlite:')) { return 'SQLite'; } return 'Desconocido'; } // Configuración del servidor MCP const MCP_CONFIG = { name: 'backend-mcp-advanced', version: '2.0.0', description: 'Servidor MCP avanzado para generación automática de backends con control granular', capabilities: [ 'configure_project', 'add_module', 'configure_database', 'define_table', 'setup_auth', 'create_endpoint', 'setup_email', 'setup_websockets', 'setup_cache', 'setup_monitoring', 'setup_docker', 'setup_testing', 'validate_config', 'generate_project' ], tools: Object.keys(MCP_TOOLS) }; /** * Función principal del servidor MCP */ function startMCPServer() { const args = process.argv.slice(2); // Detectar DATABASE_URL desde argumentos o variables de entorno let databaseUrl = process.env.DATABASE_URL; // Si hay argumentos y el primero no es una opción, asumimos que es DATABASE_URL if (args.length > 0 && !args[0].startsWith('-')) { const potentialUrl = args[0]; if (potentialUrl.includes('://')) { databaseUrl = potentialUrl; process.env.DATABASE_URL = databaseUrl; console.error(`🔗 Base de datos configurada: ${detectDatabaseProvider(databaseUrl)}`); } } // Manejar argumentos especiales if (args.includes('--help') || args.includes('-h')) { showHelp(); return; } if (args.includes('--version') || args.includes('-v')) { console.log(`Backend MCP Server v${MCP_CONFIG.version}`); return; } // Determinar modo de operación const hasToolFlag = args.includes('--tool') || args.includes('-t'); const hasConfigFlag = args.includes('--config') || args.includes('-c'); if (hasToolFlag) { handleToolMode(args).catch(error => { console.error('❌ Error en modo herramientas:', error.message); process.exit(1); }); } else if (hasConfigFlag) { handleConfigMode(args); } else { // Modo MCP Server - Implementación del protocolo MCP startMCPProtocolServer(databaseUrl); } } /** * Inicia el servidor MCP con el protocolo estándar */ function startMCPProtocolServer(databaseUrl) { console.error(`🚀 Iniciando Backend MCP Server...`); if (databaseUrl) { console.error(`🔗 Base de datos: ${detectDatabaseProvider(databaseUrl)}`); } else { console.error(`⚠️ No hay base de datos configurada`); console.error(`💡 Usa la herramienta 'configure_database' para configurar una base de datos`); } console.error(`📋 Herramientas disponibles: ${Object.keys(MCP_TOOLS).length}`); console.error(`✅ Servidor MCP iniciado correctamente`); console.error(`🔧 Esperando conexiones de agentes IA...`); // Implementación del protocolo MCP usando stdio process.stdin.setEncoding('utf8'); let buffer = ''; process.stdin.on('data', (chunk) => { buffer += chunk; // Procesar mensajes completos (separados por \n) const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Mantener la línea incompleta en el buffer for (const line of lines) { if (line.trim()) { try { const message = JSON.parse(line); handleMCPMessage(message).catch(error => { console.error(`Error handling MCP message: ${error.message}`); if (message.id) { sendMCPError(message.id, -32603, `Internal error: ${error.message}`); } }); } catch (error) { console.error(`Error parsing JSON: ${error.message}`); } } } }); process.stdin.on('end', () => { process.exit(0); }); // Enviar mensaje de inicialización sendMCPResponse({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }); } /** * Maneja mensajes del protocolo MCP */ async function handleMCPMessage(message) { try { switch (message.method) { case 'initialize': handleInitialize(message); break; case 'tools/list': handleToolsList(message); break; case 'tools/call': await handleToolCall(message); break; default: sendMCPError(message.id, -32601, `Method not found: ${message.method}`); } } catch (error) { sendMCPError(message.id, -32603, `Internal error: ${error.message}`); } } /** * Maneja la inicialización del servidor MCP */ function handleInitialize(message) { const response = { jsonrpc: '2.0', id: message.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {}, logging: {} }, serverInfo: { name: MCP_CONFIG.name, version: MCP_CONFIG.version } } }; sendMCPResponse(response); } /** * Maneja la lista de herramientas */ function handleToolsList(message) { const tools = Object.entries(MCP_TOOLS).map(([name, tool]) => ({ name, description: tool.description, inputSchema: tool.inputSchema })); const response = { jsonrpc: '2.0', id: message.id, result: { tools } }; sendMCPResponse(response); } /** * Maneja la ejecución de herramientas con autenticación JWT */ async function handleToolCall(message) { try { const { name, arguments: args } = message.params; // Verificar autenticación JWT para herramientas try { const user = await authenticateToolCall(name, args || {}); console.error(`🔐 Usuario autenticado: ${user.email} (Plan: ${user.plan})`); } catch (authError) { sendMCPError(message.id, -32001, authError.message); return; } const result = await executeTool(name, args || {}); const response = { jsonrpc: '2.0', id: message.id, result: { content: [ { type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) } ] } }; sendMCPResponse(response); } catch (error) { sendMCPError(message.id, -32603, `Tool execution error: ${error.message}`); } } /** * Envía una respuesta MCP */ function sendMCPResponse(response) { process.stdout.write(JSON.stringify(response) + '\n'); } /** * Envía un error MCP */ function sendMCPError(id, code, message) { const error = { jsonrpc: '2.0', id, error: { code, message } }; sendMCPResponse(error); } /** * Maneja el modo de herramientas avanzadas con autenticación JWT */ async function handleToolMode(args) { const toolIndex = Math.max(args.indexOf('--tool'), args.indexOf('-t')); const toolName = args[toolIndex + 1]; const paramsIndex = Math.max(args.indexOf('--params'), args.indexOf('-p')); if (!toolName) { console.error('❌ Error: Se requiere especificar una herramienta'); console.log('\n🔧 Herramientas disponibles:'); Object.keys(MCP_TOOLS).forEach(tool => { console.log(` • ${tool}: ${MCP_TOOLS[tool].description}`); }); process.exit(1); } let params = {}; if (paramsIndex !== -1 && args[paramsIndex + 1]) { try { params = JSON.parse(args[paramsIndex + 1]); } catch (error) { console.error('❌ Error: Parámetros JSON inválidos'); process.exit(1); } } try { // Verificar autenticación JWT console.log('🔐 Verificando autenticación...'); const user = await authenticateToolCall(toolName, params); console.log(`✅ Usuario autenticado: ${user.email} (Plan: ${user.plan})`); console.log(`🔧 Ejecutando herramienta: ${toolName}`); const result = await executeTool(toolName, params); console.log('✅ Resultado:'); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error('❌ Error:', error.message); if (error.message.includes('Token JWT requerido')) { console.log('\n💡 Para obtener tu token JWT:'); console.log(' 1. Visita: https://backend-mcp.com/pricing'); console.log(' 2. Compra tu plan por $5 USD/mes'); console.log(' 3. Usa: --jwt tu_token_aqui'); } process.exit(1); } } /** * Maneja el modo de configuración JSON */ async function handleConfigMode(args) { const configIndex = Math.max(args.indexOf('--config'), args.indexOf('-c')); const configPath = args[configIndex + 1]; if (!configPath || !fs.existsSync(configPath)) { console.error('❌ Error: Archivo de configuración no encontrado'); process.exit(1); } try { const configData = JSON.parse(fs.readFileSync(configPath, 'utf8')); console.log('📋 Ejecutando configuración desde archivo...'); // Ejecutar cada paso de configuración for (const step of configData.steps || []) { console.log(`🔧 Ejecutando: ${step.tool}`); const result = await executeTool(step.tool, step.params); console.log(`✅ ${result.message}`); } console.log('🎉 Configuración completada exitosamente'); } catch (error) { console.error('❌ Error:', error.message); process.exit(1); } } /** * Maneja el modo de compatibilidad (original) */ function handleLegacyMode(args) { // Extraer parámetros const projectNameIndex = Math.max(args.indexOf('--project-name'), args.indexOf('-p')); const projectName = projectNameIndex !== -1 ? args[projectNameIndex + 1] : null; const description = args.find(arg => !arg.startsWith('-') && arg !== projectName) || 'Proyecto generado por MCP'; if (!projectName && !description) { console.error('Error: Se requiere al menos un nombre de proyecto o descripción'); showHelp(); process.exit(1); } // Ejecutar el orchestrator con los parámetros const orchestratorPath = path.join(__dirname, 'orchestrator.js'); const orchestratorArgs = []; if (projectName) { orchestratorArgs.push('--project-name', projectName); } orchestratorArgs.push(description); console.log(`🚀 Iniciando Backend MCP Server (modo compatibilidad)...`); console.log(`📦 Proyecto: ${projectName || 'auto-generado'}`); console.log(`📝 Descripción: ${description}`); console.log(`⚙️ Ejecutando: node ${orchestratorPath} ${orchestratorArgs.join(' ')}`); const orchestrator = spawn('node', [orchestratorPath, ...orchestratorArgs], { stdio: 'inherit', cwd: __dirname }); orchestrator.on('close', (code) => { if (code === 0) { console.log('✅ Backend generado exitosamente'); } else { console.error(`❌ Error en la generación (código: ${code})`); process.exit(code); } }); orchestrator.on('error', (error) => { console.error('❌ Error al ejecutar el orchestrator:', error.message); process.exit(1); }); } /** * Mostrar ayuda del comando */ function showHelp() { console.log(`\n🔧 Backend MCP Server v${MCP_CONFIG.version}`); console.log(`📋 ${MCP_CONFIG.description}\n`); console.log('📖 Uso:'); console.log(' # Modo MCP con npx (recomendado)'); console.log(' npx backend-mcp "<DATABASE_URL>"'); console.log(' npx backend-mcp "postgresql://user:pass@localhost:5432/db"\n'); console.log(' # Modo compatibilidad (original)'); console.log(' node mcp-server.js [opciones] <descripción>'); console.log(' npx backend-mcp [opciones] <descripción>\n'); console.log(' # Modo herramientas avanzadas'); console.log(' node mcp-server.js --tool <herramienta> --params <json>'); console.log(' node mcp-server.js -t <herramienta> -p <json>\n'); console.log(' # Modo configuración JSON'); console.log(' node mcp-server.js --config <archivo.json>'); console.log(' node mcp-server.js -c <archivo.json>\n'); console.log('🔧 Opciones:'); console.log(' --project-name, -p <nombre> Nombre del proyecto (modo compatibilidad)'); console.log(' --tool, -t <herramienta> Ejecutar herramienta específica'); console.log(' --params, -p <json> Parámetros JSON para la herramienta'); console.log(' --config, -c <archivo> Ejecutar desde archivo de configuración'); console.log(' --help, -h Mostrar esta ayuda\n'); console.log('💡 Ejemplos:'); console.log(' # Modo MCP con npx (para agentes IA)'); console.log(' npx backend-mcp "postgresql://user:pass@localhost:5432/mydb"'); console.log(' npx backend-mcp "mysql://user:pass@localhost:3306/mydb"'); console.log(' npx backend-mcp "mongodb://user:pass@localhost:27017/mydb"\n'); console.log(' # Configuración en agente IA:'); console.log(' {'); console.log(' "mcpServers": {'); console.log(' "BackendGenerator": {'); console.log(' "command": "npx",'); console.log(' "args": ["-y", "backend-mcp", "postgresql://user:pass@host:5432/db"],'); console.log(' "env": {}'); console.log(' }'); console.log(' }'); console.log(' }\n'); console.log(' # Modo compatibilidad'); console.log(' node mcp-server.js -p "ecommerce" "API para tienda online"\n'); console.log(' # Configurar proyecto con herramientas'); console.log(' node mcp-server.js -t configure_project -p \'{"projectName":"mi-api","description":"Mi API"}\'') console.log(' node mcp-server.js -t define_table -p \'{"tableName":"User","fields":[{"name":"id","type":"Int"}]}\'') console.log(' node mcp-server.js -t generate_project -p \'{"installDependencies":true}\'') console.log(''); console.log(' # Desde archivo de configuración'); console.log(' node mcp-server.js -c ./config/ecommerce-config.json\n'); console.log('🚀 Herramientas MCP Disponibles:'); Object.entries(MCP_TOOLS).forEach(([name, tool]) => { console.log(` • ${name}: ${tool.description}`); }); console.log(''); console.log('📚 Documentación:'); console.log(' • Guía completa: AI-TOOLS-GUIDE.md'); console.log(' • Esquemas JSON: schemas/mcp-tools-schemas.json'); console.log(' • Configuración MCP: MCP-SETUP.md'); console.log(''); } // Manejar argumentos de ayuda if (process.argv.includes('--help') || process.argv.includes('-h')) { showHelp(); process.exit(0); } // Iniciar el servidor MCP if (require.main === module) { startMCPServer(); } module.exports = { startMCPServer, MCP_CONFIG, MCP_TOOLS, executeTool };