Session 374: Task-MCP and Delegation System Integration (Phase 4 & 6) - Add task_delegations tool: query delegations for a specific task - Add task_delegation_query tool: query across all tasks by status/backend - Enhance taskShow() to display recent delegation history - New delegations.ts module with getRecentDelegations helper Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
212 lines
5.4 KiB
JavaScript
212 lines
5.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Task MCP Server
|
|
*
|
|
* Exposes task management tools via Model Context Protocol.
|
|
* Uses PostgreSQL with pgvector for semantic search.
|
|
*
|
|
* Requires SSH tunnel to docker-host:
|
|
* ssh -L 5432:172.27.0.3:5432 docker-host -N &
|
|
*/
|
|
|
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
import {
|
|
CallToolRequestSchema,
|
|
ListToolsRequestSchema,
|
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
|
|
import { testConnection, close } from './db.js';
|
|
import { toolDefinitions } from './tools/index.js';
|
|
import { taskAdd, taskList, taskShow, taskClose, taskUpdate } from './tools/crud.js';
|
|
import { taskSimilar, taskContext } from './tools/search.js';
|
|
import { taskLink, checklistAdd, checklistToggle, taskResolveDuplicate } from './tools/relations.js';
|
|
import { epicAdd, epicList, epicShow, epicAssign } from './tools/epics.js';
|
|
import { taskDelegations, taskDelegationQuery } from './tools/delegations.js';
|
|
|
|
// Create MCP server
|
|
const server = new Server(
|
|
{ name: 'task-mcp', version: '1.0.0' },
|
|
{ capabilities: { tools: {} } }
|
|
);
|
|
|
|
// Register tool list handler
|
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
tools: toolDefinitions,
|
|
}));
|
|
|
|
// Register tool call handler
|
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
const { name, arguments: args } = request.params;
|
|
|
|
try {
|
|
let result: string;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const a = args as any;
|
|
|
|
switch (name) {
|
|
// CRUD
|
|
case 'task_add':
|
|
result = await taskAdd({
|
|
title: a.title,
|
|
project: a.project,
|
|
type: a.type,
|
|
priority: a.priority,
|
|
description: a.description,
|
|
});
|
|
break;
|
|
case 'task_list':
|
|
result = await taskList({
|
|
project: a.project,
|
|
status: a.status,
|
|
type: a.type,
|
|
priority: a.priority,
|
|
limit: a.limit,
|
|
});
|
|
break;
|
|
case 'task_show':
|
|
result = await taskShow(a.id);
|
|
break;
|
|
case 'task_close':
|
|
result = await taskClose(a.id);
|
|
break;
|
|
case 'task_update':
|
|
result = await taskUpdate({
|
|
id: a.id,
|
|
status: a.status,
|
|
priority: a.priority,
|
|
type: a.type,
|
|
title: a.title,
|
|
});
|
|
break;
|
|
|
|
// Search
|
|
case 'task_similar':
|
|
result = await taskSimilar({
|
|
query: a.query,
|
|
project: a.project,
|
|
limit: a.limit,
|
|
});
|
|
break;
|
|
case 'task_context':
|
|
result = await taskContext({
|
|
description: a.description,
|
|
project: a.project,
|
|
limit: a.limit,
|
|
});
|
|
break;
|
|
|
|
// Relations
|
|
case 'task_link':
|
|
result = await taskLink({
|
|
from_id: a.from_id,
|
|
to_id: a.to_id,
|
|
link_type: a.link_type,
|
|
});
|
|
break;
|
|
case 'task_checklist_add':
|
|
result = await checklistAdd({
|
|
task_id: a.task_id,
|
|
item: a.item,
|
|
});
|
|
break;
|
|
case 'task_checklist_toggle':
|
|
result = await checklistToggle({
|
|
item_id: a.item_id,
|
|
checked: a.checked,
|
|
});
|
|
break;
|
|
case 'task_resolve_duplicate':
|
|
result = await taskResolveDuplicate({
|
|
duplicate_id: a.duplicate_id,
|
|
dominant_id: a.dominant_id,
|
|
});
|
|
break;
|
|
|
|
// Epics
|
|
case 'epic_add':
|
|
result = await epicAdd({
|
|
title: a.title,
|
|
project: a.project,
|
|
description: a.description,
|
|
});
|
|
break;
|
|
case 'epic_list':
|
|
result = await epicList({
|
|
project: a.project,
|
|
status: a.status,
|
|
limit: a.limit,
|
|
});
|
|
break;
|
|
case 'epic_show':
|
|
result = await epicShow(a.id);
|
|
break;
|
|
case 'epic_assign':
|
|
result = await epicAssign({
|
|
task_id: a.task_id,
|
|
epic_id: a.epic_id,
|
|
});
|
|
break;
|
|
|
|
// Delegations
|
|
case 'task_delegations':
|
|
result = await taskDelegations({ task_id: a.task_id });
|
|
break;
|
|
case 'task_delegation_query':
|
|
result = await taskDelegationQuery({
|
|
status: a.status,
|
|
backend: a.backend,
|
|
limit: a.limit,
|
|
});
|
|
break;
|
|
|
|
default:
|
|
throw new Error(`Unknown tool: ${name}`);
|
|
}
|
|
|
|
return {
|
|
content: [{ type: 'text', text: result }],
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return {
|
|
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
});
|
|
|
|
// Main entry point
|
|
async function main() {
|
|
// Test database connection
|
|
const connected = await testConnection();
|
|
if (!connected) {
|
|
console.error('Failed to connect to database. Ensure SSH tunnel is active:');
|
|
console.error(' ssh -L 5432:172.27.0.3:5432 docker-host -N &');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.error('task-mcp: Connected to database');
|
|
|
|
// Set up cleanup
|
|
process.on('SIGINT', async () => {
|
|
await close();
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGTERM', async () => {
|
|
await close();
|
|
process.exit(0);
|
|
});
|
|
|
|
// Start server
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
console.error('task-mcp: Server started');
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('task-mcp: Fatal error:', error);
|
|
process.exit(1);
|
|
});
|