@notbnull/mcp-cursor-taskmaster
Version:
A project-scoped MCP server requiring explicit --project-dir configuration for task management, memory retrieval, and agentic workflows
830 lines • 30.7 kB
JavaScript
;
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"));
const os_1 = __importDefault(require("os"));
// Function to get project directory from multiple sources
function getProjectDirectory() {
// 1. Try environment variables that Cursor might set
const envVars = [
"CURSOR_PROJECT_DIR",
"CURSOR_WORKSPACE",
"PROJECT_DIR",
"WORKSPACE_DIR",
"PWD",
"INIT_CWD",
];
for (const envVar of envVars) {
const dir = process.env[envVar];
if (dir && fs_1.default.existsSync(dir) && dir !== "/" && dir !== "\\") {
console.error(`Cursor Taskmaster: Found project directory from ${envVar}: ${dir}`);
return path_1.default.resolve(dir);
}
}
// 2. Try command line arguments
const args = process.argv;
const projectDirIndex = args.findIndex((arg) => arg === "--project-dir");
if (projectDirIndex !== -1 && projectDirIndex + 1 < args.length) {
const cmdProjectDir = args[projectDirIndex + 1];
if (fs_1.default.existsSync(cmdProjectDir)) {
console.error(`Cursor Taskmaster: Found project directory from command line: ${cmdProjectDir}`);
return path_1.default.resolve(cmdProjectDir);
}
}
// 3. Try to find a project directory by looking for common project files
let currentDir = process.cwd();
const projectIndicators = [
".git",
"package.json",
".cursor",
"tsconfig.json",
"pyproject.toml",
"Cargo.toml",
];
// Walk up the directory tree looking for project indicators
while (currentDir !== path_1.default.dirname(currentDir)) {
for (const indicator of projectIndicators) {
if (fs_1.default.existsSync(path_1.default.join(currentDir, indicator))) {
console.error(`Cursor Taskmaster: Found project directory by ${indicator}: ${currentDir}`);
return currentDir;
}
}
currentDir = path_1.default.dirname(currentDir);
}
// 4. Last resort: use home directory fallback
const fallbackDir = path_1.default.join(os_1.default.homedir(), ".cursor-taskmaster-projects", "default");
console.error(`Cursor Taskmaster: Warning - Could not detect project directory, using fallback: ${fallbackDir}`);
return fallbackDir;
}
// Function to ensure stores are initialized for the current project
async function ensureInitialized(toolProjectDir) {
let projectDir = toolProjectDir;
// If no project directory provided to tool, try to detect it
if (!projectDir) {
projectDir = getProjectDirectory();
}
await initializeStoresForProject(projectDir);
}
// 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;
// Configuration
const ENABLE_APPROVAL_UI = process.env.CURSOR_TASKMASTER_APPROVAL_UI !== "false";
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;
memoryStore = new memory_store_js_1.MemoryStore(projectDir);
await memoryStore.initialize();
// Try to initialize VectorStore, but make it optional
try {
vectorStore = new vector_store_js_1.VectorStore(path_1.default.join(projectDir, ".taskmaster"));
await vectorStore.initialize();
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;
}
taskStore = new task_store_js_1.TaskStore(projectDir);
await taskStore.initialize();
workflowEngine = new workflow_engine_js_1.WorkflowEngine(taskStore, memoryStore);
console.error(`Cursor Taskmaster: Initialized successfully for project: ${projectDir}`);
}
catch (error) {
console.error(`Cursor Taskmaster: Failed to initialize:`, error);
throw error;
}
}
// 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();
// 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 {
const transport = new stdio_js_1.StdioServerTransport();
await server.connect(transport);
console.error("Cursor Taskmaster MCP Server started (project-aware initialization on first tool use)");
}
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