feat: Add task-mcp server for task management via MCP
Implements 10 MCP tools for task management: - CRUD: task_add, task_list, task_show, task_close, task_update - Search: task_similar (pgvector), task_context - Relations: task_link, task_checklist_add, task_checklist_toggle Uses PostgreSQL with pgvector for semantic search via LiteLLM embeddings. Connects via SSH tunnel to docker-host:5435. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
166
src/index.ts
Normal file
166
src/index.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
#!/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 } from './tools/relations.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;
|
||||
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user