@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
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"));
// 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