MCP TaskManager
by Rudra-ravi
Verified
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
interface Task {
id: string;
title: string;
description: string;
done: boolean;
approved: boolean;
completedDetails: string;
}
interface RequestEntry {
requestId: string;
originalRequest: string;
splitDetails: string;
tasks: Task[];
completed: boolean;
}
interface TaskManagerFile {
requests: RequestEntry[];
}
// Zod Schemas
const RequestPlanningSchema = z.object({
originalRequest: z.string(),
splitDetails: z.string().optional(),
tasks: z.array(
z.object({
title: z.string(),
description: z.string(),
})
),
});
const GetNextTaskSchema = z.object({
requestId: z.string(),
});
const MarkTaskDoneSchema = z.object({
requestId: z.string(),
taskId: z.string(),
completedDetails: z.string().optional(),
});
const ApproveTaskCompletionSchema = z.object({
requestId: z.string(),
taskId: z.string(),
});
const ApproveRequestCompletionSchema = z.object({
requestId: z.string(),
});
const OpenTaskDetailsSchema = z.object({
taskId: z.string(),
});
const ListRequestsSchema = z.object({});
const AddTasksToRequestSchema = z.object({
requestId: z.string(),
tasks: z.array(
z.object({
title: z.string(),
description: z.string(),
})
),
});
const UpdateTaskSchema = z.object({
requestId: z.string(),
taskId: z.string(),
title: z.string().optional(),
description: z.string().optional(),
});
const DeleteTaskSchema = z.object({
requestId: z.string(),
taskId: z.string(),
});
export class TaskManagerServer {
private requestCounter = 0;
private taskCounter = 0;
private data: TaskManagerFile = { requests: [] };
private kv: KVNamespace | null = null;
private initialized = false;
constructor(kv?: KVNamespace) {
this.kv = kv || null;
}
private async init() {
if (!this.initialized) {
await this.loadTasks();
this.initialized = true;
}
}
private async loadTasks() {
if (this.kv) {
const data = await this.kv.get("tasks", { type: "json" });
if (data) {
this.data = data as TaskManagerFile;
const allTaskIds: number[] = [];
const allRequestIds: number[] = [];
for (const req of this.data.requests) {
const reqNum = Number.parseInt(req.requestId.replace("req-", ""), 10);
if (!Number.isNaN(reqNum)) {
allRequestIds.push(reqNum);
}
for (const t of req.tasks) {
const tNum = Number.parseInt(t.id.replace("task-", ""), 10);
if (!Number.isNaN(tNum)) {
allTaskIds.push(tNum);
}
}
}
this.requestCounter =
allRequestIds.length > 0 ? Math.max(...allRequestIds) : 0;
this.taskCounter = allTaskIds.length > 0 ? Math.max(...allTaskIds) : 0;
}
}
}
private async saveTasks() {
if (this.kv) {
await this.kv.put("tasks", JSON.stringify(this.data));
}
}
public async listTools() {
await this.init();
return [
{
name: "request_planning",
description: "Register a new user request and plan its associated tasks.",
inputSchema: RequestPlanningSchema,
},
{
name: "get_next_task",
description: "Get the next pending task for a request.",
inputSchema: GetNextTaskSchema,
},
{
name: "mark_task_done",
description: "Mark a task as completed.",
inputSchema: MarkTaskDoneSchema,
},
{
name: "approve_task_completion",
description: "Approve a completed task.",
inputSchema: ApproveTaskCompletionSchema,
},
{
name: "approve_request_completion",
description: "Approve the completion of an entire request.",
inputSchema: ApproveRequestCompletionSchema,
},
{
name: "open_task_details",
description: "Get details of a specific task.",
inputSchema: OpenTaskDetailsSchema,
},
{
name: "list_requests",
description: "List all requests in the system.",
inputSchema: ListRequestsSchema,
},
{
name: "add_tasks_to_request",
description: "Add new tasks to an existing request.",
inputSchema: AddTasksToRequestSchema,
},
{
name: "update_task",
description: "Update an existing task.",
inputSchema: UpdateTaskSchema,
},
{
name: "delete_task",
description: "Delete a task from a request.",
inputSchema: DeleteTaskSchema,
},
];
}
public async callTool(name: string, parameters: any) {
await this.init();
switch (name) {
case "request_planning": {
const parsed = RequestPlanningSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.requestPlanning(
parsed.data.originalRequest,
parsed.data.tasks,
parsed.data.splitDetails
);
}
case "get_next_task": {
const parsed = GetNextTaskSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.getNextTask(parsed.data.requestId);
}
case "mark_task_done": {
const parsed = MarkTaskDoneSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.markTaskDone(
parsed.data.requestId,
parsed.data.taskId,
parsed.data.completedDetails
);
}
case "approve_task_completion": {
const parsed = ApproveTaskCompletionSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.approveTaskCompletion(
parsed.data.requestId,
parsed.data.taskId
);
}
case "approve_request_completion": {
const parsed = ApproveRequestCompletionSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.approveRequestCompletion(parsed.data.requestId);
}
case "open_task_details": {
const parsed = OpenTaskDetailsSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.openTaskDetails(parsed.data.taskId);
}
case "list_requests": {
const parsed = ListRequestsSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.listRequests();
}
case "add_tasks_to_request": {
const parsed = AddTasksToRequestSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.addTasksToRequest(parsed.data.requestId, parsed.data.tasks);
}
case "update_task": {
const parsed = UpdateTaskSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.updateTask(parsed.data.requestId, parsed.data.taskId, {
title: parsed.data.title,
description: parsed.data.description,
});
}
case "delete_task": {
const parsed = DeleteTaskSchema.safeParse(parameters);
if (!parsed.success) {
throw new Error(`Invalid parameters: ${parsed.error}`);
}
return this.deleteTask(parsed.data.requestId, parsed.data.taskId);
}
default:
throw new Error(`Unknown tool: ${name}`);
}
}
private formatTaskProgressTable(requestId: string): string {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) return "Request not found";
let table = "Task Progress:\n";
table += "| Status | Task | Description |\n";
table += "|--------|------|-------------|\n";
for (const task of request.tasks) {
const status = task.approved
? "✅"
: task.done
? "⏳"
: "❌";
table += `| ${status} | ${task.title} | ${task.description} |\n`;
}
return table;
}
private formatRequestsList(): string {
let output = "Requests:\n";
output += "| ID | Original Request | Tasks Done | Total Tasks |\n";
output += "|-----|-----------------|------------|-------------|\n";
for (const req of this.data.requests) {
const doneTasks = req.tasks.filter((t) => t.approved).length;
output += `| ${req.requestId} | ${req.originalRequest} | ${doneTasks} | ${req.tasks.length} |\n`;
}
return output;
}
public async requestPlanning(
originalRequest: string,
tasks: { title: string; description: string }[],
splitDetails?: string
) {
this.requestCounter++;
const requestId = `req-${this.requestCounter}`;
const taskEntries: Task[] = tasks.map((t) => {
this.taskCounter++;
return {
id: `task-${this.taskCounter}`,
title: t.title,
description: t.description,
done: false,
approved: false,
completedDetails: "",
};
});
this.data.requests.push({
requestId,
originalRequest,
splitDetails: splitDetails || "",
tasks: taskEntries,
completed: false,
});
await this.saveTasks();
return {
requestId,
message: "Request registered successfully.\n" + this.formatTaskProgressTable(requestId),
};
}
public async getNextTask(requestId: string) {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) {
throw new Error("Request not found");
}
const nextTask = request.tasks.find((t) => !t.done);
const allTasksDone = request.tasks.every((t) => t.approved);
await this.saveTasks();
if (allTasksDone) {
return {
message:
"All tasks are done! Awaiting request completion approval.\n" +
this.formatTaskProgressTable(requestId),
taskId: null,
allTasksDone: true,
};
}
if (!nextTask) {
return {
message:
"All tasks are done but some need approval.\n" +
this.formatTaskProgressTable(requestId),
taskId: null,
allTasksDone: false,
};
}
return {
message:
`Next task: ${nextTask.title}\n${nextTask.description}\n` +
this.formatTaskProgressTable(requestId),
taskId: nextTask.id,
allTasksDone: false,
};
}
public async markTaskDone(
requestId: string,
taskId: string,
completedDetails?: string
) {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) {
throw new Error("Request not found");
}
const task = request.tasks.find((t) => t.id === taskId);
if (!task) {
throw new Error("Task not found");
}
task.done = true;
if (completedDetails) {
task.completedDetails = completedDetails;
}
await this.saveTasks();
return {
message:
"Task marked as done. Awaiting approval.\n" +
this.formatTaskProgressTable(requestId),
};
}
public async approveTaskCompletion(requestId: string, taskId: string) {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) {
throw new Error("Request not found");
}
const task = request.tasks.find((t) => t.id === taskId);
if (!task) {
throw new Error("Task not found");
}
task.approved = true;
await this.saveTasks();
return {
message:
"Task completion approved.\n" + this.formatTaskProgressTable(requestId),
};
}
public async approveRequestCompletion(requestId: string) {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) {
throw new Error("Request not found");
}
if (!request.tasks.every((t) => t.approved)) {
throw new Error("Not all tasks are approved yet");
}
request.completed = true;
await this.saveTasks();
return {
message:
"Request completion approved. All done!\n" +
this.formatTaskProgressTable(requestId),
};
}
public async openTaskDetails(taskId: string) {
for (const request of this.data.requests) {
const task = request.tasks.find((t) => t.id === taskId);
if (task) {
return {
message: `Task Details:
ID: ${task.id}
Title: ${task.title}
Description: ${task.description}
Status: ${task.approved ? "Approved" : task.done ? "Done" : "Pending"}
${
task.completedDetails
? `Completion Details: ${task.completedDetails}`
: ""
}`,
};
}
}
throw new Error("Task not found");
}
public async listRequests() {
return {
message: this.formatRequestsList(),
};
}
public async addTasksToRequest(
requestId: string,
tasks: { title: string; description: string }[]
) {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) {
throw new Error("Request not found");
}
const newTasks: Task[] = tasks.map((t) => {
this.taskCounter++;
return {
id: `task-${this.taskCounter}`,
title: t.title,
description: t.description,
done: false,
approved: false,
completedDetails: "",
};
});
request.tasks.push(...newTasks);
request.completed = false; // Reset completion since new tasks were added
await this.saveTasks();
return {
message:
"Tasks added to request.\n" + this.formatTaskProgressTable(requestId),
};
}
public async updateTask(
requestId: string,
taskId: string,
updates: { title?: string; description?: string }
) {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) {
throw new Error("Request not found");
}
const task = request.tasks.find((t) => t.id === taskId);
if (!task) {
throw new Error("Task not found");
}
if (task.done || task.approved) {
throw new Error("Cannot update completed or approved tasks");
}
if (updates.title) {
task.title = updates.title;
}
if (updates.description) {
task.description = updates.description;
}
await this.saveTasks();
return {
message:
"Task updated successfully.\n" + this.formatTaskProgressTable(requestId),
};
}
public async deleteTask(requestId: string, taskId: string) {
const request = this.data.requests.find((r) => r.requestId === requestId);
if (!request) {
throw new Error("Request not found");
}
const taskIndex = request.tasks.findIndex((t) => t.id === taskId);
if (taskIndex === -1) {
throw new Error("Task not found");
}
const task = request.tasks[taskIndex];
if (task.done || task.approved) {
throw new Error("Cannot delete completed or approved tasks");
}
request.tasks.splice(taskIndex, 1);
await this.saveTasks();
return {
message:
"Task deleted successfully.\n" + this.formatTaskProgressTable(requestId),
};
}
}