Skip to content

Commit b90319b

Browse files
Correctly register tools
1 parent e42e45f commit b90319b

File tree

5 files changed

+101
-233
lines changed

5 files changed

+101
-233
lines changed

index.ts

Lines changed: 96 additions & 227 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22

33
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
44
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5-
import * as path from "node:path";
6-
import * as os from "node:os";
75
import { z } from "zod";
86

97
import { TaskManagerServer } from "./src/server/TaskManagerServer.js";
10-
import { TaskState } from "./src/types/index.js";
11-
12-
const DEFAULT_PATH = path.join(os.homedir(), "Documents", "tasks.json");
13-
const TASK_FILE_PATH = process.env.TASK_MANAGER_FILE_PATH || DEFAULT_PATH;
8+
import { ALL_TOOLS } from "./src/types/tools.js";
149

1510
// Initialize the server
1611
const server = new McpServer({
@@ -20,232 +15,106 @@ const server = new McpServer({
2015

2116
const taskManager = new TaskManagerServer();
2217

23-
// Define our schemas
24-
const TaskSchema = z.object({
25-
title: z.string(),
26-
description: z.string(),
27-
toolRecommendations: z.string().optional(),
28-
ruleRecommendations: z.string().optional()
29-
});
30-
31-
type Task = z.infer<typeof TaskSchema>;
32-
33-
// Project tools
34-
server.tool(
35-
"list_projects",
36-
{
37-
state: z.enum(["open", "pending_approval", "completed", "all"]).optional()
38-
},
39-
async ({ state }: { state?: TaskState }) => {
40-
const result = await taskManager.listProjects(state);
41-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
42-
}
43-
);
44-
45-
server.tool(
46-
"read_project",
47-
{
48-
projectId: z.string()
49-
},
50-
async ({ projectId }: { projectId: string }) => {
51-
const result = await taskManager.openTaskDetails(projectId);
52-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
53-
}
54-
);
55-
56-
server.tool(
57-
"create_project",
58-
{
59-
initialPrompt: z.string(),
60-
projectPlan: z.string().optional(),
61-
tasks: z.array(TaskSchema)
62-
},
63-
async ({ initialPrompt, tasks, projectPlan }: {
64-
initialPrompt: string;
65-
tasks: Task[];
66-
projectPlan?: string;
67-
}) => {
68-
const result = await taskManager.createProject(initialPrompt, tasks, projectPlan);
69-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
70-
}
71-
);
72-
73-
server.tool(
74-
"delete_project",
75-
{
76-
projectId: z.string()
77-
},
78-
async ({ projectId }: { projectId: string }) => {
79-
const projectIndex = taskManager["data"].projects.findIndex(
80-
(p) => p.projectId === projectId
81-
);
82-
if (projectIndex === -1) {
83-
return { content: [{ type: "text", text: JSON.stringify({ error: "Project not found" }) }] };
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+
}
34+
}
35+
}
8436
}
85-
86-
taskManager["data"].projects.splice(projectIndex, 1);
87-
await taskManager["saveTasks"]();
88-
return { content: [{ type: "text", text: JSON.stringify({ message: "Project has been deleted" }) }] };
89-
}
90-
);
91-
92-
server.tool(
93-
"add_tasks_to_project",
94-
{
95-
projectId: z.string(),
96-
tasks: z.array(TaskSchema)
97-
},
98-
async ({ projectId, tasks }: { projectId: string; tasks: Task[] }) => {
99-
const result = await taskManager.addTasksToProject(projectId, tasks);
100-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
101-
}
102-
);
103-
104-
server.tool(
105-
"finalize_project",
106-
{
107-
projectId: z.string()
108-
},
109-
async ({ projectId }: { projectId: string }) => {
110-
const result = await taskManager.approveProjectCompletion(projectId);
111-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
112-
}
113-
);
114-
115-
// Task tools
116-
server.tool(
117-
"list_tasks",
118-
{
119-
projectId: z.string().optional(),
120-
state: z.enum(["open", "pending_approval", "completed", "all"]).optional()
121-
},
122-
async ({ projectId, state }: { projectId?: string; state?: TaskState }) => {
123-
const result = await taskManager.listTasks(projectId, state);
124-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
125-
}
126-
);
127-
128-
server.tool(
129-
"read_task",
130-
{
131-
taskId: z.string()
132-
},
133-
async ({ taskId }: { taskId: string }) => {
134-
const result = await taskManager.openTaskDetails(taskId);
135-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
136-
}
137-
);
138-
139-
server.tool(
140-
"create_task",
141-
{
142-
projectId: z.string(),
143-
title: z.string(),
144-
description: z.string(),
145-
toolRecommendations: z.string().optional(),
146-
ruleRecommendations: z.string().optional()
147-
},
148-
async ({ projectId, title, description, toolRecommendations, ruleRecommendations }: {
149-
projectId: string;
150-
title: string;
151-
description: string;
152-
toolRecommendations?: string;
153-
ruleRecommendations?: string;
154-
}) => {
155-
const result = await taskManager.addTasksToProject(projectId, [
156-
{ title, description, toolRecommendations, ruleRecommendations }
157-
]);
158-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
15937
}
160-
);
16138

162-
server.tool(
163-
"update_task",
164-
{
165-
projectId: z.string(),
166-
taskId: z.string(),
167-
title: z.string().optional(),
168-
description: z.string().optional(),
169-
status: z.enum(["not started", "in progress", "done"]).optional(),
170-
completedDetails: z.string().optional(),
171-
toolRecommendations: z.string().optional(),
172-
ruleRecommendations: z.string().optional()
173-
},
174-
async ({ projectId, taskId, title, description, status, completedDetails, toolRecommendations, ruleRecommendations }: {
175-
projectId: string;
176-
taskId: string;
177-
title?: string;
178-
description?: string;
179-
status?: "not started" | "in progress" | "done";
180-
completedDetails?: string;
181-
toolRecommendations?: string;
182-
ruleRecommendations?: string;
183-
}) => {
184-
// Handle status changes first
185-
if (status === "done") {
186-
if (!completedDetails) {
187-
return { content: [{ type: "text", text: JSON.stringify({ error: "completedDetails is required when setting status to 'done'" }) }] };
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'" }) }] };
89+
}
90+
await taskManager.markTaskDone(params.projectId, params.taskId, params.completedDetails);
91+
}
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;
188112
}
189-
await taskManager.markTaskDone(projectId, taskId, completedDetails);
190-
}
191-
192-
// Update other fields if provided
193-
const updates: {
194-
title?: string;
195-
description?: string;
196-
toolRecommendations?: string;
197-
ruleRecommendations?: string;
198-
} = {};
199-
200-
if (title) updates.title = title;
201-
if (description) updates.description = description;
202-
if (toolRecommendations) updates.toolRecommendations = toolRecommendations;
203-
if (ruleRecommendations) updates.ruleRecommendations = ruleRecommendations;
204-
205-
if (Object.keys(updates).length > 0) {
206-
const result = await taskManager.updateTask(projectId, taskId, updates);
207113
return { content: [{ type: "text", text: JSON.stringify(result) }] };
208114
}
209-
210-
return { content: [{ type: "text", text: JSON.stringify({ message: "Task updated" }) }] };
211-
}
212-
);
213-
214-
server.tool(
215-
"delete_task",
216-
{
217-
projectId: z.string(),
218-
taskId: z.string()
219-
},
220-
async ({ projectId, taskId }: { projectId: string; taskId: string }) => {
221-
const result = await taskManager.deleteTask(projectId, taskId);
222-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
223-
}
224-
);
225-
226-
server.tool(
227-
"approve_task",
228-
{
229-
projectId: z.string(),
230-
taskId: z.string()
231-
},
232-
async ({ projectId, taskId }: { projectId: string; taskId: string }) => {
233-
const result = await taskManager.approveTaskCompletion(projectId, taskId);
234-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
235-
}
236-
);
237-
238-
server.tool(
239-
"get_next_task",
240-
{
241-
projectId: z.string()
242-
},
243-
async ({ projectId }: { projectId: string }) => {
244-
const result = await taskManager.getNextTask(projectId);
245-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
246-
}
247-
);
115+
);
116+
}
248117

249118
// Start the server
250-
const transport = new StdioServerTransport();
251-
await server.connect(transport);
119+
const transport = new StdioServerTransport();
120+
await server.connect(transport);

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "taskqueue-mcp",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "Task Queue MCP Server",
55
"author": "Christopher C. Smith ([email protected])",
66
"main": "dist/index.js",

src/server/TaskManagerServer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as fs from "node:fs/promises";
22
import * as path from "node:path";
33
import * as os from "node:os";
4-
import { Task, Project, TaskManagerFile, TaskState } from "../types/index.js";
4+
import { Task, TaskManagerFile, TaskState } from "../types/index.js";
55

66
// Get platform-appropriate app data directory
77
const getAppDataDir = () => {
@@ -23,7 +23,6 @@ const getAppDataDir = () => {
2323

2424
// Default path follows platform-specific conventions
2525
const DEFAULT_PATH = path.join(getAppDataDir(), "tasks.json");
26-
2726
const TASK_FILE_PATH = process.env.TASK_MANAGER_FILE_PATH || DEFAULT_PATH;
2827

2928
export class TaskManagerServer {

tests/unit/tools.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,4 @@ describe('Tools', () => {
224224
expect(required).not.toContain("ruleRecommendations");
225225
});
226226
});
227-
});
227+
});

0 commit comments

Comments
 (0)