Skip to content

Commit 0402d0b

Browse files
Merge pull request #25 from chriscarrollsmith/5-standardize-error-message-formatting-in-update_task-and-related-functions
Standardize success and error message formatting
2 parents 0709381 + f9cc969 commit 0402d0b

File tree

13 files changed

+881
-514
lines changed

13 files changed

+881
-514
lines changed

.github/workflows/npm-publish.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ jobs:
1313
node-version: 'latest'
1414
- run: npm ci
1515
- run: npm install -g tsx
16-
- run: npm run build # Add build step if needed
1716
- run: npm test
1817

1918
publish:
2019
needs: test
2120
if: github.ref == 'refs/heads/main' # Only run this job on main branch
21+
permissions:
22+
packages: write
23+
contents: write
2224
runs-on: ubuntu-latest
2325
steps:
2426
- uses: actions/checkout@v4

index.ts

Lines changed: 8 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
44
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
55
import { TaskManager } from "./src/server/TaskManager.js";
6-
import { ALL_TOOLS } from "./src/server/tools.js";
6+
import { ALL_TOOLS, executeToolWithErrorHandling } from "./src/server/tools.js";
77
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
88

99
// Create server with capabilities BEFORE setting up handlers
1010
const server = new Server(
1111
{
1212
name: "task-manager-server",
13-
version: "1.0.9"
13+
version: "1.1.0"
1414
},
1515
{
1616
capabilities: {
@@ -28,7 +28,7 @@ console.error('Server starting with env:', {
2828
NODE_ENV: process.env.NODE_ENV
2929
});
3030

31-
// Initialize task manager
31+
// Create task manager instance
3232
const taskManager = new TaskManager();
3333

3434
// Set up request handlers AFTER capabilities are configured
@@ -39,210 +39,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
3939
});
4040

4141
server.setRequestHandler(CallToolRequestSchema, async (request) => {
42-
try {
43-
const { name } = request.params;
44-
const args = request.params.arguments || {};
45-
46-
// For validation, ensure args is an object when expected
47-
if (name !== "list_projects" && name !== "list_tasks" && Object.keys(args).length === 0) {
48-
throw new Error("Invalid arguments: expected object with parameters");
49-
}
50-
51-
switch (name) {
52-
// Project tools
53-
case "list_projects": {
54-
const result = await taskManager.listProjects();
55-
return {
56-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
57-
};
58-
}
59-
60-
case "read_project": {
61-
const projectId = String(args.projectId);
62-
if (!projectId) {
63-
throw new Error("Missing required parameter: projectId");
64-
}
65-
const result = await taskManager.getNextTask(projectId);
66-
return {
67-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
68-
};
69-
}
70-
71-
case "create_project": {
72-
const initialPrompt = String(args.initialPrompt || "");
73-
if (!initialPrompt || !args.tasks || !Array.isArray(args.tasks)) {
74-
throw new Error("Missing required parameters: initialPrompt and/or tasks");
75-
}
76-
const projectPlan = args.projectPlan ? String(args.projectPlan) : undefined;
77-
78-
const result = await taskManager.createProject(
79-
initialPrompt,
80-
args.tasks,
81-
projectPlan
82-
);
83-
return {
84-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
85-
};
86-
}
87-
88-
case "delete_project": {
89-
const projectId = String(args.projectId);
90-
if (!projectId) {
91-
throw new Error("Missing required parameter: projectId");
92-
}
93-
// Use the private data and saveTasks via indexing since there's no explicit delete method
94-
const projectIndex = taskManager["data"].projects.findIndex((p) => p.projectId === projectId);
95-
if (projectIndex === -1) {
96-
return {
97-
content: [{ type: "text", text: JSON.stringify({ status: "error", message: "Project not found" }, null, 2) }],
98-
};
99-
}
100-
101-
taskManager["data"].projects.splice(projectIndex, 1);
102-
await taskManager["saveTasks"]();
103-
return {
104-
content: [{ type: "text", text: JSON.stringify({
105-
status: "project_deleted",
106-
message: `Project ${projectId} has been deleted.`
107-
}, null, 2) }],
108-
};
109-
}
110-
111-
case "add_tasks_to_project": {
112-
const projectId = String(args.projectId);
113-
if (!projectId || !args.tasks || !Array.isArray(args.tasks)) {
114-
throw new Error("Missing required parameters: projectId and/or tasks");
115-
}
116-
const result = await taskManager.addTasksToProject(projectId, args.tasks);
117-
return {
118-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
119-
};
120-
}
121-
122-
case "finalize_project": {
123-
const projectId = String(args.projectId);
124-
if (!projectId) {
125-
throw new Error("Missing required parameter: projectId");
126-
}
127-
const result = await taskManager.approveProjectCompletion(projectId);
128-
return {
129-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
130-
};
131-
}
132-
133-
// Task tools
134-
case "list_tasks": {
135-
// No explicit list tasks method, so return a message
136-
return {
137-
content: [{ type: "text", text: JSON.stringify({
138-
status: "error",
139-
message: "list_tasks functionality to be implemented in future version"
140-
}, null, 2) }],
141-
};
142-
}
143-
144-
case "read_task": {
145-
const taskId = String(args.taskId);
146-
if (!taskId) {
147-
throw new Error("Missing required parameter: taskId");
148-
}
149-
const result = await taskManager.openTaskDetails(taskId);
150-
return {
151-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
152-
};
153-
}
154-
155-
case "create_task": {
156-
const projectId = String(args.projectId);
157-
const title = String(args.title || "");
158-
const description = String(args.description || "");
159-
160-
if (!projectId || !title || !description) {
161-
throw new Error("Missing required parameters: projectId, title, and/or description");
162-
}
163-
164-
const result = await taskManager.addTasksToProject(projectId, [{
165-
title,
166-
description
167-
}]);
168-
return {
169-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
170-
};
171-
}
172-
173-
case "update_task": {
174-
const projectId = String(args.projectId);
175-
const taskId = String(args.taskId);
176-
177-
if (!projectId || !taskId) {
178-
throw new Error("Missing required parameters: projectId and/or taskId");
179-
}
180-
181-
const updates = Object.fromEntries(
182-
Object.entries({
183-
title: args.title !== undefined ? String(args.title) : undefined,
184-
description: args.description !== undefined ? String(args.description) : undefined,
185-
status: args.status !== undefined ? String(args.status) as "not started" | "in progress" | "done" : undefined,
186-
completedDetails: args.completedDetails !== undefined ? String(args.completedDetails) : undefined,
187-
toolRecommendations: args.toolRecommendations !== undefined ? String(args.toolRecommendations) : undefined,
188-
ruleRecommendations: args.ruleRecommendations !== undefined ? String(args.ruleRecommendations) : undefined
189-
}).filter(([_, value]) => value !== undefined)
190-
);
191-
192-
const result = await taskManager.updateTask(projectId, taskId, updates);
193-
194-
return {
195-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
196-
};
197-
}
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}`);
238-
}
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-
}
42+
return executeToolWithErrorHandling(
43+
request.params.name,
44+
request.params.arguments || {},
45+
taskManager
46+
);
24647
});
24748

24849
// Start the server

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.9",
3+
"version": "1.1.0",
44
"description": "Task Queue MCP Server",
55
"author": "Christopher C. Smith ([email protected])",
66
"main": "dist/index.js",

src/client/cli.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { Command } from "commander";
44
import * as fs from "node:fs/promises";
55
import * as path from "node:path";
66
import * as os from "node:os";
7-
import { TaskManagerFile } from "../types/index.js";
87
import chalk from "chalk";
8+
import { TaskManagerFile, ErrorCode } from "../types/index.js";
9+
import { createError, normalizeError } from "../utils/errors.js";
10+
import { formatCliError } from "./errors.js";
911

1012
const program = new Command();
1113
const DEFAULT_PATH = path.join(os.homedir(), "Documents", "tasks.json");
@@ -30,11 +32,21 @@ async function readData(): Promise<TaskManagerFile> {
3032
try {
3133
return JSON.parse(data);
3234
} catch (error) {
33-
throw new Error(`Failed to parse JSON data: ${error instanceof Error ? error.message : String(error)}`);
35+
throw createError(
36+
ErrorCode.FileParseError,
37+
"Failed to parse task file",
38+
{ originalError: error }
39+
);
3440
}
3541
} catch (error) {
36-
console.error(chalk.red(`Error reading task file: ${error instanceof Error ? error.message : String(error)}`));
37-
return { projects: [] };
42+
if (error instanceof Error && error.message.includes("ENOENT")) {
43+
return { projects: [] };
44+
}
45+
throw createError(
46+
ErrorCode.FileReadError,
47+
"Failed to read task file",
48+
{ originalError: error }
49+
);
3850
}
3951
}
4052

@@ -54,8 +66,11 @@ async function writeData(data: TaskManagerFile): Promise<void> {
5466
await fs.writeFile(TASK_FILE_PATH, JSON.stringify(data, null, 2), "utf-8");
5567
console.log(chalk.green('Data saved successfully'));
5668
} catch (error) {
57-
console.error(chalk.red(`Error writing to task file: ${error instanceof Error ? error.message : String(error)}`));
58-
throw error;
69+
throw createError(
70+
ErrorCode.FileWriteError,
71+
"Failed to write task file",
72+
{ originalError: error }
73+
);
5974
}
6075
}
6176

@@ -65,10 +80,10 @@ program
6580
.version("1.0.0");
6681

6782
program
68-
.command("approve-task")
83+
.command("approve")
6984
.description("Approve a completed task")
70-
.argument("<projectId>", "ID of the project containing the task")
71-
.argument("<taskId>", "ID of the task to approve")
85+
.argument("<projectId>", "Project ID")
86+
.argument("<taskId>", "Task ID")
7287
.option('-f, --force', 'Force approval even if task is not marked as done')
7388
.action(async (projectId, taskId, options) => {
7489
try {
@@ -145,15 +160,15 @@ program
145160
console.log(chalk.yellow(`${completedTasks - approvedTasks} tasks remaining to be approved.`));
146161
}
147162
} catch (error) {
148-
console.error(chalk.red(`An error occurred: ${error instanceof Error ? error.message : String(error)}`));
163+
console.error(chalk.red(formatCliError(normalizeError(error))));
149164
process.exit(1);
150165
}
151166
});
152167

153168
program
154-
.command("approve-project")
155-
.description("Approve project completion")
156-
.argument("<projectId>", "ID of the project to approve")
169+
.command("finalize")
170+
.description("Mark a project as complete")
171+
.argument("<projectId>", "Project ID")
157172
.action(async (projectId) => {
158173
try {
159174
console.log(chalk.blue(`Approving project ${chalk.bold(projectId)}...`));
@@ -231,7 +246,7 @@ program
231246
console.log(chalk.blue(` task-manager-cli list -p ${projectId}`));
232247

233248
} catch (error) {
234-
console.error(chalk.red(`An error occurred: ${error instanceof Error ? error.message : String(error)}`));
249+
console.error(chalk.red(formatCliError(normalizeError(error))));
235250
process.exit(1);
236251
}
237252
});
@@ -366,7 +381,7 @@ program
366381
});
367382
}
368383
} catch (error) {
369-
console.error(chalk.red(`An error occurred: ${error instanceof Error ? error.message : String(error)}`));
384+
console.error(chalk.red(formatCliError(normalizeError(error))));
370385
process.exit(1);
371386
}
372387
});

src/client/errors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { StandardError } from "../types/index.js";
2+
/**
3+
* Formats an error message for CLI output
4+
*/
5+
export function formatCliError(error: StandardError): string {
6+
const details = error.details ? `: ${JSON.stringify(error.details)}` : '';
7+
return `[${error.code}] ${error.message}${details}`;
8+
}

0 commit comments

Comments
 (0)