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.

921 lines (718 loc) β€’ 24.7 kB
#!/usr/bin/env node /** * MCP Module Generator Script * Generates new modules with proper structure and templates */ const fs = require('fs'); const path = require('path'); const readline = require('readline'); class ModuleGenerator { constructor() { this.moduleName = ''; this.moduleType = 'service'; this.features = { database: false, auth: false, validation: false, testing: true, documentation: true }; this.dependencies = []; this.integrations = []; } async generate() { console.log('πŸ”§ MCP Module Generator'); console.log('========================\n'); try { await this.promptModuleDetails(); await this.selectFeatures(); await this.createModuleStructure(); await this.generateManifest(); await this.generateInitFile(); await this.generateTemplates(); await this.generateDocumentation(); console.log('\nπŸŽ‰ Module generated successfully!'); console.log(`πŸ“ Module location: modules/${this.moduleName}`); console.log('\nπŸ“‹ Next steps:'); console.log('1. Review the generated files'); console.log('2. Customize templates as needed'); console.log('3. Test the module with the orchestrator'); } catch (error) { console.error('❌ Error generating module:', error.message); process.exit(1); } } async promptModuleDetails() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); this.moduleName = await this.question(rl, 'πŸ“ Module name: '); const typeChoice = await this.question(rl, 'πŸ—οΈ Module type (service/middleware/utility/integration) [service]: '); this.moduleType = typeChoice.toLowerCase() || 'service'; rl.close(); } async selectFeatures() { console.log('\nβš™οΈ Module features:'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const dbChoice = await this.question(rl, 'πŸ—„οΈ Include database integration? (y/N): '); this.features.database = dbChoice.toLowerCase() === 'y'; const authChoice = await this.question(rl, 'πŸ” Include authentication? (y/N): '); this.features.auth = authChoice.toLowerCase() === 'y'; const validationChoice = await this.question(rl, 'βœ… Include validation? (y/N): '); this.features.validation = validationChoice.toLowerCase() === 'y'; rl.close(); // Set dependencies based on features if (this.features.database) { this.dependencies.push('database'); this.integrations.push('database'); } if (this.features.auth) { this.dependencies.push('auth'); this.integrations.push('auth'); } if (this.features.validation) { this.dependencies.push('validation'); this.integrations.push('validation'); } } async createModuleStructure() { console.log('\nπŸ—οΈ Creating module structure...'); const modulePath = path.join('modules', this.moduleName); if (fs.existsSync(modulePath)) { throw new Error(`Module ${this.moduleName} already exists`); } // Create module directories const directories = [ modulePath, path.join(modulePath, 'templates'), path.join(modulePath, 'examples'), path.join(modulePath, 'schemas'), path.join(modulePath, 'tests') ]; directories.forEach(dir => { fs.mkdirSync(dir, { recursive: true }); }); } async generateManifest() { const manifest = { name: this.moduleName, version: '1.0.0', description: `${this.moduleName.charAt(0).toUpperCase() + this.moduleName.slice(1)} module for MCP Backend`, type: this.moduleType, author: 'MCP Module Generator', license: 'MIT', triggers: [ `init-${this.moduleName}`, `setup-${this.moduleName}`, `configure-${this.moduleName}` ], entry_points: { main: 'init.js', templates: 'templates/', schemas: 'schemas/', examples: 'examples/' }, dependencies: { npm: this.getNpmDependencies(), system: this.getSystemDependencies(), modules: this.dependencies }, environment: { required: this.getRequiredEnvVars(), optional: this.getOptionalEnvVars() }, features: Object.keys(this.features).filter(key => this.features[key]), automation: { setup: { description: `Set up ${this.moduleName} module`, steps: this.getSetupSteps() }, configure: { description: `Configure ${this.moduleName} module`, steps: this.getConfigSteps() } }, generated_files: this.getGeneratedFiles(), integrations: this.integrations.map(integration => ({ module: integration, type: 'dependency', description: `Integrates with ${integration} module` })) }; const manifestPath = path.join('modules', this.moduleName, 'manifest.yaml'); const yamlContent = this.objectToYaml(manifest); fs.writeFileSync(manifestPath, yamlContent); } async generateInitFile() { const initContent = `const fs = require('fs'); const path = require('path'); /** * ${this.moduleName.charAt(0).toUpperCase() + this.moduleName.slice(1)} Module Initializer * Generated by MCP Module Generator */ class ${this.toPascalCase(this.moduleName)}Module { constructor(options = {}) { this.options = { projectPath: process.cwd(), ...options }; this.moduleName = '${this.moduleName}'; } async initialize() { console.log(\`πŸ”§ Initializing \${this.moduleName} module...\`); try { await this.createDirectories(); await this.generateConfigFiles(); ${this.features.database ? 'await this.setupDatabase();' : ''} ${this.features.auth ? 'await this.setupAuthentication();' : ''} ${this.features.validation ? 'await this.setupValidation();' : ''} await this.generateServiceFiles(); await this.updatePackageJson(); console.log(\`βœ… \${this.moduleName} module initialized successfully\`); return true; } catch (error) { console.error(\`❌ Error initializing \${this.moduleName} module:\`, error.message); throw error; } } async createDirectories() { const directories = [ 'src/services/${this.moduleName}', 'src/controllers/${this.moduleName}', 'src/routes/${this.moduleName}', 'src/types/${this.moduleName}', 'tests/${this.moduleName}' ]; directories.forEach(dir => { const fullPath = path.join(this.options.projectPath, dir); if (!fs.existsSync(fullPath)) { fs.mkdirSync(fullPath, { recursive: true }); } }); } async generateConfigFiles() { // Generate configuration file const configContent = \`export interface ${this.toPascalCase(this.moduleName)}Config { enabled: boolean; ${this.getConfigInterface()} } export const ${this.toCamelCase(this.moduleName)}Config: ${this.toPascalCase(this.moduleName)}Config = { enabled: process.env.${this.moduleName.toUpperCase()}_ENABLED === 'true', ${this.getDefaultConfig()} }; \`; const configPath = path.join(this.options.projectPath, 'src/config', \`\${this.moduleName}.ts\`); this.ensureDirectoryExists(path.dirname(configPath)); fs.writeFileSync(configPath, configContent); } ${this.features.database ? this.getDatabaseSetupMethod() : ''} ${this.features.auth ? this.getAuthSetupMethod() : ''} ${this.features.validation ? this.getValidationSetupMethod() : ''} async generateServiceFiles() { // Generate service class const serviceContent = \`import { ${this.toPascalCase(this.moduleName)}Config } from '../../config/${this.moduleName}'; ${this.getServiceImports()} export class ${this.toPascalCase(this.moduleName)}Service { private config: ${this.toPascalCase(this.moduleName)}Config; constructor(config: ${this.toPascalCase(this.moduleName)}Config) { this.config = config; } ${this.getServiceMethods()} } export const ${this.toCamelCase(this.moduleName)}Service = new ${this.toPascalCase(this.moduleName)}Service(${this.toCamelCase(this.moduleName)}Config); \`; const servicePath = path.join(this.options.projectPath, 'src/services', this.moduleName, 'index.ts'); fs.writeFileSync(servicePath, serviceContent); // Generate controller const controllerContent = \`import { Request, Response, NextFunction } from 'express'; import { ${this.toCamelCase(this.moduleName)}Service } from '../../services/${this.moduleName}'; ${this.getControllerImports()} export class ${this.toPascalCase(this.moduleName)}Controller { ${this.getControllerMethods()} } export const ${this.toCamelCase(this.moduleName)}Controller = new ${this.toPascalCase(this.moduleName)}Controller(); \`; const controllerPath = path.join(this.options.projectPath, 'src/controllers', this.moduleName, 'index.ts'); fs.writeFileSync(controllerPath, controllerContent); // Generate routes const routesContent = \`import { Router } from 'express'; import { ${this.toCamelCase(this.moduleName)}Controller } from '../../controllers/${this.moduleName}'; ${this.getRoutesImports()} const router = Router(); ${this.getRoutes()} export default router; \`; const routesPath = path.join(this.options.projectPath, 'src/routes', this.moduleName, 'index.ts'); fs.writeFileSync(routesPath, routesContent); // Generate types const typesContent = \`${this.getTypeDefinitions()}\`; const typesPath = path.join(this.options.projectPath, 'src/types', this.moduleName, 'index.ts'); fs.writeFileSync(typesPath, typesContent); } async updatePackageJson() { const packageJsonPath = path.join(this.options.projectPath, 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // Add new dependencies const newDependencies = this.getNpmDependencies(); Object.assign(packageJson.dependencies, newDependencies); // Add new scripts const newScripts = this.getPackageScripts(); Object.assign(packageJson.scripts, newScripts); fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); } } ensureDirectoryExists(dirPath) { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } ${this.getHelperMethods()} } module.exports = ${this.toPascalCase(this.moduleName)}Module;`; const initPath = path.join('modules', this.moduleName, 'init.js'); fs.writeFileSync(initPath, initContent); } async generateTemplates() { // Generate service template const serviceTemplate = this.getServiceTemplate(); const serviceTemplatePath = path.join('modules', this.moduleName, 'templates', 'service.hbs'); fs.writeFileSync(serviceTemplatePath, serviceTemplate); // Generate controller template const controllerTemplate = this.getControllerTemplate(); const controllerTemplatePath = path.join('modules', this.moduleName, 'templates', 'controller.hbs'); fs.writeFileSync(controllerTemplatePath, controllerTemplate); // Generate routes template const routesTemplate = this.getRoutesTemplate(); const routesTemplatePath = path.join('modules', this.moduleName, 'templates', 'routes.hbs'); fs.writeFileSync(routesTemplatePath, routesTemplate); } async generateDocumentation() { const readmeContent = `# ${this.moduleName.charAt(0).toUpperCase() + this.moduleName.slice(1)} Module ${this.moduleName.charAt(0).toUpperCase() + this.moduleName.slice(1)} module for MCP Backend framework. ## Features ${Object.keys(this.features).filter(key => this.features[key]).map(feature => `- βœ… ${feature.charAt(0).toUpperCase() + feature.slice(1)}`).join('\n')} ## Installation This module is automatically installed when using the MCP Backend Generator. ## Configuration ### Environment Variables ${this.getRequiredEnvVars().map(env => `- \`${env}\` (required)`).join('\n')} ${this.getOptionalEnvVars().map(env => `- \`${env}\` (optional)`).join('\n')} ### Configuration File \`\`\`typescript // src/config/${this.moduleName}.ts export const ${this.toCamelCase(this.moduleName)}Config = { enabled: true, // Add your configuration here }; \`\`\` ## Usage ### Service \`\`\`typescript import { ${this.toCamelCase(this.moduleName)}Service } from './services/${this.moduleName}'; // Use the service const result = await ${this.toCamelCase(this.moduleName)}Service.someMethod(); \`\`\` ### Controller \`\`\`typescript import { ${this.toCamelCase(this.moduleName)}Controller } from './controllers/${this.moduleName}'; // Use in routes router.get('/', ${this.toCamelCase(this.moduleName)}Controller.handleRequest); \`\`\` ## API Endpoints ${this.getApiDocumentation()} ## Testing \`\`\`bash npm test -- ${this.moduleName} \`\`\` ## Dependencies ${this.dependencies.map(dep => `- ${dep}`).join('\n')} ## Integration ${this.integrations.map(integration => `- Integrates with \`${integration}\` module`).join('\n')} ## License MIT`; const readmePath = path.join('modules', this.moduleName, 'README.md'); fs.writeFileSync(readmePath, readmeContent); } // Helper methods question(rl, prompt) { return new Promise((resolve) => { rl.question(prompt, (answer) => { resolve(answer.trim()); }); }); } toPascalCase(str) { return str.replace(/(^|-)([a-z])/g, (_, __, letter) => letter.toUpperCase()); } toCamelCase(str) { const pascal = this.toPascalCase(str); return pascal.charAt(0).toLowerCase() + pascal.slice(1); } objectToYaml(obj, indent = 0) { let yaml = ''; const spaces = ' '.repeat(indent); for (const [key, value] of Object.entries(obj)) { if (Array.isArray(value)) { yaml += `${spaces}${key}:\n`; value.forEach(item => { if (typeof item === 'object') { yaml += `${spaces} -\n${this.objectToYaml(item, indent + 2).replace(/^/gm, ' ')}`; } else { yaml += `${spaces} - ${item}\n`; } }); } else if (typeof value === 'object' && value !== null) { yaml += `${spaces}${key}:\n${this.objectToYaml(value, indent + 1)}`; } else { yaml += `${spaces}${key}: ${value}\n`; } } return yaml; } // Configuration methods getNpmDependencies() { const deps = {}; if (this.features.database) { deps['@prisma/client'] = '^5.0.0'; } if (this.features.validation) { deps['joi'] = '^17.9.2'; } return deps; } getSystemDependencies() { return []; } getRequiredEnvVars() { const vars = [`${this.moduleName.toUpperCase()}_ENABLED`]; if (this.features.database) { vars.push(`${this.moduleName.toUpperCase()}_DB_URL`); } return vars; } getOptionalEnvVars() { return [ `${this.moduleName.toUpperCase()}_LOG_LEVEL`, `${this.moduleName.toUpperCase()}_TIMEOUT` ]; } getSetupSteps() { return [ 'Create directory structure', 'Generate configuration files', 'Set up service classes', 'Create API endpoints', 'Update package.json' ]; } getConfigSteps() { return [ 'Validate environment variables', 'Initialize service connections', 'Set up middleware', 'Register routes' ]; } getGeneratedFiles() { return [ `src/config/${this.moduleName}.ts`, `src/services/${this.moduleName}/index.ts`, `src/controllers/${this.moduleName}/index.ts`, `src/routes/${this.moduleName}/index.ts`, `src/types/${this.moduleName}/index.ts` ]; } // Template generation methods getServiceTemplate() { return `import { {{pascalCase name}}Config } from '../../config/{{name}}'; export class {{pascalCase name}}Service { private config: {{pascalCase name}}Config; constructor(config: {{pascalCase name}}Config) { this.config = config; } async initialize(): Promise<void> { // Initialize service } async process(data: any): Promise<any> { // Process data return data; } async cleanup(): Promise<void> { // Cleanup resources } } export const {{camelCase name}}Service = new {{pascalCase name}}Service({{camelCase name}}Config);`; } getControllerTemplate() { return `import { Request, Response, NextFunction } from 'express'; import { {{camelCase name}}Service } from '../../services/{{name}}'; export class {{pascalCase name}}Controller { async handleRequest(req: Request, res: Response, next: NextFunction): Promise<void> { try { const result = await {{camelCase name}}Service.process(req.body); res.json({ success: true, data: result }); } catch (error) { next(error); } } async getStatus(req: Request, res: Response): Promise<void> { res.json({ status: 'OK', service: '{{name}}' }); } } export const {{camelCase name}}Controller = new {{pascalCase name}}Controller();`; } getRoutesTemplate() { return `import { Router } from 'express'; import { {{camelCase name}}Controller } from '../../controllers/{{name}}'; const router = Router(); router.post('/', {{camelCase name}}Controller.handleRequest); router.get('/status', {{camelCase name}}Controller.getStatus); export default router;`; } // Code generation methods getConfigInterface() { let config = 'timeout: number;\n logLevel: string;'; if (this.features.database) { config += '\n databaseUrl: string;'; } return config; } getDefaultConfig() { let config = `timeout: parseInt(process.env.${this.moduleName.toUpperCase()}_TIMEOUT || '5000'),\n logLevel: process.env.${this.moduleName.toUpperCase()}_LOG_LEVEL || 'info'`; if (this.features.database) { config += `,\n databaseUrl: process.env.${this.moduleName.toUpperCase()}_DB_URL || ''`; } return config; } getServiceImports() { let imports = ''; if (this.features.database) { imports += "import { PrismaClient } from '@prisma/client';\n"; } if (this.features.validation) { imports += "import Joi from 'joi';\n"; } return imports; } getServiceMethods() { return `async initialize(): Promise<void> { if (!this.config.enabled) { return; } // Initialize service } async process(data: any): Promise<any> { // Process data return data; } async cleanup(): Promise<void> { // Cleanup resources }`; } getControllerImports() { let imports = ''; if (this.features.auth) { imports += "import { authMiddleware } from '../../middleware/auth';\n"; } if (this.features.validation) { imports += "import { validateRequest } from '../../middleware/validation';\n"; } return imports; } getControllerMethods() { return `async handleRequest(req: Request, res: Response, next: NextFunction): Promise<void> { try { const result = await ${this.toCamelCase(this.moduleName)}Service.process(req.body); res.json({ success: true, data: result }); } catch (error) { next(error); } } async getStatus(req: Request, res: Response): Promise<void> { res.json({ status: 'OK', service: '${this.moduleName}' }); }`; } getRoutesImports() { let imports = ''; if (this.features.auth) { imports += "import { authMiddleware } from '../../middleware/auth';\n"; } return imports; } getRoutes() { let routes = `router.post('/', ${this.toCamelCase(this.moduleName)}Controller.handleRequest); router.get('/status', ${this.toCamelCase(this.moduleName)}Controller.getStatus);`; if (this.features.auth) { routes = routes.replace('router.post(\'/\'', 'router.post(\'/\', authMiddleware,'); } return routes; } getTypeDefinitions() { return `export interface ${this.toPascalCase(this.moduleName)}Request { // Define request interface } export interface ${this.toPascalCase(this.moduleName)}Response { success: boolean; data?: any; error?: string; } export interface ${this.toPascalCase(this.moduleName)}Config { enabled: boolean; timeout: number; logLevel: string; }`; } getPackageScripts() { return { [`${this.moduleName}:test`]: `jest tests/${this.moduleName}`, [`${this.moduleName}:dev`]: `tsx watch src/services/${this.moduleName}/index.ts` }; } getApiDocumentation() { return `### POST /${this.moduleName} Process ${this.moduleName} request. **Request:** \`\`\`json { "data": "example" } \`\`\` **Response:** \`\`\`json { "success": true, "data": "processed" } \`\`\` ### GET /${this.moduleName}/status Get service status. **Response:** \`\`\`json { "status": "OK", "service": "${this.moduleName}" } \`\`\``; } getDatabaseSetupMethod() { return `async setupDatabase() { // Generate Prisma schema const schemaContent = \`generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model ${this.toPascalCase(this.moduleName)} { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("${this.moduleName}") } \`; const schemaPath = path.join(this.options.projectPath, 'prisma', 'schema.prisma'); this.ensureDirectoryExists(path.dirname(schemaPath)); if (fs.existsSync(schemaPath)) { // Append to existing schema const existingSchema = fs.readFileSync(schemaPath, 'utf8'); if (!existingSchema.includes(\`model \${this.toPascalCase(this.moduleName)}\`)) { fs.appendFileSync(schemaPath, '\n' + schemaContent.split('\n').slice(6).join('\n')); } } else { fs.writeFileSync(schemaPath, schemaContent); } }`; } getAuthSetupMethod() { return `async setupAuthentication() { // Generate auth middleware const middlewareContent = \`import { Request, Response, NextFunction } from 'express'; import { authService } from '../services/auth'; export const ${this.toCamelCase(this.moduleName)}AuthMiddleware = async ( req: Request, res: Response, next: NextFunction ): Promise<void> => { try { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { res.status(401).json({ error: 'No token provided' }); return; } const user = await authService.verifyToken(token); req.user = user; next(); } catch (error) { res.status(401).json({ error: 'Invalid token' }); } }; \`; const middlewarePath = path.join(this.options.projectPath, 'src/middleware', \`\${this.moduleName}-auth.ts\`); this.ensureDirectoryExists(path.dirname(middlewarePath)); fs.writeFileSync(middlewarePath, middlewareContent); }`; } getValidationSetupMethod() { return `async setupValidation() { // Generate validation schemas const schemaContent = \`import Joi from 'joi'; export const ${this.toCamelCase(this.moduleName)}Schema = Joi.object({ // Define validation schema }); export const validate${this.toPascalCase(this.moduleName)} = (data: any) => { return ${this.toCamelCase(this.moduleName)}Schema.validate(data); }; \`; const schemaPath = path.join(this.options.projectPath, 'src/schemas', \`\${this.moduleName}.ts\`); this.ensureDirectoryExists(path.dirname(schemaPath)); fs.writeFileSync(schemaPath, schemaContent); }`; } getHelperMethods() { return `getConfigInterface() { return this.getConfigInterface(); } getDefaultConfig() { return this.getDefaultConfig(); } getServiceImports() { return this.getServiceImports(); } getServiceMethods() { return this.getServiceMethods(); } getControllerImports() { return this.getControllerImports(); } getControllerMethods() { return this.getControllerMethods(); } getRoutesImports() { return this.getRoutesImports(); } getRoutes() { return this.getRoutes(); } getTypeDefinitions() { return this.getTypeDefinitions(); } getPackageScripts() { return this.getPackageScripts(); } getNpmDependencies() { return this.getNpmDependencies(); }`; } } // Run the generator if (require.main === module) { const generator = new ModuleGenerator(); generator.generate().catch(console.error); } module.exports = ModuleGenerator;