server-generator.ts•14.2 kB
import { ParsedAPICollection } from '../parser/types.js';
import { GeneratedMCPTool } from './mcp-tool-generator.js';
import { TemplateContext } from './template-engine.js';
/**
* Configuration for server generation
*/
export interface ServerGeneratorConfig {
/** Server name */
serverName: string;
/** Server version */
serverVersion?: string;
/** Server description */
serverDescription?: string;
/** Package name for generated server */
packageName?: string;
/** Author information */
author?: string;
/** License */
license?: string;
/** Repository URL */
repository?: string;
/** Enable debug logging */
debug?: boolean;
/** Include test files */
includeTests?: boolean;
/** Include documentation */
includeDocumentation?: boolean;
/** TypeScript configuration */
typescript?: boolean;
/** Server configuration options */
serverConfig?: {
timeout?: number;
maxResponseLength?: number;
allowLocalhost?: boolean;
allowPrivateIps?: boolean;
userAgent?: string;
};
}
/**
* Generated server structure
*/
export interface GeneratedServer {
/** Server metadata */
name: string;
version: string;
description: string;
packageName: string;
/** Generated tools */
tools: GeneratedMCPTool[];
/** API specifications */
apis: ParsedAPICollection['apis'];
/** Server configuration */
configuration: {
timeout: number;
maxResponseLength: number;
allowLocalhost: boolean;
allowPrivateIps: boolean;
userAgent: string;
};
/** Package dependencies */
dependencies: PackageDependency[];
/** Import statements */
imports: ImportStatement[];
/** Export statements */
exports: ExportStatement[];
/** Generated metadata */
metadata: {
generatedAt: string;
generatedBy: string;
sourceFile?: string;
version: string;
toolCount: number;
apiCount: number;
};
}
/**
* Package dependency information
*/
export interface PackageDependency {
name: string;
version: string;
type: 'dependency' | 'devDependency';
description?: string;
}
/**
* Import statement information
*/
export interface ImportStatement {
module: string;
imports: string[];
isDefault?: boolean;
isNamespace?: boolean;
alias?: string;
}
/**
* Export statement information
*/
export interface ExportStatement {
name: string;
type: 'function' | 'class' | 'interface' | 'type' | 'const';
isDefault?: boolean;
}
/**
* Server generation result
*/
export interface ServerGenerationResult {
/** Generated server structure */
server: GeneratedServer;
/** Template context for code generation */
templateContext: TemplateContext;
/** Generation statistics */
stats: {
toolsGenerated: number;
apisProcessed: number;
dependenciesAdded: number;
importsGenerated: number;
exportsGenerated: number;
};
/** Any warnings during generation */
warnings: string[];
}
/**
* Generator for MCP server structure and metadata
*/
export class ServerGenerator {
private config: Required<ServerGeneratorConfig>;
constructor(config: ServerGeneratorConfig) {
this.config = {
serverName: config.serverName,
serverVersion: config.serverVersion ?? '1.0.0',
serverDescription: config.serverDescription ?? `MCP server for ${config.serverName}`,
packageName: config.packageName ?? this.generatePackageName(config.serverName),
author: config.author ?? 'Generated by MCP Builder CLI',
license: config.license ?? 'MIT',
repository: config.repository ?? '',
debug: config.debug ?? false,
includeTests: config.includeTests ?? true,
includeDocumentation: config.includeDocumentation ?? true,
typescript: config.typescript ?? true,
serverConfig: {
timeout: config.serverConfig?.timeout ?? parseInt(process.env.API_TIMEOUT || '30000', 10),
maxResponseLength: config.serverConfig?.maxResponseLength ?? parseInt(process.env.MAX_RESPONSE_LENGTH || '50000', 10),
allowLocalhost: config.serverConfig?.allowLocalhost ?? (process.env.ALLOW_LOCALHOST === 'true'),
allowPrivateIps: config.serverConfig?.allowPrivateIps ?? false,
userAgent: config.serverConfig?.userAgent ?? `MCP-Server/${config.serverVersion ?? '1.0.0'}`,
},
};
this.log('ServerGenerator initialized', {
serverName: this.config.serverName,
packageName: this.config.packageName,
includeTests: this.config.includeTests,
includeDocumentation: this.config.includeDocumentation,
});
}
/**
* Generate server structure from API collection and tools
*/
generateServer(
apiCollection: ParsedAPICollection,
tools: GeneratedMCPTool[]
): ServerGenerationResult {
this.log(`Generating server for ${apiCollection.name}`, {
apiCount: apiCollection.apis.length,
toolCount: tools.length,
});
// Generate server structure
const server = this.createServerStructure(apiCollection, tools);
// Generate template context
const templateContext = this.createTemplateContext(server);
// Calculate statistics
const stats = {
toolsGenerated: tools.length,
apisProcessed: apiCollection.apis.length,
dependenciesAdded: server.dependencies.length,
importsGenerated: server.imports.length,
exportsGenerated: server.exports.length,
};
// Generate warnings
const warnings = this.generateWarnings(server, apiCollection, tools);
this.log('Server generation completed', stats);
return {
server,
templateContext,
stats,
warnings,
};
}
/**
* Create server structure from API collection and tools
*/
private createServerStructure(
apiCollection: ParsedAPICollection,
tools: GeneratedMCPTool[]
): GeneratedServer {
const timestamp = new Date().toISOString();
return {
name: this.config.serverName,
version: this.config.serverVersion,
description: this.config.serverDescription,
packageName: this.config.packageName,
tools,
apis: apiCollection.apis,
configuration: {
timeout: this.config.serverConfig.timeout ?? parseInt(process.env.API_TIMEOUT || '30000', 10),
maxResponseLength: this.config.serverConfig.maxResponseLength ?? parseInt(process.env.MAX_RESPONSE_LENGTH || '50000', 10),
allowLocalhost: this.config.serverConfig.allowLocalhost ?? (process.env.ALLOW_LOCALHOST === 'true'),
allowPrivateIps: this.config.serverConfig.allowPrivateIps ?? false,
userAgent: this.config.serverConfig.userAgent ?? `MCP-Server/${this.config.serverVersion}`,
},
dependencies: this.generateDependencies(),
imports: this.generateImports(tools),
exports: this.generateExports(tools),
metadata: {
generatedAt: timestamp,
generatedBy: 'MCP Builder CLI',
sourceFile: apiCollection.metadata.fileName,
version: '1.0.0',
toolCount: tools.length,
apiCount: apiCollection.apis.length,
},
};
}
/**
* Generate package dependencies
*/
private generateDependencies(): PackageDependency[] {
const dependencies: PackageDependency[] = [
{
name: '@modelcontextprotocol/sdk',
version: '^1.0.0',
type: 'dependency',
description: 'Model Context Protocol SDK',
},
{
name: 'zod',
version: '^3.22.0',
type: 'dependency',
description: 'TypeScript-first schema validation',
},
{
name: 'node-fetch',
version: '^3.3.0',
type: 'dependency',
description: 'HTTP client for Node.js',
},
];
if (this.config.typescript) {
dependencies.push(
{
name: 'typescript',
version: '^5.0.0',
type: 'devDependency',
description: 'TypeScript compiler',
},
{
name: '@types/node',
version: '^20.0.0',
type: 'devDependency',
description: 'Node.js type definitions',
}
);
}
if (this.config.includeTests) {
dependencies.push(
{
name: 'vitest',
version: '^1.0.0',
type: 'devDependency',
description: 'Testing framework',
},
{
name: '@types/jest',
version: '^29.0.0',
type: 'devDependency',
description: 'Jest type definitions',
}
);
}
return dependencies;
}
/**
* Generate import statements
*/
private generateImports(tools: GeneratedMCPTool[]): ImportStatement[] {
const imports: ImportStatement[] = [
{
module: '@modelcontextprotocol/sdk/server/index.js',
imports: ['Server'],
},
{
module: '@modelcontextprotocol/sdk/server/stdio.js',
imports: ['StdioServerTransport'],
},
{
module: '@modelcontextprotocol/sdk/types.js',
imports: ['CallToolRequestSchema', 'ListToolsRequestSchema'],
},
{
module: 'zod',
imports: ['z'],
},
{
module: 'node-fetch',
imports: ['fetch'],
isDefault: true,
},
];
// Add tool-specific imports if needed
const hasComplexValidation = tools.some(tool =>
tool.validation.bodySchema || tool.validation.headerSchema || tool.validation.querySchema
);
if (hasComplexValidation) {
imports.push({
module: './validation.js',
imports: ['validateRequest', 'ValidationError'],
});
}
return imports;
}
/**
* Generate export statements
*/
private generateExports(tools: GeneratedMCPTool[]): ExportStatement[] {
const exports: ExportStatement[] = [
{
name: 'MCPServer',
type: 'class',
isDefault: true,
},
{
name: 'ALL_TOOLS',
type: 'const',
},
{
name: 'TOOL_MAP',
type: 'const',
},
];
// Add tool handler exports
for (const tool of tools) {
exports.push({
name: tool.functionName,
type: 'function',
});
}
return exports;
}
/**
* Create template context for code generation
*/
private createTemplateContext(server: GeneratedServer): TemplateContext {
return {
server: {
name: server.name,
version: server.version,
description: server.description,
packageName: server.packageName,
author: this.config.author,
license: this.config.license,
repository: this.config.repository,
},
tools: server.tools.map(tool => ({
name: tool.name,
description: tool.description,
functionName: tool.functionName,
httpMethod: tool.httpMethod,
inputSchema: tool.inputSchema,
hasBody: tool.hasBody,
parameters: tool.parameters,
})),
apis: server.apis.map(api => ({
name: api.name,
description: api.description,
method: api.method,
url: api.url,
headers: api.headers,
body: api.body,
parameters: api.parameters,
})),
imports: server.imports.map(imp =>
imp.isDefault
? `import ${imp.imports[0]} from '${imp.module}';`
: `import { ${imp.imports.join(', ')} } from '${imp.module}';`
),
exports: server.exports.map(exp =>
exp.isDefault
? `export default ${exp.name};`
: `export { ${exp.name} };`
),
metadata: server.metadata,
configuration: server.configuration,
};
}
/**
* Generate warnings for potential issues
*/
private generateWarnings(
server: GeneratedServer,
apiCollection: ParsedAPICollection,
tools: GeneratedMCPTool[]
): string[] {
const warnings: string[] = [];
// Check for missing descriptions
const apisWithoutDescriptions = server.apis.filter(api => !api.description);
if (apisWithoutDescriptions.length > 0) {
warnings.push(`${apisWithoutDescriptions.length} APIs are missing descriptions`);
}
// Check for tools with many parameters
const complexTools = tools.filter(tool => tool.parameters.length > 8);
if (complexTools.length > 0) {
warnings.push(`${complexTools.length} tools have more than 8 parameters, consider simplifying`);
}
// Check for duplicate API endpoints
const endpoints = server.apis.map(api => `${api.method} ${api.url}`);
const duplicateEndpoints = endpoints.filter((endpoint, index) =>
endpoints.indexOf(endpoint) !== index
);
if (duplicateEndpoints.length > 0) {
warnings.push(`Duplicate API endpoints detected: ${[...new Set(duplicateEndpoints)].join(', ')}`);
}
// Check for missing base URL
if (!apiCollection.baseUrl && server.apis.some(api => !api.url.startsWith('http'))) {
warnings.push('Some APIs have relative URLs but no base URL is specified');
}
// Check for large number of tools
if (tools.length > 20) {
warnings.push(`Generated ${tools.length} tools, consider grouping related functionality`);
}
return warnings;
}
/**
* Generate package name from server name
*/
private generatePackageName(serverName: string): string {
return serverName
.toLowerCase()
.replace(/[^a-z0-9]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
}
/**
* Update generator configuration
*/
updateConfig(config: Partial<ServerGeneratorConfig>): void {
Object.assign(this.config, config);
this.log('ServerGenerator configuration updated', this.config);
}
/**
* Get current configuration
*/
getConfig(): Required<ServerGeneratorConfig> {
return { ...this.config };
}
/**
* Log messages with optional debug filtering
*/
private log(message: string, data?: any): void {
if (this.config.debug) {
const timestamp = new Date().toISOString();
if (data !== undefined) {
console.error(`[${timestamp}] ServerGenerator: ${message}`, data);
} else {
console.error(`[${timestamp}] ServerGenerator: ${message}`);
}
}
}
}