UNPKG

@jerfowler/agent-comm-mcp-server

Version:

MCP server for AI agent task communication and delegation with diagnostic lifecycle visibility

965 lines 45.7 kB
#!/usr/bin/env node /** * Agent Communication MCP Server * A Model Context Protocol server for agent task communication and delegation */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { getConfig, validateConfig, getServerInfo, validateEnvironment } from './config.js'; import { AgentCommError } from './types.js'; import * as fs from './utils/fs-extra-safe.js'; // Import core components import { ConnectionManager } from './core/ConnectionManager.js'; import { EventLogger } from './logging/EventLogger.js'; import { ErrorLogger } from './logging/ErrorLogger.js'; import { TaskContextManager } from './core/TaskContextManager.js'; import { ResourceManager } from './resources/ResourceManager.js'; import { PromptManager } from './prompts/PromptManager.js'; import { ResponseEnhancer } from './core/ResponseEnhancer.js'; import { ComplianceTracker } from './core/ComplianceTracker.js'; import { DelegationTracker } from './core/DelegationTracker.js'; // Import tools import { checkTasks } from './tools/check-tasks.js'; import { readTask } from './tools/read-task.js'; import { writeTask } from './tools/write-task.js'; import { createTaskTool } from './tools/create-task.js'; import { listAgents } from './tools/list-agents.js'; import { archiveTasksTool } from './tools/archive-tasks.js'; import { restoreTasksTool } from './tools/restore-tasks.js'; // Import diagnostic tools (v0.4.0) import { getFullLifecycle } from './tools/get-full-lifecycle.js'; import { trackTaskProgress } from './tools/track-task-progress.js'; // Import context-based tools import { getTaskContext } from './tools/get-task-context.js'; import { submitPlan } from './tools/submit-plan.js'; import { reportProgress } from './tools/report-progress.js'; import { markComplete } from './tools/mark-complete.js'; import { archiveCompletedTasks } from './tools/archive-completed-tasks.js'; // Import best practice tools import { getServerInfo as getServerInfoTool, initializeServerStartTime } from './tools/get-server-info.js'; import { ping } from './tools/ping.js'; // Import integration tools import { syncTodoCheckboxes } from './tools/sync-todo-checkboxes.js'; import { protocolConfig } from './tools/protocol-config.js'; /** * Create MCP server instance (exported for testing) */ export function createMCPServer() { // Validate environment first validateEnvironment(); const baseConfig = getConfig(); const serverInfo = getServerInfo(); // Ensure required directories exist fs.ensureDirSync(baseConfig.commDir); if (baseConfig.enableArchiving) { fs.ensureDirSync(baseConfig.archiveDir); } fs.ensureDirSync(baseConfig.logDir); // Initialize core components - extend BaseServerConfig to ServerConfig const connectionManager = new ConnectionManager(); const eventLogger = new EventLogger(baseConfig.logDir); const errorLogger = new ErrorLogger(baseConfig.logDir); // Create initial config for components that need it const initialConfig = { ...baseConfig, connectionManager, eventLogger, errorLogger }; // Initialize Smart Response System components const promptManager = new PromptManager(initialConfig); const complianceTracker = new ComplianceTracker(initialConfig); const delegationTracker = new DelegationTracker(initialConfig); const responseEnhancer = new ResponseEnhancer(initialConfig); // Create complete config with all components const config = { ...baseConfig, connectionManager, eventLogger, errorLogger, promptManager, complianceTracker, delegationTracker, responseEnhancer }; // Validate the complete config validateConfig(config); // Initialize TaskContextManager and ResourceManager const taskContextManager = new TaskContextManager(config); const resourceManager = new ResourceManager({ taskContextManager, eventLogger, connectionManager }); // Initialize server start time for uptime tracking initializeServerStartTime(); const server = new Server({ name: serverInfo.name, version: serverInfo.version }, { capabilities: { tools: {}, resources: {}, prompts: {} } }); // Configure server with handlers setupServerHandlers(server, config, resourceManager); return server; } /** * Helper function to enhance tool responses with Smart Response System */ async function enhanceToolResponse(toolName, toolResponse, agent, config) { // If ResponseEnhancer is not configured, return original response if (!config.responseEnhancer) { return toolResponse; } try { // Create enhancement context with optional properties const context = { toolName, toolResponse, agent }; // Add optional components if available if (config.promptManager) { context.promptManager = config.promptManager; } if (config.complianceTracker) { context.complianceTracker = config.complianceTracker; } if (config.delegationTracker) { context.delegationTracker = config.delegationTracker; } // Enhance the response const enhanced = await config.responseEnhancer.enhance(context); return enhanced; } catch (error) { // On error, return original response return toolResponse; } } /** * Set up server request handlers */ function setupServerHandlers(server, config, resourceManager) { // Tool call handler server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case 'check_tasks': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await checkTasks(config, args ?? {}); const enhanced = await enhanceToolResponse('check_tasks', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'read_task': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await readTask(config, args ?? {}); const enhanced = await enhanceToolResponse('read_task', result, agent, config); return { content: [ { type: 'text', text: typeof enhanced === 'string' ? enhanced : JSON.stringify(enhanced, null, 2) } ] }; } case 'write_task': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await writeTask(config, args ?? {}); const enhanced = await enhanceToolResponse('write_task', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'create_task': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await createTaskTool(config, args ?? {}); const enhanced = await enhanceToolResponse('create_task', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'list_agents': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await listAgents(config); const enhanced = await enhanceToolResponse('list_agents', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'archive_tasks': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await archiveTasksTool(config, args ?? {}); const enhanced = await enhanceToolResponse('archive_tasks', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'restore_tasks': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await restoreTasksTool(config, args ?? {}); const enhanced = await enhanceToolResponse('restore_tasks', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } // Context-based tools case 'get_task_context': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await getTaskContext(config, args ?? {}); const enhanced = await enhanceToolResponse('get_task_context', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'submit_plan': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await submitPlan(config, args ?? {}); const enhanced = await enhanceToolResponse('submit_plan', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'report_progress': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await reportProgress(config, args ?? {}); const enhanced = await enhanceToolResponse('report_progress', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'mark_complete': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await markComplete(config, args ?? {}); const enhanced = await enhanceToolResponse('mark_complete', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'archive_completed_tasks': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await archiveCompletedTasks(config, args ?? {}); const enhanced = await enhanceToolResponse('archive_completed_tasks', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } // Diagnostic tools (v0.4.0) case 'get_full_lifecycle': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await getFullLifecycle(config, (args ?? {})); const enhanced = await enhanceToolResponse('get_full_lifecycle', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'track_task_progress': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await trackTaskProgress(config, (args ?? {})); const enhanced = await enhanceToolResponse('track_task_progress', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } // Best practice tools case 'get_server_info': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await getServerInfoTool(config, args ?? {}); const enhanced = await enhanceToolResponse('get_server_info', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'ping': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await ping(config, args ?? {}); const enhanced = await enhanceToolResponse('ping', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'sync_todo_checkboxes': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await syncTodoCheckboxes(config, args ?? {}); const enhanced = await enhanceToolResponse('sync_todo_checkboxes', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } case 'protocol_config': { const agent = (args && typeof args === 'object' && 'agent' in args && typeof args['agent'] === 'string') ? args['agent'] : 'default-agent'; const result = await protocolConfig(args); const enhanced = await enhanceToolResponse('protocol_config', result, agent, config); return { content: [ { type: 'text', text: JSON.stringify(enhanced, null, 2) } ] }; } default: throw new AgentCommError(`Unknown tool: ${name}`, 'UNKNOWN_TOOL'); } } catch (error) { if (error instanceof AgentCommError) { throw error; } const message = error instanceof Error ? error.message : String(error); throw new AgentCommError(`Internal Error: ${message}`, 'INTERNAL_ERROR'); } }); // Tools list handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'check_tasks', description: 'Check for tasks assigned to an agent', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Agent name (e.g., senior-frontend-engineer)' } }, required: ['agent'] } }, { name: 'read_task', description: 'Read a task file by type (init, plan, done, error)', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Agent name' }, task: { type: 'string', description: 'Task folder name' }, file: { type: 'string', enum: ['INIT', 'PLAN', 'DONE', 'ERROR'], description: 'File type to read (init, plan, done, error)' } }, required: ['agent', 'task', 'file'] } }, { name: 'write_task', description: 'Write a task progress file', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Agent name' }, task: { type: 'string', description: 'Task folder name' }, file: { type: 'string', enum: ['PLAN', 'DONE', 'ERROR'], description: 'File type to write (plan, done, error)' }, content: { type: 'string', description: 'File content to write' } }, required: ['agent', 'task', 'file', 'content'] } }, { name: 'create_task', description: 'Unified task creation tool with duplicate prevention - replaces delegate_task and init_task', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Target agent name' }, taskName: { type: 'string', description: 'Clean task name (NO timestamps) - will be auto-timestamped internally' }, content: { type: 'string', description: 'Task content in markdown format (optional for self tasks)' }, parentTask: { type: 'string', description: 'Parent task ID for subtasks (optional)' } }, required: ['agent', 'taskName'] } }, { name: 'list_agents', description: 'List all agents with task counts and statistics', inputSchema: { type: 'object', properties: {}, additionalProperties: false } }, { name: 'archive_tasks', description: 'Archive tasks to clean up communication directory (clear comms)', inputSchema: { type: 'object', properties: { mode: { type: 'string', enum: ['completed', 'all', 'by-agent', 'by-date'], description: 'Archive mode: completed tasks only, all tasks, by specific agent, or by date' }, agent: { type: 'string', description: 'Agent name (required for by-agent mode)' }, olderThan: { type: 'number', description: 'Archive tasks older than N days (required for by-date mode)', minimum: 1 }, dryRun: { type: 'boolean', description: 'Preview changes without actually archiving' } } } }, { name: 'restore_tasks', description: 'Restore tasks from archive', inputSchema: { type: 'object', properties: { timestamp: { type: 'string', description: 'Archive timestamp (YYYY-MM-DDTHH-mm-ss format)' }, agent: { type: 'string', description: 'Restore tasks for specific agent only (optional)' }, taskName: { type: 'string', description: 'Restore tasks matching this name pattern (optional)' } }, required: ['timestamp'] } }, // Context-based tools { name: 'get_task_context', description: 'Get pure task context without file paths - for current or specified task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Optional task ID. If omitted, returns context for current active task' }, agent: { type: 'string', description: 'Agent name (defaults to default-agent if not provided)' } }, required: [] } }, { name: 'submit_plan', description: 'Submit implementation plan content - handles file creation internally', inputSchema: { type: 'object', properties: { content: { type: 'string', description: 'Plan content in markdown format with progress markers' }, agent: { type: 'string', description: 'Agent name submitting the plan' }, agentContext: { type: 'object', description: 'Optional: Agent context data including identity, capabilities, and working context', properties: { identity: { type: 'object' }, currentCapabilities: { type: 'object' }, workingContext: { type: 'object' } } }, contextEstimate: { type: 'object', description: 'Optional: Context estimate for the plan', properties: { estimatedTokensRequired: { type: 'number' }, confidenceLevel: { type: 'number' }, criticalSections: { type: 'array', items: { type: 'string' } } } } }, required: ['content', 'agent'] } }, { name: 'report_progress', description: 'Report progress updates on plan steps - no file operations exposed', inputSchema: { type: 'object', properties: { updates: { type: 'array', items: { type: 'object', properties: { step: { type: 'number', description: 'Step number being updated' }, status: { type: 'string', enum: ['COMPLETE', 'IN_PROGRESS', 'PENDING', 'BLOCKED'], description: 'New status for this step' }, description: { type: 'string', description: 'Description of work done or current state' }, timeSpent: { type: 'number', description: 'Optional: time spent in minutes' }, estimatedTimeRemaining: { type: 'number', description: 'Optional: estimated remaining time in minutes' }, blocker: { type: 'string', description: 'Optional: description of blocking issue' } }, required: ['step', 'status', 'description'] }, description: 'Array of step updates' }, agent: { type: 'string', description: 'Agent name reporting progress' }, contextStatus: { type: 'object', description: 'Optional: Current context usage status', properties: { currentUsage: { type: 'number' }, trend: { type: 'string', enum: ['INCREASING', 'DECREASING', 'STABLE'] }, estimatedRemaining: { type: 'number' } } }, capabilityChanges: { type: 'object', description: 'Optional: Capability changes discovered during execution', properties: { discoveredLimitations: { type: 'array', items: { type: 'string' } }, toolEffectiveness: { type: 'object' }, adaptations: { type: 'array', items: { type: 'string' } } } } }, required: ['updates', 'agent'] } }, { name: 'mark_complete', description: 'Mark task as complete or error with intelligent reconciliation for unchecked plan items', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['DONE', 'ERROR'], description: 'Completion status' }, summary: { type: 'string', description: 'Completion summary with results or error details' }, agent: { type: 'string', description: 'Agent name completing the task' }, reconciliation_mode: { type: 'string', enum: ['strict', 'auto_complete', 'reconcile', 'force'], description: 'How to handle unchecked plan items: strict (default, requires all checked), auto_complete (marks all complete), reconcile (explain variances), force (override with documentation)' }, reconciliation_explanations: { type: 'object', description: 'For reconcile mode: mapping of unchecked item titles to explanations of why they are complete', additionalProperties: { type: 'string' } } }, required: ['status', 'summary', 'agent'] } }, { name: 'archive_completed_tasks', description: 'Archive all completed tasks - batch cleanup operation', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Optional: Archive tasks for specific agent only' } }, required: [] } }, // Diagnostic tools (v0.4.0) { name: 'get_full_lifecycle', description: 'Get complete lifecycle visibility for a task - diagnostic tool for comprehensive task journey', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Agent name' }, taskId: { type: 'string', description: 'Task ID to get lifecycle for' }, include_progress: { type: 'boolean', description: 'Optional: Include progress markers analysis (default: true)' } }, required: ['agent', 'taskId'] } }, { name: 'track_task_progress', description: 'Track real-time task progress - diagnostic tool for progress monitoring', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Agent name' }, taskId: { type: 'string', description: 'Task ID to track progress for' } }, required: ['agent', 'taskId'] } }, { name: 'get_server_info', description: 'Get comprehensive server information including version, capabilities, and runtime status', inputSchema: { type: 'object', properties: {}, additionalProperties: false } }, { name: 'ping', description: 'Health check tool that returns server status and timestamp', inputSchema: { type: 'object', properties: {}, additionalProperties: false } }, { name: 'sync_todo_checkboxes', description: 'Sync TodoWrite updates to PLAN.md checkboxes - TodoWrite integration for automatic checkbox updates', inputSchema: { type: 'object', properties: { agent: { type: 'string', description: 'Agent name for which to sync todo updates' }, todoUpdates: { type: 'array', description: 'Array of todo update objects with title and status', items: { type: 'object', properties: { title: { type: 'string', description: 'Todo title to match against PLAN.md checkboxes' }, status: { type: 'string', enum: ['pending', 'in_progress', 'completed'], description: 'New status for the todo item' } }, required: ['title', 'status'] } }, taskId: { type: 'string', description: 'Optional specific task ID to target. If not provided, uses the most recent task for the agent.' } }, required: ['agent', 'todoUpdates'] } }, { name: 'protocol_config', description: 'Manage protocol injection configuration for task and plan templates', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['get', 'set', 'reset'], description: 'Action to perform: get current config, set new config, or reset to defaults' }, config: { type: 'object', properties: { task: { type: 'object', properties: { enabled: { type: 'boolean', description: 'Whether to inject protocol instructions in task INIT.md files' }, template: { type: 'string', description: 'Custom template for task protocol injection' } }, required: ['enabled', 'template'] }, plan: { type: 'object', properties: { enabled: { type: 'boolean', description: 'Whether to inject protocol instructions in plan PLAN.md files' }, template: { type: 'string', description: 'Custom template for plan protocol injection' } }, required: ['enabled', 'template'] } }, required: ['task', 'plan'], description: 'Protocol configuration object (required for set action)' } }, required: ['action'], additionalProperties: false } } ] }; }); // Resource handlers server.setRequestHandler(ListResourcesRequestSchema, async (request) => { // Handle the optional params with proper type narrowing for exactOptionalPropertyTypes const options = request.params?.cursor ? { cursor: request.params.cursor } : undefined; return resourceManager.listResources(options); }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { if (!request.params?.uri) { throw new AgentCommError('URI parameter is required', 'INVALID_PARAMS'); } return resourceManager.readResource(request.params.uri); }); // Initialize PromptManager const promptManager = new PromptManager(config); // Prompts list handler server.setRequestHandler(ListPromptsRequestSchema, async () => { const result = await promptManager.listPrompts(); return { prompts: result.prompts }; }); // Prompts get handler server.setRequestHandler(GetPromptRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; const result = await promptManager.getPrompt(name, args ?? {}); return { description: result.description, messages: result.messages }; } catch (error) { if (error instanceof Error) { throw new AgentCommError(error.message, 'PROMPT_ERROR'); } throw new AgentCommError('Failed to get prompt', 'PROMPT_ERROR'); } }); } async function main() { // Create MCP server const server = createMCPServer(); const config = getConfig(); const serverInfo = getServerInfo(); // Start server const transport = new StdioServerTransport(); await server.connect(transport); // Log to stderr (stdout is reserved for MCP protocol) console.error(`Agent Communication MCP Server v${serverInfo.version} started`); console.error(`Communication directory: ${config.commDir}`); if (config.enableArchiving) { console.error(`Archive directory: ${config.archiveDir}`); } else { console.error('Archiving is disabled'); } } // Handle graceful shutdown process.on('SIGINT', () => { console.error('Received SIGINT, shutting down gracefully...'); process.exit(0); }); process.on('SIGTERM', () => { console.error('Received SIGTERM, shutting down gracefully...'); process.exit(0); }); // Start the server main().catch((error) => { console.error('Failed to start server:', error); process.exit(1); }); //# sourceMappingURL=index.js.map