/** * Request router for MCP */ import { MCPRequest } from "../utils/types.ts"; import { ILogger } from "../core/logger.ts"; import { MCPMethodNotFoundError } from "../utils/errors.ts"; import { ToolRegistry } from "./tools.ts"; /** * Request router implementation */ export class RequestRouter { private totalRequests = 0; private successfulRequests = 0; private failedRequests = 0; constructor( private toolRegistry: ToolRegistry, private logger: ILogger, ) {} /** * Routes a request to the appropriate handler */ async route(request: MCPRequest): Promise { this.totalRequests++; try { // Parse method to determine handler const { method, params } = request; // Handle built-in methods if (method.startsWith('rpc.')) { return await this.handleRPCMethod(method, params); } // Handle tool invocations (both formats) if (method.startsWith('tools.') || method.startsWith('tools/')) { return await this.handleToolMethod(method, params); } // Try to execute as a tool directly const tool = this.toolRegistry.getTool(method); if (tool) { const result = await this.toolRegistry.executeTool(method, params); this.successfulRequests++; return result; } // Method not found throw new MCPMethodNotFoundError(method); } catch (error) { this.failedRequests++; throw error; } } /** * Gets router metrics */ getMetrics(): { totalRequests: number; successfulRequests: number; failedRequests: number; } { return { totalRequests: this.totalRequests, successfulRequests: this.successfulRequests, failedRequests: this.failedRequests, }; } /** * Handles built-in RPC methods */ private async handleRPCMethod(method: string, params: unknown): Promise { switch (method) { case 'rpc.discover': return this.discoverMethods(); case 'rpc.ping': return { pong: true }; case 'rpc.describe': return this.describeMethod(params); default: throw new MCPMethodNotFoundError(method); } } /** * Handles tool-related methods */ private async handleToolMethod(method: string, params: unknown): Promise { switch (method) { case 'tools.list': case 'tools/list': return this.toolRegistry.listTools(); case 'tools.invoke': case 'tools/call': return await this.invokeTool(params); case 'tools.describe': case 'tools/describe': return this.describeTool(params); default: throw new MCPMethodNotFoundError(method); } } /** * Discovers all available methods */ private discoverMethods(): Record { const methods: Record = { 'rpc.discover': 'Discover all available methods', 'rpc.ping': 'Ping the server', 'rpc.describe': 'Describe a specific method', 'tools.list': 'List all available tools', 'tools.invoke': 'Invoke a specific tool', 'tools.describe': 'Describe a specific tool', }; // Add all registered tools for (const tool of this.toolRegistry.listTools()) { methods[tool.name] = tool.description; } return methods; } /** * Describes a specific method */ private describeMethod(params: unknown): unknown { if (!params || typeof params !== 'object' || !('method' in params)) { throw new Error('Invalid params: method required'); } const { method } = params as { method: string }; // Check if it's a tool const tool = this.toolRegistry.getTool(method); if (tool) { return { name: tool.name, description: tool.description, inputSchema: tool.inputSchema, }; } // Check built-in methods const builtInMethods: Record = { 'rpc.discover': { description: 'Discover all available methods', inputSchema: { type: 'object', properties: {} }, }, 'rpc.ping': { description: 'Ping the server', inputSchema: { type: 'object', properties: {} }, }, 'rpc.describe': { description: 'Describe a specific method', inputSchema: { type: 'object', properties: { method: { type: 'string' }, }, required: ['method'], }, }, 'tools.list': { description: 'List all available tools', inputSchema: { type: 'object', properties: {} }, }, 'tools.invoke': { description: 'Invoke a specific tool', inputSchema: { type: 'object', properties: { tool: { type: 'string' }, input: { type: 'object' }, }, required: ['tool', 'input'], }, }, 'tools.describe': { description: 'Describe a specific tool', inputSchema: { type: 'object', properties: { tool: { type: 'string' }, }, required: ['tool'], }, }, }; if (method in builtInMethods) { return builtInMethods[method]; } throw new MCPMethodNotFoundError(method); } /** * Invokes a tool */ private async invokeTool(params: unknown): Promise { if (!params || typeof params !== 'object') { throw new Error('Invalid params: parameters required'); } // Handle MCP standard format: tools/call with name and arguments if ('name' in params && 'arguments' in params) { const { name, arguments: args } = params as { name: string; arguments?: unknown }; return await this.toolRegistry.executeTool(name, args || {}); } // Handle alternative format: tools.invoke with tool and input if ('tool' in params) { const { tool, input } = params as { tool: string; input?: unknown }; return await this.toolRegistry.executeTool(tool, input || {}); } throw new Error('Invalid params: either name+arguments or tool+input required'); } /** * Describes a specific tool */ private describeTool(params: unknown): unknown { if (!params || typeof params !== 'object' || !('tool' in params)) { throw new Error('Invalid params: tool required'); } const { tool: toolName } = params as { tool: string }; const tool = this.toolRegistry.getTool(toolName); if (!tool) { throw new Error(`Tool not found: ${toolName}`); } return { name: tool.name, description: tool.description, inputSchema: tool.inputSchema, }; } }