Skip to main content
Glama

Context Pods

by conorluddy
create-mcp.ts12.2 kB
/** * Create MCP server tool */ import { join } from 'path'; import { promises as fs } from 'fs'; import { TemplateSelector, DefaultTemplateEngine, logger, type TemplateMetadata, TemplateLanguage, type TemplateProcessingResult, } from '@context-pods/core'; import { BaseTool, type ToolResult } from './base-tool.js'; import { getRegistryOperations } from '../registry/index.js'; import { CONFIG } from '../config/index.js'; /** * Arguments for create-mcp tool */ interface CreateMCPArgs extends Record<string, unknown> { name: string; template?: string; outputPath?: string; description?: string; language?: string; variables?: Record<string, unknown>; generateMcpConfig?: boolean; configName?: string; configPath?: string; command?: string; args?: string[]; env?: Record<string, string>; } /** * Template selection result with proper typing */ interface TypedTemplateSelectionResult { template: { name: string; language: string; tags?: string[]; optimization: { turboRepo: boolean; hotReload: boolean; sharedDependencies: boolean; buildCaching: boolean; }; variables: Record< string, { type: string; required: boolean; default?: unknown; } >; }; templatePath: string; reasons: string[]; score: number; } /** * Create MCP server tool implementation */ export class CreateMCPTool extends BaseTool { private templateSelector: TemplateSelector; private templateEngine: DefaultTemplateEngine; constructor() { super('create-mcp'); this.templateSelector = new TemplateSelector(CONFIG.templatesPath); this.templateEngine = new DefaultTemplateEngine(); } /** * Validate create-mcp arguments */ protected async validateArguments(args: unknown): Promise<string | null> { const typedArgs = args as CreateMCPArgs; // Validate required arguments let error = this.validateStringArgument(typedArgs, 'name', true, 1, 50); if (error) return error; // Validate optional arguments error = this.validateStringArgument(typedArgs, 'template', false); if (error) return error; error = this.validateStringArgument(typedArgs, 'outputPath', false); if (error) return error; error = this.validateStringArgument(typedArgs, 'description', false, 0, 500); if (error) return error; error = this.validateStringArgument(typedArgs, 'language', false); if (error) return error; if (typedArgs.variables !== undefined) { error = this.validateArgument(typedArgs, 'variables', 'object', false); if (error) return error; } // Validate MCP config arguments error = this.validateArgument(typedArgs, 'generateMcpConfig', 'boolean', false); if (error) return error; error = this.validateStringArgument(typedArgs, 'configName', false); if (error) return error; error = this.validateStringArgument(typedArgs, 'configPath', false); if (error) return error; error = this.validateStringArgument(typedArgs, 'command', false); if (error) return error; if (typedArgs.args !== undefined) { error = this.validateArgument(typedArgs, 'args', 'object', false); if (error) return error; } if (typedArgs.env !== undefined) { error = this.validateArgument(typedArgs, 'env', 'object', false); if (error) return error; } // Validate name format (alphanumeric, hyphens, underscores) const namePattern = /^[a-zA-Z][a-zA-Z0-9_-]*$/; if (!namePattern.test(typedArgs.name)) { return 'Server name must start with a letter and contain only letters, numbers, hyphens, and underscores'; } // Check if name is available const registry = await getRegistryOperations(); const isAvailable = await registry.isNameAvailable(typedArgs.name); if (!isAvailable) { return `Server name '${typedArgs.name}' is already taken`; } return null; } /** * Execute create-mcp tool */ protected async execute(args: unknown): Promise<ToolResult> { const typedArgs = args as CreateMCPArgs; const warnings: string[] = []; try { // Step 1: Select template const template = await this.selectTemplate(typedArgs); if (!template) { return { success: false, error: 'No suitable template found', }; } logger.info(`Selected template: ${template.template.name}`, { reasons: template.reasons, score: template.score, }); // Step 2: Prepare output path const outputPath = this.prepareOutputPath(typedArgs); // Step 3: Check if output directory already exists try { await fs.access(outputPath); return { success: false, error: `Output directory already exists: ${outputPath}`, }; } catch { // Directory doesn't exist, which is what we want } // Step 4: Prepare template variables const variables = this.prepareTemplateVariables( typedArgs, template.template as TemplateMetadata, ); // Step 5: Validate template variables const validationResult = await this.templateEngine.validateVariables( template.template as TemplateMetadata, variables, ); if (!validationResult.isValid) { const errorDetails = validationResult.errors .map((err) => `• ${err.field}: ${err.message}`) .join('\n'); return { success: false, error: `Template variable validation failed:\n${errorDetails}`, }; } // Step 6: Register server in registry const registry = await getRegistryOperations(); const serverMetadata = await registry.registerServer({ name: typedArgs.name, template: template.template.name, path: outputPath, templateVariables: variables, description: typedArgs.description, tags: template.template.tags, }); try { // Step 7: Mark as building await registry.markServerBuilding(serverMetadata.id); // Step 8: Process template const shouldGenerateConfig = typedArgs.generateMcpConfig ?? (template.template as TemplateMetadata).mcpConfig?.generateByDefault ?? false; const result = await this.templateEngine.process(template.template as TemplateMetadata, { variables, outputPath, templatePath: template.templatePath, optimization: { turboRepo: template.template.optimization.turboRepo, hotReload: template.template.optimization.hotReload, sharedDependencies: template.template.optimization.sharedDependencies, buildCaching: template.template.optimization.buildCaching, }, mcpConfig: shouldGenerateConfig ? { generateConfig: true, configName: typedArgs.configName, configPath: typedArgs.configPath, command: typedArgs.command, args: typedArgs.args, env: typedArgs.env, } : undefined, }); if (!result.success) { await registry.markServerError( serverMetadata.id, result.errors?.join(', ') || 'Template processing failed', ); return { success: false, error: result.errors?.join(', ') || 'Template processing failed', }; } // Step 9: Mark as ready await registry.markServerReady(serverMetadata.id, result.buildCommand, result.devCommand); // Add warnings from template processing if (result.warnings) { warnings.push(...result.warnings); } // Step 10: Create success message const successMessage = this.createSuccessMessage( typedArgs.name, template.template.name, outputPath, result, ); return { success: true, data: successMessage, warnings, }; } catch (error) { // Mark server as error if processing failed await registry.markServerError( serverMetadata.id, error instanceof Error ? error.message : String(error), ); throw error; } } catch (error) { logger.error('Error creating MCP server:', error); return { success: false, error: error instanceof Error ? error.message : String(error), }; } } /** * Select appropriate template */ private async selectTemplate(args: CreateMCPArgs): Promise<TypedTemplateSelectionResult | null> { if (args.template) { // Specific template requested const templates = await this.templateSelector.getAvailableTemplates(); const template = templates.find((t) => t.template.name === args.template); if (!template) { throw new Error(`Template '${args.template}' not found`); } return template; } // Auto-select template based on language preference if (args.language) { const languageMap: Record<string, TemplateLanguage> = { typescript: TemplateLanguage.TYPESCRIPT, javascript: TemplateLanguage.NODEJS, python: TemplateLanguage.PYTHON, rust: TemplateLanguage.RUST, shell: TemplateLanguage.SHELL, }; const templateLanguage = languageMap[args.language.toLowerCase()]; if (templateLanguage) { return await this.templateSelector.getRecommendedTemplate(templateLanguage); } } // Default to TypeScript advanced template const templates = await this.templateSelector.getAvailableTemplates(); const defaultTemplate = templates.find( (t) => t.template.name.includes('typescript') && t.template.name.includes('advanced'), ); return defaultTemplate || templates[0] || null; } /** * Prepare output path */ private prepareOutputPath(args: CreateMCPArgs): string { if (args.outputPath) { return args.outputPath; } // Use configured output path based on mode return join(CONFIG.generatedPackagesPath, args.name); } /** * Prepare template variables */ private prepareTemplateVariables( args: CreateMCPArgs, template: TemplateMetadata, ): Record<string, unknown> { const variables: Record<string, unknown> = { serverName: args.name, serverDescription: args.description || `MCP server: ${args.name}`, ...args.variables, }; // Add default values for common template variables if (!variables.packageName) { variables.packageName = args.name; } if (!variables.authorName) { variables.authorName = 'Context-Pods'; } // Add template-specific defaults for (const [varName, varDef] of Object.entries(template.variables)) { if (!variables[varName] && varDef.default !== undefined) { variables[varName] = varDef.default; } } return variables; } /** * Create success message */ private createSuccessMessage( name: string, templateName: string, outputPath: string, result: TemplateProcessingResult, ): string { let message = `🎉 Successfully created MCP server: ${name}\n\n`; message += `📋 Details:\n`; message += `- Template: ${templateName}\n`; message += `- Output: ${outputPath}\n`; message += `- Files generated: ${result.generatedFiles?.length || 0}\n`; if (result.buildCommand) { message += `- Build command: ${result.buildCommand}\n`; } if (result.devCommand) { message += `- Dev command: ${result.devCommand}\n`; } if (result.mcpConfigPath) { message += `- MCP config: ${result.mcpConfigPath}\n`; } message += `\n🚀 Next steps:\n`; message += `1. Navigate to: cd ${outputPath}\n`; if (result.buildCommand) { message += `2. Build: ${result.buildCommand}\n`; } if (result.devCommand) { message += `3. Start development: ${result.devCommand}\n`; } return message; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/conorluddy/ContextPods'

If you have feedback or need assistance with the MCP directory API, please join our Discord server