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
JavaScript
/**
* 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
};