|
1 | 1 | #!/usr/bin/env node
|
2 | 2 |
|
3 |
| -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
4 | 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
5 |
| -import { z } from "zod"; |
6 |
| - |
| 4 | +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; |
7 | 5 | import { TaskManagerServer } from "./src/server/TaskManagerServer.js";
|
8 | 6 | import { ALL_TOOLS } from "./src/types/tools.js";
|
| 7 | +import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; |
9 | 8 |
|
10 |
| -// Initialize the server |
11 |
| -const server = new McpServer({ |
| 9 | +const server = new Server({ |
12 | 10 | name: "task-manager-server",
|
13 |
| - version: "1.0.0" |
| 11 | + version: "1.0.4" |
14 | 12 | });
|
15 | 13 |
|
16 | 14 | const taskManager = new TaskManagerServer();
|
17 | 15 |
|
18 |
| -// Register all tools with their predefined schemas and descriptions |
19 |
| -for (const tool of ALL_TOOLS) { |
20 |
| - // Convert JSON schema properties to Zod schema |
21 |
| - const zodSchema: { [key: string]: z.ZodType<any> } = {}; |
22 |
| - |
23 |
| - if (tool.inputSchema.properties) { |
24 |
| - for (const [key, prop] of Object.entries(tool.inputSchema.properties)) { |
25 |
| - if (typeof prop === 'object' && prop !== null) { |
26 |
| - const schemaProp = prop as { type: string; enum?: string[]; description?: string }; |
27 |
| - if (schemaProp.type === "string") { |
28 |
| - zodSchema[key] = schemaProp.enum |
29 |
| - ? z.enum(schemaProp.enum as [string, ...string[]]) |
30 |
| - : z.string(); |
31 |
| - if (!Array.isArray(tool.inputSchema.required) || !tool.inputSchema.required.includes(key)) { |
32 |
| - zodSchema[key] = zodSchema[key].optional(); |
33 |
| - } |
| 16 | +server.setRequestHandler(ListToolsRequestSchema, async () => ({ |
| 17 | + tools: ALL_TOOLS, |
| 18 | +})); |
| 19 | + |
| 20 | +server.setRequestHandler(CallToolRequestSchema, async (request) => { |
| 21 | + try { |
| 22 | + const { name } = request.params; |
| 23 | + const args = request.params.arguments || {}; |
| 24 | + |
| 25 | + // For validation, ensure args is an object when expected |
| 26 | + if (name !== "list_projects" && name !== "list_tasks" && Object.keys(args).length === 0) { |
| 27 | + throw new Error("Invalid arguments: expected object with parameters"); |
| 28 | + } |
| 29 | + |
| 30 | + switch (name) { |
| 31 | + // Project tools |
| 32 | + case "list_projects": { |
| 33 | + const result = await taskManager.listProjects(); |
| 34 | + return { |
| 35 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 36 | + }; |
| 37 | + } |
| 38 | + |
| 39 | + case "read_project": { |
| 40 | + const projectId = String(args.projectId); |
| 41 | + if (!projectId) { |
| 42 | + throw new Error("Missing required parameter: projectId"); |
34 | 43 | }
|
| 44 | + const result = await taskManager.getNextTask(projectId); |
| 45 | + return { |
| 46 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 47 | + }; |
35 | 48 | }
|
36 |
| - } |
37 |
| - } |
38 | 49 |
|
39 |
| - server.tool( |
40 |
| - tool.name, |
41 |
| - zodSchema, |
42 |
| - async (params: any) => { |
43 |
| - let result; |
44 |
| - switch (tool.name) { |
45 |
| - case "list_projects": |
46 |
| - result = await taskManager.listProjects(params.state); |
47 |
| - break; |
48 |
| - case "read_project": |
49 |
| - result = await taskManager.openTaskDetails(params.projectId); |
50 |
| - break; |
51 |
| - case "create_project": |
52 |
| - result = await taskManager.createProject(params.initialPrompt, params.tasks, params.projectPlan); |
53 |
| - break; |
54 |
| - case "delete_project": |
55 |
| - const projectIndex = taskManager["data"].projects.findIndex( |
56 |
| - (p) => p.projectId === params.projectId |
57 |
| - ); |
58 |
| - if (projectIndex === -1) { |
59 |
| - return { content: [{ type: "text", text: JSON.stringify({ error: "Project not found" }) }] }; |
60 |
| - } |
61 |
| - taskManager["data"].projects.splice(projectIndex, 1); |
62 |
| - await taskManager["saveTasks"](); |
63 |
| - result = { message: "Project has been deleted" }; |
64 |
| - break; |
65 |
| - case "add_tasks_to_project": |
66 |
| - result = await taskManager.addTasksToProject(params.projectId, params.tasks); |
67 |
| - break; |
68 |
| - case "finalize_project": |
69 |
| - result = await taskManager.approveProjectCompletion(params.projectId); |
70 |
| - break; |
71 |
| - case "list_tasks": |
72 |
| - result = await taskManager.listTasks(params.projectId, params.state); |
73 |
| - break; |
74 |
| - case "read_task": |
75 |
| - result = await taskManager.openTaskDetails(params.taskId); |
76 |
| - break; |
77 |
| - case "create_task": |
78 |
| - result = await taskManager.addTasksToProject(params.projectId, [{ |
79 |
| - title: params.title, |
80 |
| - description: params.description, |
81 |
| - toolRecommendations: params.toolRecommendations, |
82 |
| - ruleRecommendations: params.ruleRecommendations |
83 |
| - }]); |
84 |
| - break; |
85 |
| - case "update_task": |
86 |
| - if (params.status === "done") { |
87 |
| - if (!params.completedDetails) { |
88 |
| - return { content: [{ type: "text", text: JSON.stringify({ error: "completedDetails is required when setting status to 'done'" }) }] }; |
| 50 | + case "create_project": { |
| 51 | + const initialPrompt = String(args.initialPrompt || ""); |
| 52 | + if (!initialPrompt || !args.tasks || !Array.isArray(args.tasks)) { |
| 53 | + throw new Error("Missing required parameters: initialPrompt and/or tasks"); |
| 54 | + } |
| 55 | + const projectPlan = args.projectPlan ? String(args.projectPlan) : undefined; |
| 56 | + |
| 57 | + const result = await taskManager.createProject( |
| 58 | + initialPrompt, |
| 59 | + args.tasks, |
| 60 | + projectPlan |
| 61 | + ); |
| 62 | + return { |
| 63 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 64 | + }; |
| 65 | + } |
| 66 | + |
| 67 | + case "delete_project": { |
| 68 | + const projectId = String(args.projectId); |
| 69 | + if (!projectId) { |
| 70 | + throw new Error("Missing required parameter: projectId"); |
| 71 | + } |
| 72 | + // Use the private data and saveTasks via indexing since there's no explicit delete method |
| 73 | + const projectIndex = taskManager["data"].projects.findIndex((p) => p.projectId === projectId); |
| 74 | + if (projectIndex === -1) { |
| 75 | + return { |
| 76 | + content: [{ type: "text", text: JSON.stringify({ status: "error", message: "Project not found" }, null, 2) }], |
| 77 | + }; |
| 78 | + } |
| 79 | + |
| 80 | + taskManager["data"].projects.splice(projectIndex, 1); |
| 81 | + await taskManager["saveTasks"](); |
| 82 | + return { |
| 83 | + content: [{ type: "text", text: JSON.stringify({ |
| 84 | + status: "project_deleted", |
| 85 | + message: `Project ${projectId} has been deleted.` |
| 86 | + }, null, 2) }], |
| 87 | + }; |
| 88 | + } |
| 89 | + |
| 90 | + case "add_tasks_to_project": { |
| 91 | + const projectId = String(args.projectId); |
| 92 | + if (!projectId || !args.tasks || !Array.isArray(args.tasks)) { |
| 93 | + throw new Error("Missing required parameters: projectId and/or tasks"); |
| 94 | + } |
| 95 | + const result = await taskManager.addTasksToProject(projectId, args.tasks); |
| 96 | + return { |
| 97 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 98 | + }; |
| 99 | + } |
| 100 | + |
| 101 | + case "finalize_project": { |
| 102 | + const projectId = String(args.projectId); |
| 103 | + if (!projectId) { |
| 104 | + throw new Error("Missing required parameter: projectId"); |
| 105 | + } |
| 106 | + const result = await taskManager.approveProjectCompletion(projectId); |
| 107 | + return { |
| 108 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 109 | + }; |
| 110 | + } |
| 111 | + |
| 112 | + // Task tools |
| 113 | + case "list_tasks": { |
| 114 | + // No explicit list tasks method, so return a message |
| 115 | + return { |
| 116 | + content: [{ type: "text", text: JSON.stringify({ |
| 117 | + status: "error", |
| 118 | + message: "list_tasks functionality to be implemented in future version" |
| 119 | + }, null, 2) }], |
| 120 | + }; |
| 121 | + } |
| 122 | + |
| 123 | + case "read_task": { |
| 124 | + const taskId = String(args.taskId); |
| 125 | + if (!taskId) { |
| 126 | + throw new Error("Missing required parameter: taskId"); |
| 127 | + } |
| 128 | + const result = await taskManager.openTaskDetails(taskId); |
| 129 | + return { |
| 130 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 131 | + }; |
| 132 | + } |
| 133 | + |
| 134 | + case "create_task": { |
| 135 | + const projectId = String(args.projectId); |
| 136 | + const title = String(args.title || ""); |
| 137 | + const description = String(args.description || ""); |
| 138 | + |
| 139 | + if (!projectId || !title || !description) { |
| 140 | + throw new Error("Missing required parameters: projectId, title, and/or description"); |
| 141 | + } |
| 142 | + |
| 143 | + const result = await taskManager.addTasksToProject(projectId, [{ |
| 144 | + title, |
| 145 | + description |
| 146 | + }]); |
| 147 | + return { |
| 148 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 149 | + }; |
| 150 | + } |
| 151 | + |
| 152 | + case "update_task": { |
| 153 | + const projectId = String(args.projectId); |
| 154 | + const taskId = String(args.taskId); |
| 155 | + |
| 156 | + if (!projectId || !taskId) { |
| 157 | + throw new Error("Missing required parameters: projectId and/or taskId"); |
| 158 | + } |
| 159 | + |
| 160 | + const updates: { title?: string; description?: string } = {}; |
| 161 | + if (args.title !== undefined) updates.title = String(args.title); |
| 162 | + if (args.description !== undefined) updates.description = String(args.description); |
| 163 | + |
| 164 | + const result = await taskManager.updateTask(projectId, taskId, updates); |
| 165 | + |
| 166 | + // Handle status change separately if needed |
| 167 | + if (args.status) { |
| 168 | + const status = args.status as "not started" | "in progress" | "done"; |
| 169 | + const proj = taskManager["data"].projects.find(p => p.projectId === projectId); |
| 170 | + if (proj) { |
| 171 | + const task = proj.tasks.find(t => t.id === taskId); |
| 172 | + if (task) { |
| 173 | + if (status === "done") { |
| 174 | + if (!args.completedDetails) { |
| 175 | + return { |
| 176 | + content: [{ type: "text", text: JSON.stringify({ |
| 177 | + status: "error", |
| 178 | + message: "completedDetails is required when setting status to 'done'" |
| 179 | + }, null, 2) }], |
| 180 | + }; |
| 181 | + } |
| 182 | + |
| 183 | + // Use markTaskDone for proper transition to done status |
| 184 | + await taskManager.markTaskDone(projectId, taskId, String(args.completedDetails)); |
| 185 | + } else { |
| 186 | + // For other status changes |
| 187 | + task.status = status; |
| 188 | + await taskManager["saveTasks"](); |
| 189 | + } |
89 | 190 | }
|
90 |
| - await taskManager.markTaskDone(params.projectId, params.taskId, params.completedDetails); |
91 | 191 | }
|
92 |
| - const updates: any = {}; |
93 |
| - if (params.title) updates.title = params.title; |
94 |
| - if (params.description) updates.description = params.description; |
95 |
| - if (params.toolRecommendations) updates.toolRecommendations = params.toolRecommendations; |
96 |
| - if (params.ruleRecommendations) updates.ruleRecommendations = params.ruleRecommendations; |
97 |
| - if (Object.keys(updates).length > 0) { |
98 |
| - result = await taskManager.updateTask(params.projectId, params.taskId, updates); |
99 |
| - } else { |
100 |
| - result = { message: "Task updated" }; |
101 |
| - } |
102 |
| - break; |
103 |
| - case "delete_task": |
104 |
| - result = await taskManager.deleteTask(params.projectId, params.taskId); |
105 |
| - break; |
106 |
| - case "approve_task": |
107 |
| - result = await taskManager.approveTaskCompletion(params.projectId, params.taskId); |
108 |
| - break; |
109 |
| - case "get_next_task": |
110 |
| - result = await taskManager.getNextTask(params.projectId); |
111 |
| - break; |
| 192 | + } |
| 193 | + |
| 194 | + return { |
| 195 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 196 | + }; |
112 | 197 | }
|
113 |
| - return { content: [{ type: "text", text: JSON.stringify(result) }] }; |
| 198 | + |
| 199 | + case "delete_task": { |
| 200 | + const projectId = String(args.projectId); |
| 201 | + const taskId = String(args.taskId); |
| 202 | + |
| 203 | + if (!projectId || !taskId) { |
| 204 | + throw new Error("Missing required parameters: projectId and/or taskId"); |
| 205 | + } |
| 206 | + const result = await taskManager.deleteTask(projectId, taskId); |
| 207 | + return { |
| 208 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 209 | + }; |
| 210 | + } |
| 211 | + |
| 212 | + case "approve_task": { |
| 213 | + const projectId = String(args.projectId); |
| 214 | + const taskId = String(args.taskId); |
| 215 | + |
| 216 | + if (!projectId || !taskId) { |
| 217 | + throw new Error("Missing required parameters: projectId and/or taskId"); |
| 218 | + } |
| 219 | + const result = await taskManager.approveTaskCompletion(projectId, taskId); |
| 220 | + return { |
| 221 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 222 | + }; |
| 223 | + } |
| 224 | + |
| 225 | + case "get_next_task": { |
| 226 | + const projectId = String(args.projectId); |
| 227 | + if (!projectId) { |
| 228 | + throw new Error("Missing required parameter: projectId"); |
| 229 | + } |
| 230 | + const result = await taskManager.getNextTask(projectId); |
| 231 | + return { |
| 232 | + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], |
| 233 | + }; |
| 234 | + } |
| 235 | + |
| 236 | + default: |
| 237 | + throw new Error(`Unknown tool: ${name}`); |
114 | 238 | }
|
115 |
| - ); |
116 |
| -} |
| 239 | + } catch (error) { |
| 240 | + const errorMessage = error instanceof Error ? error.message : String(error); |
| 241 | + return { |
| 242 | + content: [{ type: "text", text: `Error: ${errorMessage}` }], |
| 243 | + isError: true, |
| 244 | + }; |
| 245 | + } |
| 246 | +}); |
117 | 247 |
|
118 | 248 | // Start the server
|
119 | 249 | const transport = new StdioServerTransport();
|
|
0 commit comments