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
JavaScript
/**
* 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
createdAt DateTime
updatedAt DateTime
@
}
\`;
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;