MCP GitHub Issue Server

/** * Path-based task management tools handler */ import { TaskManager } from '../task/manager/task-manager.js'; import { TemplateManager } from '../template/manager.js'; import { Logger } from '../logging/index.js'; import { ErrorCodes, createError } from '../errors/index.js'; import { Tool, ToolResponse } from './types.js'; import { ToolDefinitions } from './definitions/tool-definitions.js'; import { NoteManager } from '../notes/note-manager.js'; export class ToolHandler { private readonly logger: Logger; private readonly tools: Map<string, Tool> = new Map(); private readonly toolHandlers: Map< string, (args: Record<string, unknown>) => Promise<ToolResponse> > = new Map(); private readonly toolDefinitions: ToolDefinitions; constructor( private readonly taskManager: TaskManager, templateManager: TemplateManager, private readonly noteManager: NoteManager ) { this.logger = Logger.getInstance().child({ component: 'ToolHandler', context: { operation: 'initialization', }, }); this.toolDefinitions = new ToolDefinitions(taskManager, templateManager); this.registerTools(); this.logger.info('Tool registration completed', { count: this.tools.size, tools: Array.from(this.tools.keys()), context: { operation: 'registerTools', timestamp: Date.now(), }, }); } private registerTools(): void { const tools = this.toolDefinitions.getTools(); for (const tool of tools) { const { handler, ...toolDef } = tool; this.tools.set(tool.name, toolDef); this.toolHandlers.set(tool.name, handler); this.logger.debug('Tool registered', { name: tool.name, schema: toolDef.inputSchema, context: { operation: 'registerTool', timestamp: Date.now(), }, }); } } async listTools(): Promise<{ tools: Tool[] }> { // Ensure all tools are registered if (this.tools.size === 0) { this.logger.warn('No tools registered, attempting registration', { context: { operation: 'listTools', timestamp: Date.now(), }, }); this.registerTools(); // Wait for registration to complete await new Promise(resolve => setTimeout(resolve, 100)); } const tools = Array.from(this.tools.values()); this.logger.info('Tools listed', { count: tools.length, tools: tools.map(t => ({ name: t.name, schema: t.inputSchema, })), context: { operation: 'listTools', timestamp: Date.now(), }, }); return { tools }; } async handleToolCall(request: { params: { name: string; arguments?: Record<string, unknown> }; }): Promise<{ _meta?: Record<string, unknown>; content: Array<{ type: string; text: string }>; }> { const { name, arguments: args = {} } = request.params; const tool = this.tools.get(name); if (!tool) { throw createError(ErrorCodes.INVALID_INPUT, 'Unknown tool', 'handleToolCall', undefined, { tool: name, }); } const handler = this.toolHandlers.get(name); if (!handler) { throw createError( ErrorCodes.INVALID_INPUT, 'Tool handler not found', 'handleToolCall', undefined, { tool: name } ); } try { // Validate dependencies are at root level if ( (name === 'create_task' || name === 'update_task') && (args as any).metadata?.dependencies ) { throw createError( ErrorCodes.INVALID_INPUT, 'Dependencies must be specified at root level, not in metadata', 'handleToolCall' ); } this.logger.debug('Executing tool', { name, args }); const result = await handler(args); this.logger.debug('Tool execution completed', { name }); // Get applicable user notes const notes = this.noteManager.getNotesForTool(name); const formattedNotes = this.noteManager.formatNotes(notes); // Add notes to response if present const response = { _meta: {}, ...result, }; if (formattedNotes) { response.content.push({ type: 'text', text: '\n\n' + formattedNotes, }); } return response; } catch (error) { this.logger.error('Tool execution failed', { tool: name, error, }); throw error; } } async getStorageMetrics(): Promise<any> { return await this.taskManager.getStorage().getMetrics(); } }