/** * MCP Server for Team Board Agent * Provides tools for agent to interact with Tracker: * - send_message: send chat message via IPC * - update_task: update task status * - create_comment: comment on a task * * Runs as a child process of the agent, communicates via stdio. * Uses file-based IPC to communicate with the host runner. */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import fs from 'fs'; import path from 'path'; const IPC_DIR = '/workspace/ipc'; const MESSAGES_DIR = path.join(IPC_DIR, 'messages'); const TASKS_DIR = path.join(IPC_DIR, 'tasks'); const agentSlug = process.env.AGENT_SLUG || 'unknown'; const agentName = process.env.AGENT_NAME || 'Agent'; function writeIpcFile(dir: string, data: object): string { fs.mkdirSync(dir, { recursive: true }); const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`; const filepath = path.join(dir, filename); const tmpPath = `${filepath}.tmp`; fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2)); fs.renameSync(tmpPath, filepath); return filename; } const server = new McpServer({ name: 'tracker', version: '1.0.0', }); // --- Chat --- server.tool( 'send_message', 'Send a message to a chat room (lobby, project, or task chat). Use for progress updates or responses.', { chat_id: z.string().describe('Chat ID to send the message to'), text: z.string().describe('Message text'), }, async (args) => { writeIpcFile(MESSAGES_DIR, { type: 'chat_message', chat_id: args.chat_id, content: args.text, sender_type: 'agent', sender_name: agentName, sender_slug: agentSlug, timestamp: new Date().toISOString(), }); return { content: [{ type: 'text' as const, text: 'Message sent.' }] }; }, ); // --- Tasks --- server.tool( 'update_task_status', 'Move a task to a different status (backlog, todo, in_progress, in_review, done)', { task_id: z.string().describe('Task ID'), status: z.enum(['backlog', 'todo', 'in_progress', 'in_review', 'done']).describe('New status'), comment: z.string().optional().describe('Optional comment about the status change'), }, async (args) => { writeIpcFile(TASKS_DIR, { type: 'task_status', task_id: args.task_id, status: args.status, comment: args.comment, agent_slug: agentSlug, timestamp: new Date().toISOString(), }); return { content: [{ type: 'text' as const, text: `Task ${args.task_id} → ${args.status}` }] }; }, ); server.tool( 'add_task_comment', 'Add a comment to a task (for progress updates, questions, or results)', { task_id: z.string().describe('Task ID'), comment: z.string().describe('Comment text'), }, async (args) => { writeIpcFile(TASKS_DIR, { type: 'task_comment', task_id: args.task_id, content: args.comment, sender_type: 'agent', sender_name: agentName, agent_slug: agentSlug, timestamp: new Date().toISOString(), }); return { content: [{ type: 'text' as const, text: 'Comment added.' }] }; }, ); server.tool( 'take_task', 'Take a task and start working on it. Changes status to in_progress.', { task_id: z.string().describe('Task ID to take'), }, async (args) => { writeIpcFile(TASKS_DIR, { type: 'task_take', task_id: args.task_id, agent_slug: agentSlug, timestamp: new Date().toISOString(), }); return { content: [{ type: 'text' as const, text: `Task ${args.task_id} taken.` }] }; }, ); server.tool( 'complete_task', 'Mark a task as done.', { task_id: z.string().describe('Task ID'), summary: z.string().optional().describe('Completion summary'), }, async (args) => { writeIpcFile(TASKS_DIR, { type: 'task_complete', task_id: args.task_id, summary: args.summary, agent_slug: agentSlug, timestamp: new Date().toISOString(), }); return { content: [{ type: 'text' as const, text: `Task ${args.task_id} completed.` }] }; }, ); // --- Start --- const transport = new StdioServerTransport(); await server.connect(transport);