UNPKG

@notbnull/mcp-cursor-taskmaster

Version:

A project-scoped MCP server requiring explicit --project-dir configuration for task management, memory retrieval, and agentic workflows

827 lines 31.2 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const zod_1 = require("zod"); const memory_store_js_1 = require("./memory-store.js"); const vector_store_js_1 = require("./vector-store.js"); const task_store_js_1 = require("./task-store.js"); const workflow_engine_js_1 = require("./workflow-engine.js"); const approval_ui_js_1 = require("./approval-ui.js"); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); // Function to get project directory from command line arguments or environment function getProjectDirectory() { const args = process.argv; // First try command line arguments const projectDirIndex = args.findIndex((arg) => arg === "--project-dir"); if (projectDirIndex !== -1 && projectDirIndex + 1 < args.length) { const projectDir = args[projectDirIndex + 1]; if (fs_1.default.existsSync(projectDir)) { const resolvedDir = path_1.default.resolve(projectDir); console.error(`Cursor Taskmaster: Using project directory from args: ${resolvedDir}`); return resolvedDir; } } // Try environment variable as fallback if (process.env.PROJECT_DIR && fs_1.default.existsSync(process.env.PROJECT_DIR)) { const resolvedDir = path_1.default.resolve(process.env.PROJECT_DIR); console.error(`Cursor Taskmaster: Using project directory from env: ${resolvedDir}`); return resolvedDir; } // Use current working directory as last resort const cwd = process.cwd(); console.error(`Cursor Taskmaster: Using current working directory: ${cwd}`); return cwd; } // Function to check if approval UI is enabled via command line arguments function isApprovalUIEnabled() { const args = process.argv; return (args.includes("--enable-approval-ui") || process.env.ENABLE_APPROVAL_UI === "true"); } // Function to ensure stores are initialized for the current project async function ensureInitialized() { if (initializationPromise) { return initializationPromise; } const projectDir = getProjectDirectory(); initializationPromise = initializeStoresForProject(projectDir); return initializationPromise; } // Schema definitions for task management const CreateTaskSchema = zod_1.z.object({ title: zod_1.z.string().describe("Title of the task"), description: zod_1.z.string().describe("Detailed description of the task"), priority: zod_1.z.enum(["low", "medium", "high", "critical"]).default("medium"), parentId: zod_1.z .string() .optional() .describe("ID of parent task for hierarchical structure"), dueDate: zod_1.z.string().optional().describe("Due date in ISO format"), estimatedTime: zod_1.z.number().optional().describe("Estimated time in minutes"), metadata: zod_1.z .record(zod_1.z.any()) .optional() .describe("Additional metadata for the task"), }); const UpdateTaskSchema = zod_1.z.object({ id: zod_1.z.string().describe("Task ID to update"), title: zod_1.z.string().optional(), description: zod_1.z.string().optional(), status: zod_1.z .enum([ "pending", "in_progress", "completed", "failed", "awaiting_approval", ]) .optional(), priority: zod_1.z.enum(["low", "medium", "high", "critical"]).optional(), dueDate: zod_1.z.string().optional(), estimatedTime: zod_1.z.number().optional(), actualTime: zod_1.z.number().optional(), metadata: zod_1.z.record(zod_1.z.any()).optional(), }); const GetTaskSchema = zod_1.z.object({ id: zod_1.z.string().describe("Task ID to retrieve"), }); const DeleteTaskSchema = zod_1.z.object({ id: zod_1.z.string().describe("Task ID to delete"), }); const GetTaskHierarchySchema = zod_1.z.object({ rootTaskId: zod_1.z .string() .optional() .describe("Root task ID to get hierarchy for (if not provided, returns all root tasks)"), }); // Memory retrieval schemas const StoreMemorySchema = zod_1.z.object({ key: zod_1.z.string().describe("Unique identifier for the memory"), content: zod_1.z.string().describe("The content to store"), metadata: zod_1.z .record(zod_1.z.any()) .optional() .describe("Optional metadata to associate with the memory"), }); const RetrieveMemorySchema = zod_1.z.object({ query: zod_1.z.string().describe("The search query to find relevant memories"), limit: zod_1.z .number() .min(1) .max(20) .default(5) .describe("Maximum number of results to return"), threshold: zod_1.z .number() .min(0) .max(1) .default(0.7) .describe("Minimum similarity threshold (0-1)"), }); // Workflow schemas const ParsePromptSchema = zod_1.z.object({ query: zod_1.z.string().describe("The user query to parse into tasks"), title: zod_1.z.string().optional().describe("Optional title for the prompt"), description: zod_1.z .string() .optional() .describe("Optional description for the prompt"), }); const ExecuteWorkflowSchema = zod_1.z.object({ promptId: zod_1.z.string().describe("The prompt ID to execute"), }); const ApproveTaskSchema = zod_1.z.object({ taskId: zod_1.z.string().describe("Task ID to approve"), userFeedback: zod_1.z.string().optional().describe("Optional user feedback"), }); const RejectTaskSchema = zod_1.z.object({ taskId: zod_1.z.string().describe("Task ID to reject"), userFeedback: zod_1.z.string().optional().describe("Optional user feedback"), modifications: zod_1.z .record(zod_1.z.any()) .optional() .describe("Optional modifications to apply to the task"), }); // Initialize server const server = new index_js_1.Server({ name: "cursor-taskmaster", version: "1.0.0", }, { capabilities: { tools: {}, }, }); // Project-aware stores - initialized lazily when first tool is used let currentProjectDir = null; let memoryStore = null; let vectorStore = null; let taskStore = null; let workflowEngine = null; let initializationPromise = null; // Configuration - using command line arguments instead of environment variables const ENABLE_APPROVAL_UI = isApprovalUIEnabled(); async function initializeStoresForProject(projectDir) { // If already initialized for this project, return if (currentProjectDir === projectDir && memoryStore && taskStore) { return; } console.error(`Cursor Taskmaster: Initializing for project: ${projectDir}`); try { currentProjectDir = projectDir; // Initialize core stores first (these are required) memoryStore = new memory_store_js_1.MemoryStore(projectDir); await memoryStore.initialize(); taskStore = new task_store_js_1.TaskStore(projectDir); await taskStore.initialize(); workflowEngine = new workflow_engine_js_1.WorkflowEngine(taskStore, memoryStore); console.error(`Cursor Taskmaster: Core stores initialized successfully for project: ${projectDir}`); // Initialize VectorStore in background (optional, non-blocking) initializeVectorStoreAsync(projectDir).catch((error) => { console.error(`Cursor Taskmaster: VectorStore initialization failed:`, error instanceof Error ? error.message : "Unknown error"); }); } catch (error) { console.error(`Cursor Taskmaster: Failed to initialize core stores:`, error); throw error; } } async function initializeVectorStoreAsync(projectDir) { try { console.error(`Cursor Taskmaster: Initializing VectorStore in background...`); vectorStore = new vector_store_js_1.VectorStore(path_1.default.join(projectDir, ".taskmaster")); // Add timeout to prevent hanging const timeoutPromise = new Promise((_, reject) => globalThis.setTimeout(() => reject(new Error("VectorStore initialization timeout")), 30000)); await Promise.race([vectorStore.initialize(), timeoutPromise]); console.error(`Cursor Taskmaster: VectorStore enabled (semantic search available)`); } catch (vectorError) { console.error(`Cursor Taskmaster: VectorStore failed, continuing without semantic search:`, vectorError instanceof Error ? vectorError.message : "Unknown error"); vectorStore = null; } } // Tool definitions const tools = [ // Task Management Tools { name: "createTask", description: "Create a new task in the project's task management system", inputSchema: { type: "object", properties: { title: { type: "string", description: "Title of the task" }, description: { type: "string", description: "Detailed description of the task", }, priority: { type: "string", enum: ["low", "medium", "high", "critical"], default: "medium", description: "Priority level of the task", }, parentId: { type: "string", description: "ID of parent task for hierarchical structure", }, dueDate: { type: "string", description: "Due date in ISO format" }, estimatedTime: { type: "number", description: "Estimated time in minutes", }, metadata: { type: "object", description: "Additional metadata for the task", additionalProperties: true, }, }, required: ["title", "description"], }, }, { name: "getTask", description: "Retrieve a specific task by ID", inputSchema: { type: "object", properties: { id: { type: "string", description: "Task ID to retrieve" }, }, required: ["id"], }, }, { name: "getAllTasks", description: "Retrieve all tasks in the project", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, }, { name: "updateTask", description: "Update an existing task", inputSchema: { type: "object", properties: { id: { type: "string", description: "Task ID to update" }, title: { type: "string" }, description: { type: "string" }, status: { type: "string", enum: [ "pending", "in_progress", "completed", "failed", "awaiting_approval", ], }, priority: { type: "string", enum: ["low", "medium", "high", "critical"], }, dueDate: { type: "string" }, estimatedTime: { type: "number" }, actualTime: { type: "number" }, metadata: { type: "object", additionalProperties: true }, }, required: ["id"], }, }, { name: "deleteTask", description: "Delete a task and all its subtasks", inputSchema: { type: "object", properties: { id: { type: "string", description: "Task ID to delete" }, }, required: ["id"], }, }, { name: "getTaskHierarchy", description: "Get hierarchical view of tasks", inputSchema: { type: "object", properties: { rootTaskId: { type: "string", description: "Root task ID to get hierarchy for (if not provided, returns all root tasks)", }, }, }, }, // Memory Retrieval Tools { name: "storeMemory", description: "Store information in the project's memory system with automatic vectorization", inputSchema: { type: "object", properties: { key: { type: "string", description: "Unique identifier for the memory", }, content: { type: "string", description: "The content to store" }, metadata: { type: "object", description: "Optional metadata", additionalProperties: true, }, }, required: ["key", "content"], }, }, { name: "retrieveMemory", description: "Retrieve relevant memories using semantic search", inputSchema: { type: "object", properties: { query: { type: "string", description: "The search query to find relevant memories", }, limit: { type: "number", minimum: 1, maximum: 20, default: 5, description: "Maximum number of results to return", }, threshold: { type: "number", minimum: 0, maximum: 1, default: 0.7, description: "Minimum similarity threshold (0-1)", }, }, required: ["query"], }, }, // Workflow Management Tools { name: "parsePrompt", description: "Parse a user query into a structured task workflow", inputSchema: { type: "object", properties: { query: { type: "string", description: "The user query to parse into tasks", }, title: { type: "string", description: "Optional title for the prompt" }, description: { type: "string", description: "Optional description for the prompt", }, }, required: ["query"], }, }, { name: "executeWorkflow", description: "Execute a workflow from a parsed prompt", inputSchema: { type: "object", properties: { promptId: { type: "string", description: "The prompt ID to execute" }, }, required: ["promptId"], }, }, { name: "approveTask", description: "Approve a task for execution", inputSchema: { type: "object", properties: { taskId: { type: "string", description: "Task ID to approve" }, userFeedback: { type: "string", description: "Optional user feedback" }, }, required: ["taskId"], }, }, { name: "rejectTask", description: "Reject a task and optionally provide modifications", inputSchema: { type: "object", properties: { taskId: { type: "string", description: "Task ID to reject" }, userFeedback: { type: "string", description: "Optional user feedback" }, modifications: { type: "object", description: "Optional modifications to apply to the task", additionalProperties: true, }, }, required: ["taskId"], }, }, { name: "resumeWorkflow", description: "Resume workflow execution from a checkpoint", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, }, { name: "getWorkflowState", description: "Get the current workflow execution state", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, }, // List Management Tools { name: "getAllPrompts", description: "Get all prompts in the project", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, }, { name: "getAllLists", description: "Get all task lists in the project", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, }, ]; // Handle tool listing server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { return { tools }; }); // Handle tool execution server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Initialize stores for current project on first tool use await ensureInitialized(); // Ensure required stores are available if (!taskStore || !memoryStore) { throw new Error("Core stores not initialized. Please check project directory configuration."); } // Task Management Tools if (name === "createTask") { const validatedArgs = CreateTaskSchema.parse(args); const task = await taskStore.createTask({ title: validatedArgs.title, description: validatedArgs.description, status: "pending", priority: validatedArgs.priority, parentId: validatedArgs.parentId, dueDate: validatedArgs.dueDate, estimatedTime: validatedArgs.estimatedTime, metadata: { ...validatedArgs.metadata, needsUserApproval: ENABLE_APPROVAL_UI, }, }); // If approval UI is enabled and task needs approval, request it if (ENABLE_APPROVAL_UI && !validatedArgs.parentId) { const approvalUI = (0, approval_ui_js_1.getApprovalUIServer)(); try { const response = await approvalUI.requestApproval({ taskId: task.id, task, }); if (!response.approved) { // Task was rejected await taskStore.updateTask(task.id, { status: "failed", metadata: { ...task.metadata, userRejected: true, userFeedback: response.userFeedback, }, }); return { content: [ { type: "text", text: `Task rejected by user${response.userFeedback ? `: ${response.userFeedback}` : ""}`, }, ], }; } // Apply any modifications if (response.modifications) { const updatedTask = await taskStore.updateTask(task.id, response.modifications); return { content: [ { type: "text", text: JSON.stringify(updatedTask || task, null, 2), }, ], }; } } catch (error) { console.error("Approval UI error:", error); // Continue without approval on error } } return { content: [ { type: "text", text: JSON.stringify(task, null, 2), }, ], }; } if (name === "getTask") { const validatedArgs = GetTaskSchema.parse(args); const task = await taskStore.getTask(validatedArgs.id); return { content: [ { type: "text", text: task ? JSON.stringify(task, null, 2) : `Task ${validatedArgs.id} not found`, }, ], }; } if (name === "getAllTasks") { const tasks = await taskStore.getAllTasks(); return { content: [ { type: "text", text: JSON.stringify(tasks, null, 2), }, ], }; } if (name === "updateTask") { const validatedArgs = UpdateTaskSchema.parse(args); const { id, ...updates } = validatedArgs; const task = await taskStore.updateTask(id, updates); return { content: [ { type: "text", text: task ? JSON.stringify(task, null, 2) : `Task ${id} not found`, }, ], }; } if (name === "deleteTask") { const validatedArgs = DeleteTaskSchema.parse(args); const success = await taskStore.deleteTask(validatedArgs.id); return { content: [ { type: "text", text: success ? `Task ${validatedArgs.id} deleted successfully` : `Task ${validatedArgs.id} not found`, }, ], }; } if (name === "getTaskHierarchy") { const validatedArgs = GetTaskHierarchySchema.parse(args); const hierarchy = await taskStore.getTaskHierarchy(validatedArgs.rootTaskId); return { content: [ { type: "text", text: JSON.stringify(hierarchy, null, 2), }, ], }; } // Memory Tools if (name === "storeMemory") { const validatedArgs = StoreMemorySchema.parse(args); const memory = await memoryStore.set(validatedArgs.key, validatedArgs.content, validatedArgs.metadata); // Only add to vector store if it's available if (vectorStore) { try { await vectorStore.add({ id: memory.id, content: validatedArgs.content, metadata: { ...validatedArgs.metadata, key: validatedArgs.key, timestamp: memory.timestamp, }, }); } catch (error) { console.error("Warning: Failed to add to vector store:", error instanceof Error ? error.message : "Unknown error"); } } return { content: [ { type: "text", text: `Successfully stored memory with key: ${validatedArgs.key}${vectorStore ? " (with semantic search)" : " (basic storage only)"}`, }, ], }; } if (name === "retrieveMemory") { const validatedArgs = RetrieveMemorySchema.parse(args); // If vector store is not available, fall back to basic text search if (!vectorStore) { const memories = await memoryStore.search(validatedArgs.query); const formattedResults = memories.map((memory) => ({ key: memory.key, content: memory.content, similarity: 1.0, // No similarity scoring for basic search metadata: memory.metadata, timestamp: memory.timestamp, })); return { content: [ { type: "text", text: JSON.stringify(formattedResults.slice(0, validatedArgs.limit), null, 2), }, ], }; } const searchResults = await vectorStore.search(validatedArgs.query, validatedArgs.limit, validatedArgs.threshold); const enrichedResults = await Promise.all(searchResults.map(async (result) => { const memory = await memoryStore.getById(result.id); return { ...result, memory }; })); const formattedResults = enrichedResults .filter((r) => r.memory !== null) .map((result) => ({ key: result.memory.key, content: result.memory.content, similarity: result.score, metadata: result.memory.metadata, timestamp: result.memory.timestamp, })); return { content: [ { type: "text", text: JSON.stringify(formattedResults, null, 2), }, ], }; } // Workflow Tools if (name === "parsePrompt") { const validatedArgs = ParsePromptSchema.parse(args); const result = await workflowEngine.parsePromptToTasks(validatedArgs.query, validatedArgs.title, validatedArgs.description); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } if (name === "executeWorkflow") { const validatedArgs = ExecuteWorkflowSchema.parse(args); const result = await workflowEngine.executeWorkflow(validatedArgs.promptId); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } if (name === "approveTask") { const validatedArgs = ApproveTaskSchema.parse(args); const success = await workflowEngine.approveTask(validatedArgs.taskId, validatedArgs.userFeedback); return { content: [ { type: "text", text: success ? `Task ${validatedArgs.taskId} approved` : `Task ${validatedArgs.taskId} not found`, }, ], }; } if (name === "rejectTask") { const validatedArgs = RejectTaskSchema.parse(args); const success = await workflowEngine.rejectTask(validatedArgs.taskId, validatedArgs.userFeedback, validatedArgs.modifications); return { content: [ { type: "text", text: success ? `Task ${validatedArgs.taskId} rejected` : `Task ${validatedArgs.taskId} not found`, }, ], }; } if (name === "resumeWorkflow") { const result = await workflowEngine.resumeWorkflow(); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } if (name === "getWorkflowState") { const state = await taskStore.getWorkflowState(); return { content: [ { type: "text", text: JSON.stringify(state, null, 2), }, ], }; } if (name === "getAllPrompts") { const prompts = await taskStore.getAllPrompts(); return { content: [ { type: "text", text: JSON.stringify(prompts, null, 2), }, ], }; } if (name === "getAllLists") { const lists = await taskStore.getAllLists(); return { content: [ { type: "text", text: JSON.stringify(lists, null, 2), }, ], }; } throw new Error(`Unknown tool: ${name}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return { content: [ { type: "text", text: `Error: ${errorMessage}`, }, ], isError: true, }; } }); // Main function async function main() { try { // Get and validate project directory early const projectDir = getProjectDirectory(); console.error(`Cursor Taskmaster: Server starting for project: ${projectDir}`); const transport = new stdio_js_1.StdioServerTransport(); await server.connect(transport); console.error("Cursor Taskmaster MCP Server started successfully"); console.error("- Core stores will initialize on first tool use"); console.error("- VectorStore (semantic search) will initialize in background"); console.error(`- Project directory: ${projectDir}`); console.error(`- Approval UI: ${ENABLE_APPROVAL_UI ? "enabled" : "disabled"}`); } catch (error) { console.error("Failed to start server:", error); process.exit(1); } } // Start the server main().catch((error) => { console.error("Fatal error:", error); process.exit(1); }); //# sourceMappingURL=index.js.map