/** * MCP Tools Registry - Manages all MCP tool integrations * * Handles registration, discovery, and execution of MCP tools for the AI integration system. * Provides a unified interface for all MCP tool interactions. */ import * as path from 'path'; import { EventEmitter } from 'eventemitter3'; import * as fs from 'fs-extra'; import { MCPToolsConfig, MCPTool, Task, OperationResult } from '../types'; import { convertErrorToOperationError } from '../utils'; /** * Raised when a requested tool ID has no entry in the registry. */ export class ToolNotFoundError extends Error { constructor(toolId: string) { super(`Tool '${toolId}' not found in the MCP Tools Registry`); this.name = 'ToolNotFoundError'; } } /** * Raised when a tool handler exists but throws during execution. */ export class ToolExecutionError extends Error { constructor(toolId: string, operation: string, cause: Error) { super( `Tool '${toolId}' failed to execute operation '${operation}': ${cause.message}` ); this.name = 'ToolExecutionError'; this.cause = cause; } } export class MCPToolsRegistry extends EventEmitter { private config: MCPToolsConfig; private tools: Map = new Map(); private toolHandlers: Map = new Map(); // Complete MCP Tools Registry private readonly AVAILABLE_TOOLS = { // Wundr MCP Tools drift_detection: { category: 'governance', capabilities: ['code-quality', 'drift-monitoring', 'baseline-creation'], handler: 'DriftDetectionHandler', description: 'Monitor code quality drift and create baselines', }, pattern_standardize: { category: 'standardization', capabilities: [ 'pattern-fixing', 'code-standardization', 'auto-remediation', ], handler: 'PatternStandardizeHandler', description: 'Automatically fix and standardize code patterns', }, monorepo_manage: { category: 'monorepo', capabilities: [ 'monorepo-management', 'package-creation', 'dependency-analysis', ], handler: 'MonorepoManageHandler', description: 'Manage monorepo structure and dependencies', }, governance_report: { category: 'governance', capabilities: [ 'report-generation', 'compliance-tracking', 'metrics-aggregation', ], handler: 'GovernanceReportHandler', description: 'Generate comprehensive governance reports', }, dependency_analyze: { category: 'analysis', capabilities: [ 'dependency-analysis', 'circular-detection', 'optimization-suggestions', ], handler: 'DependencyAnalyzeHandler', description: 'Analyze project dependencies and detect issues', }, test_baseline: { category: 'testing', capabilities: [ 'test-coverage', 'baseline-management', 'regression-detection', ], handler: 'TestBaselineHandler', description: 'Manage test coverage baselines and comparisons', }, claude_config: { category: 'config', capabilities: [ 'configuration-management', 'hook-setup', 'convention-creation', ], handler: 'ClaudeConfigHandler', description: 'Configure Claude Code integration settings', }, // Ruflo MCP Tools swarm_init: { category: 'coordination', capabilities: [ 'swarm-initialization', 'topology-setup', 'agent-spawning', ], handler: 'SwarmInitHandler', description: 'Initialize swarm coordination systems', }, agent_spawn: { category: 'coordination', capabilities: [ 'agent-creation', 'capability-assignment', 'resource-allocation', ], handler: 'AgentSpawnHandler', description: 'Spawn and configure AI agents', }, task_orchestrate: { category: 'coordination', capabilities: [ 'task-distribution', 'workload-balancing', 'priority-management', ], handler: 'TaskOrchestrateHandler', description: 'Orchestrate task distribution across agents', }, swarm_status: { category: 'monitoring', capabilities: [ 'status-monitoring', 'health-checking', 'performance-tracking', ], handler: 'SwarmStatusHandler', description: 'Monitor swarm health and status', }, agent_list: { category: 'monitoring', capabilities: [ 'agent-discovery', 'capability-listing', 'availability-checking', ], handler: 'AgentListHandler', description: 'List and discover available agents', }, agent_metrics: { category: 'monitoring', capabilities: [ 'metrics-collection', 'performance-analysis', 'trend-tracking', ], handler: 'AgentMetricsHandler', description: 'Collect and analyze agent performance metrics', }, task_status: { category: 'monitoring', capabilities: [ 'task-tracking', 'progress-monitoring', 'completion-detection', ], handler: 'TaskStatusHandler', description: 'Monitor task execution status', }, task_results: { category: 'monitoring', capabilities: [ 'result-aggregation', 'outcome-analysis', 'success-tracking', ], handler: 'TaskResultsHandler', description: 'Aggregate and analyze task results', }, memory_usage: { category: 'memory', capabilities: [ 'memory-monitoring', 'usage-optimization', 'cleanup-automation', ], handler: 'MemoryUsageHandler', description: 'Monitor and optimize memory usage', }, neural_status: { category: 'neural', capabilities: ['neural-monitoring', 'model-status', 'training-progress'], handler: 'NeuralStatusHandler', description: 'Monitor neural network training and status', }, neural_train: { category: 'neural', capabilities: [ 'model-training', 'pattern-learning', 'performance-optimization', ], handler: 'NeuralTrainHandler', description: 'Train neural models on execution patterns', }, neural_patterns: { category: 'neural', capabilities: [ 'pattern-recognition', 'behavior-analysis', 'prediction-generation', ], handler: 'NeuralPatternsHandler', description: 'Analyze and recognize behavioral patterns', }, github_swarm: { category: 'github', capabilities: [ 'github-integration', 'swarm-coordination', 'repository-management', ], handler: 'GitHubSwarmHandler', description: 'Coordinate GitHub operations with swarm intelligence', }, repo_analyze: { category: 'github', capabilities: [ 'repository-analysis', 'code-quality', 'structure-assessment', ], handler: 'RepoAnalyzeHandler', description: 'Analyze repository structure and quality', }, pr_enhance: { category: 'github', capabilities: [ 'pull-request-enhancement', 'automated-review', 'quality-improvement', ], handler: 'PREnhanceHandler', description: 'Enhance pull requests with automated analysis', }, issue_triage: { category: 'github', capabilities: [ 'issue-classification', 'priority-assignment', 'automated-triage', ], handler: 'IssueTriageHandler', description: 'Automatically triage and classify issues', }, code_review: { category: 'github', capabilities: [ 'automated-review', 'quality-assessment', 'feedback-generation', ], handler: 'CodeReviewHandler', description: 'Perform automated code reviews', }, benchmark_run: { category: 'system', capabilities: [ 'performance-benchmarking', 'load-testing', 'capacity-planning', ], handler: 'BenchmarkRunHandler', description: 'Run performance benchmarks and load tests', }, features_detect: { category: 'system', capabilities: [ 'feature-detection', 'capability-discovery', 'system-profiling', ], handler: 'FeaturesDetectHandler', description: 'Detect available system features and capabilities', }, swarm_monitor: { category: 'system', capabilities: [ 'swarm-monitoring', 'resource-tracking', 'health-assessment', ], handler: 'SwarmMonitorHandler', description: 'Comprehensive swarm monitoring and assessment', }, }; constructor(config: MCPToolsConfig) { super(); this.config = config; } async initialize(): Promise { try { // Load tool configurations await this.loadToolConfigurations(); // Initialize tool handlers await this.initializeToolHandlers(); // Discover and register tools await this.discoverTools(); // Setup auto-discovery if enabled if (this.config.autoDiscovery) { await this.setupAutoDiscovery(); } return { success: true, message: `MCP Tools Registry initialized with ${this.tools.size} tools`, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { success: false, message: `MCP Tools Registry initialization failed: ${errorMessage}`, error: convertErrorToOperationError(error, 'MCP_REGISTRY_ERROR'), }; } } private async loadToolConfigurations(): Promise { const configPath = path.join(this.config.registryPath, 'tools.json'); try { if (await fs.pathExists(configPath)) { const toolConfigs = await fs.readJson(configPath); this.mergeToolConfigurations(toolConfigs); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; console.warn(`Failed to load tool configurations: ${errorMessage}`); } } private mergeToolConfigurations(configs: any): void { for (const [toolId, config] of Object.entries(configs)) { const availableTools = this.AVAILABLE_TOOLS as Record; if (availableTools[toolId]) { Object.assign(availableTools[toolId], config); } } } private async initializeToolHandlers(): Promise { const handlersPath = path.join(__dirname, '..', 'handlers'); for (const [toolId, toolConfig] of Object.entries(this.AVAILABLE_TOOLS)) { try { const handlerPath = path.join( handlersPath, toolConfig.category, `${toolConfig.handler}.ts` ); if (await fs.pathExists(handlerPath.replace('.ts', '.js'))) { const HandlerClass = require(handlerPath.replace('.ts', '')).default; const handler = new HandlerClass(toolConfig); this.toolHandlers.set(toolId, handler); } else { // Create default handler this.toolHandlers.set( toolId, new DefaultMCPHandler(toolId, toolConfig) ); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; console.warn( `Failed to initialize handler for ${toolId}: ${errorMessage}` ); this.toolHandlers.set( toolId, new DefaultMCPHandler(toolId, toolConfig) ); } } } private async discoverTools(): Promise { // Register enabled tools for (const toolId of this.config.enabledTools) { const availableTools = this.AVAILABLE_TOOLS as Record; if (availableTools[toolId]) { await this.registerTool(toolId, availableTools[toolId]); } } // Auto-discover if enabled if (this.config.autoDiscovery) { for (const [toolId, toolConfig] of Object.entries(this.AVAILABLE_TOOLS)) { if (!this.tools.has(toolId)) { await this.registerTool(toolId, toolConfig); } } } } private async registerTool(toolId: string, toolConfig: any): Promise { const tool: MCPTool = { id: toolId, name: toolConfig.description || toolId, category: toolConfig.category, capabilities: toolConfig.capabilities, status: 'available', metadata: { version: toolConfig.version || '1.0.0', dependencies: toolConfig.dependencies || [], configuration: { ...toolConfig, registeredAt: new Date().toISOString(), }, permissions: toolConfig.permissions || [], handler: toolConfig.handler, }, }; this.tools.set(toolId, tool); this.emit('tool-registered', tool); } private async setupAutoDiscovery(): Promise { // Setup periodic discovery of new tools setInterval(async () => { await this.discoverNewTools(); }, 60000); // Check every minute } private async discoverNewTools(): Promise { // Check for new MCP servers or tool configurations try { const mcpConfigPath = path.join( process.env['HOME'] || '', '.claude', 'mcp.json' ); if (await fs.pathExists(mcpConfigPath)) { const mcpConfig = await fs.readJson(mcpConfigPath); for (const serverName of Object.keys(mcpConfig.servers || {})) { if (!this.tools.has(serverName)) { await this.discoverServerTools( serverName, mcpConfig.servers[serverName] ); } } } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; console.warn(`Tool discovery failed: ${errorMessage}`); } } private async discoverServerTools( serverName: string, serverConfig: any ): Promise { // Discover tools provided by MCP server try { // This would interface with the actual MCP server to discover tools // For now, we'll simulate tool discovery const discoveredTool: MCPTool = { id: serverName, name: serverConfig.name || serverName, category: 'external', capabilities: ['external-integration'], status: 'available', metadata: { version: serverConfig.version || '1.0.0', dependencies: serverConfig.dependencies || [], configuration: { ...serverConfig, discoveredAt: new Date().toISOString(), }, permissions: serverConfig.permissions || [], server: serverName, }, }; this.tools.set(serverName, discoveredTool); this.emit('tool-discovered', discoveredTool); } catch (error) { console.warn( `Failed to discover tools for server ${serverName}: ${error.message}` ); } } /** * Execute MCP tool operation. * * Throws `ToolNotFoundError` when the tool ID is not registered so callers * can distinguish "tool does not exist" from ordinary execution failures. * All other errors are caught, wrapped, and returned as a failed * `OperationResult` to keep the public API non-throwing for runtime errors. */ async executeTool( toolId: string, operation: string, params: any ): Promise { const tool = this.tools.get(toolId); if (!tool) { // Re-throw as a typed error so callers can discriminate on ToolNotFoundError. throw new ToolNotFoundError(toolId); } if (tool.status !== 'available') { return { success: false, message: `Tool ${toolId} is not available (status: ${tool.status})`, }; } // Set tool status to busy tool.status = 'busy'; this.emit('tool-busy', tool); const handler = this.toolHandlers.get(toolId); if (!handler) { tool.status = 'error'; this.emit( 'tool-error', tool, new Error(`No handler found for tool ${toolId}`) ); return { success: false, message: `No handler found for tool ${toolId}`, }; } try { // Execute tool operation with timeout const timeoutPromise = new Promise((_, reject) => setTimeout( () => reject(new Error('Tool execution timeout')), this.config.timeout ) ); const result = await Promise.race([ handler.execute(operation, params), timeoutPromise, ]); // Reset tool status tool.status = 'available'; this.emit('tool-available', tool); return { success: true, message: 'Tool executed successfully', data: result, }; } catch (error) { tool.status = 'error'; this.emit('tool-error', tool, error); const cause = error instanceof Error ? error : new Error(String(error)); const wrapped = new ToolExecutionError(toolId, operation, cause); return { success: false, message: wrapped.message, error: convertErrorToOperationError( wrapped, 'MCP_TOOL_EXECUTION_ERROR' ), }; } } /** * Select optimal tools for a task */ async selectToolsForTask(task: Task): Promise { const selectedTools: MCPTool[] = []; // Match tools based on required capabilities for (const tool of this.tools.values()) { if (tool.status === 'available') { const hasRequiredCapabilities = task.requiredCapabilities.some( capability => tool.capabilities.includes(capability) ); if (hasRequiredCapabilities) { selectedTools.push(tool); } } } // Sort by relevance and performance selectedTools.sort((a, b) => { const aScore = this.calculateToolScore(a, task); const bScore = this.calculateToolScore(b, task); return bScore - aScore; }); return selectedTools.slice(0, 5); // Return top 5 tools } private calculateToolScore(tool: MCPTool, task: Task): number { let score = 0; // Score based on capability match const matchingCapabilities = tool.capabilities.filter(cap => task.requiredCapabilities.includes(cap) ).length; score += matchingCapabilities * 10; // Score based on task type alignment if (this.isToolAlignedWithTask(tool, task)) { score += 5; } // Penalty for busy tools if (tool.status === 'busy') { score -= 3; } return score; } private isToolAlignedWithTask(tool: MCPTool, task: Task): boolean { const taskTypeMapping = { coding: ['standardization', 'analysis'], review: ['governance', 'github'], testing: ['testing'], analysis: ['analysis', 'monitoring'], documentation: ['config', 'governance'], deployment: ['system', 'monorepo'], optimization: ['neural', 'performance'], }; const relevantCategories = taskTypeMapping[task.type] || []; return relevantCategories.includes(tool.category); } /** * Get available tools by category */ getToolsByCategory(category: string): MCPTool[] { return Array.from(this.tools.values()).filter( tool => tool.category === category ); } /** * Get tools by capabilities */ getToolsByCapabilities(capabilities: string[]): MCPTool[] { return Array.from(this.tools.values()).filter(tool => capabilities.some(cap => tool.capabilities.includes(cap)) ); } /** * Get comprehensive metrics */ async getMetrics(): Promise { const toolsByCategory: Record = {}; const toolsByStatus: Record = {}; for (const tool of this.tools.values()) { toolsByCategory[tool.category] = (toolsByCategory[tool.category] || 0) + 1; toolsByStatus[tool.status] = (toolsByStatus[tool.status] || 0) + 1; } return { totalTools: this.tools.size, availableTools: Object.keys(this.AVAILABLE_TOOLS).length, enabledTools: this.config.enabledTools.length, toolsByCategory, toolsByStatus, autoDiscovery: this.config.autoDiscovery, cacheEnabled: this.config.cacheResults, }; } /** * Register a callable handler function for a specific tool ID. * * The handler receives `(operation: string, params: any)` and must return a * promise resolving to the operation result. Calling this after initialization * will override the default handler that was assigned during * `initializeToolHandlers()`. * * @param toolId - The tool identifier as declared in AVAILABLE_TOOLS. * @param handler - An object or function with an `execute` method, or any * object that conforms to `{ execute(op, params): Promise }`. */ registerToolHandler( toolId: string, handler: { execute(operation: string, params: any): Promise } ): void { if (!this.tools.has(toolId)) { throw new ToolNotFoundError(toolId); } this.toolHandlers.set(toolId, handler); } async shutdown(): Promise { try { // Shutdown all tool handlers for (const handler of this.toolHandlers.values()) { if (handler.shutdown) { await handler.shutdown(); } } this.tools.clear(); this.toolHandlers.clear(); return { success: true, message: 'MCP Tools Registry shutdown completed', }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { success: false, message: `Shutdown failed: ${errorMessage}`, error: convertErrorToOperationError(error, 'MCP_REGISTRY_ERROR'), }; } } } /** * Default MCP Handler for tools without specific compiled handlers on disk. * * Real operation callables can be bound at runtime via `registerOperation`. * If a registered callable exists for the requested operation it is invoked * directly. If no callable has been registered the handler throws so the * caller receives a clear error instead of a silent simulated result. */ class DefaultMCPHandler { private toolId: string; private _config: any; private operationHandlers: Map Promise> = new Map(); constructor(toolId: string, config: any) { this.toolId = toolId; this._config = config; } /** * Bind a callable to a specific operation name. * The callable receives the raw `params` object and must return a promise. */ registerOperation( operation: string, handler: (params: any) => Promise ): void { this.operationHandlers.set(operation, handler); } async execute(operation: string, params: any): Promise { const callable = this.operationHandlers.get(operation); if (!callable) { throw new Error( `No handler registered for operation '${operation}' on tool '${this.toolId}'. ` + `Register a callable via DefaultMCPHandler.registerOperation() or provide a ` + `compiled handler module at the expected handlers path.` ); } return callable(params); } async shutdown(): Promise { this.operationHandlers.clear(); } }