Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MCP Task Manager

A Model Context Protocol (MCP) server for AI task management. This tool helps AI assistants handle multi-step tasks in a structured way, with user approval checkpoints.
MCP Task Manager ([npm package: taskqueue-mcp](https://www.npmjs.com/package/taskqueue-mcp)) is a Model Context Protocol (MCP) server for AI task management. This tool helps AI assistants handle multi-step tasks in a structured way, with optional user approval checkpoints.

## Features

Expand Down
47 changes: 13 additions & 34 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot
const server = new Server(
{
name: "task-manager-server",
version: "1.0.7"
version: "1.0.8"
},
{
capabilities: {
Expand Down Expand Up @@ -178,40 +178,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
throw new Error("Missing required parameters: projectId and/or taskId");
}

const updates: { title?: string; description?: string } = {};
if (args.title !== undefined) updates.title = String(args.title);
if (args.description !== undefined) updates.description = String(args.description);

const updates = Object.fromEntries(
Object.entries({
title: args.title !== undefined ? String(args.title) : undefined,
description: args.description !== undefined ? String(args.description) : undefined,
status: args.status !== undefined ? String(args.status) as "not started" | "in progress" | "done" : undefined,
completedDetails: args.completedDetails !== undefined ? String(args.completedDetails) : undefined,
toolRecommendations: args.toolRecommendations !== undefined ? String(args.toolRecommendations) : undefined,
ruleRecommendations: args.ruleRecommendations !== undefined ? String(args.ruleRecommendations) : undefined
}).filter(([_, value]) => value !== undefined)
);

const result = await taskManager.updateTask(projectId, taskId, updates);

// Handle status change separately if needed
if (args.status) {
const status = args.status as "not started" | "in progress" | "done";
const proj = taskManager["data"].projects.find(p => p.projectId === projectId);
if (proj) {
const task = proj.tasks.find(t => t.id === taskId);
if (task) {
if (status === "done") {
if (!args.completedDetails) {
return {
content: [{ type: "text", text: JSON.stringify({
status: "error",
message: "completedDetails is required when setting status to 'done'"
}, null, 2) }],
};
}

// Use markTaskDone for proper transition to done status
await taskManager.markTaskDone(projectId, taskId, String(args.completedDetails));
} else {
// For other status changes
task.status = status;
await taskManager["saveTasks"]();
}
}
}
}


return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
Expand Down
6 changes: 6 additions & 0 deletions jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ module.exports = {
],
},
modulePathIgnorePatterns: ['<rootDir>/dist/'],
// Force Jest to exit after all tests have completed
forceExit: true,
// Detect open handles and warn about them
detectOpenHandles: true,
// Extend the timeout to allow sufficient time for tests to complete
testTimeout: 30000,
};
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "taskqueue-mcp",
"version": "1.0.7",
"version": "1.0.8",
"description": "Task Queue MCP Server",
"author": "Christopher C. Smith ([email protected])",
"main": "dist/index.js",
Expand Down
43 changes: 10 additions & 33 deletions src/server/TaskManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ export class TaskManager {
public async createProject(
initialPrompt: string,
tasks: { title: string; description: string; toolRecommendations?: string; ruleRecommendations?: string }[],
projectPlan?: string
projectPlan?: string,
autoApprove?: boolean
) {
await this.ensureInitialized();
this.projectCounter += 1;
Expand All @@ -156,6 +157,7 @@ export class TaskManager {
projectPlan: projectPlan || initialPrompt,
tasks: newTasks,
completed: false,
autoApprove: autoApprove === true ? true : false,
});

await this.saveTasks();
Expand Down Expand Up @@ -213,38 +215,6 @@ export class TaskManager {
};
}

public async markTaskDone(
projectId: string,
taskId: string,
completedDetails?: string
) {
await this.ensureInitialized();
const proj = this.data.projects.find((p) => p.projectId === projectId);
if (!proj) return { status: "error", message: "Project not found" };
const task = proj.tasks.find((t) => t.id === taskId);
if (!task) return { status: "error", message: "Task not found" };
if (task.status === "done")
return {
status: "already_done",
message: "Task is already marked done.",
};

task.status = "done";
task.completedDetails = completedDetails || "";
await this.saveTasks();
return {
status: "task_marked_done",
projectId: proj.projectId,
task: {
id: task.id,
title: task.title,
description: task.description,
completedDetails: task.completedDetails,
approved: task.approved,
},
};
}

public async approveTaskCompletion(projectId: string, taskId: string) {
await this.ensureInitialized();
const proj = this.data.projects.find((p) => p.projectId === projectId);
Expand Down Expand Up @@ -457,6 +427,8 @@ export class TaskManager {
description?: string;
toolRecommendations?: string;
ruleRecommendations?: string;
status?: "not started" | "in progress" | "done";
completedDetails?: string;
}
) {
await this.ensureInitialized();
Expand All @@ -473,6 +445,11 @@ export class TaskManager {
// Update the task with the provided updates
project.tasks[taskIndex] = { ...project.tasks[taskIndex], ...updates };

// Check if status was updated to 'done' and if project has autoApprove enabled
if (updates.status === 'done' && project.autoApprove) {
project.tasks[taskIndex].approved = true;
}

await this.saveTasks();
return project.tasks[taskIndex];
}
Expand Down
4 changes: 4 additions & 0 deletions src/server/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const createProjectTool: Tool = {
required: ["title", "description"],
},
},
autoApprove: {
type: "boolean",
description: "If true, tasks will be automatically approved when marked as done. If false or not provided, tasks require manual approval.",
},
},
required: ["initialPrompt", "tasks"],
},
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Project {
projectPlan: string;
tasks: Task[];
completed: boolean;
autoApprove?: boolean;
}

export interface TaskManagerFile {
Expand Down
Loading