chore: changeset + update rules.

This commit is contained in:
Eyal Toledano
2025-04-03 04:09:27 -04:00
parent 68f0bfc811
commit a908109cf7
54 changed files with 1462 additions and 316 deletions

View File

@@ -7,6 +7,7 @@ import { z } from "zod";
import {
handleApiResult,
createErrorResponse,
createContentResponse,
getProjectRootFromSession
} from "./utils.js";
import { addTaskDirect } from "../core/task-master-core.js";
@@ -14,11 +15,12 @@ import { addTaskDirect } from "../core/task-master-core.js";
/**
* Register the add-task tool with the MCP server
* @param {Object} server - FastMCP server instance
* @param {AsyncOperationManager} asyncManager - The async operation manager instance.
*/
export function registerAddTaskTool(server) {
export function registerAddTaskTool(server, asyncManager) {
server.addTool({
name: "add_task",
description: "Add a new task using AI",
description: "Starts adding a new task using AI in the background.",
parameters: z.object({
prompt: z.string().describe("Description of the task to add"),
dependencies: z.string().optional().describe("Comma-separated list of task IDs this task depends on"),
@@ -26,29 +28,38 @@ export function registerAddTaskTool(server) {
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z.string().optional().describe("Root directory of the project (default: current working directory)")
}),
execute: async (args, { log, reportProgress, session }) => {
execute: async (args, context) => {
const { log, reportProgress, session } = context;
try {
log.info(`MCP add_task called with prompt: "${args.prompt}"`);
log.info(`MCP add_task request received with prompt: \"${args.prompt}\"`);
// Get project root using the utility function
if (!args.prompt) {
return createErrorResponse("Prompt is required for add_task.", "VALIDATION_ERROR");
}
let rootFolder = getProjectRootFromSession(session, log);
// Fallback to args.projectRoot if session didn't provide one
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Call the direct function with the resolved rootFolder
const result = await addTaskDirect({
projectRoot: rootFolder, // Pass the resolved root
const directArgs = {
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
};
const operationId = asyncManager.addOperation(addTaskDirect, directArgs, context);
return handleApiResult(result, log);
log.info(`Started background operation for add_task. Operation ID: ${operationId}`);
return createContentResponse({
message: "Add task operation started successfully.",
operationId: operationId
});
} catch (error) {
log.error(`Error in add_task MCP tool: ${error.message}`);
return createErrorResponse(error.message, "ADD_TASK_ERROR");
log.error(`Error initiating add_task operation: ${error.message}`, { stack: error.stack });
return createErrorResponse(`Failed to start add task operation: ${error.message}`, "ADD_TASK_INIT_ERROR");
}
}
});

View File

@@ -30,7 +30,7 @@ export function registerAnalyzeTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -42,9 +42,9 @@ export function registerAnalyzeTool(server) {
const result = await analyzeTaskComplexityDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Task complexity analysis complete: ${result.data.message}`);

View File

@@ -26,7 +26,7 @@ export function registerComplexityReportTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Getting complexity report with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -38,9 +38,9 @@ export function registerComplexityReportTool(server) {
const result = await complexityReportDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`);

View File

@@ -30,7 +30,7 @@ export function registerExpandAllTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -42,19 +42,19 @@ export function registerExpandAllTool(server) {
const result = await expandAllTasksDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`All tasks expanded successfully: ${result.data.message}`);
log.info(`Successfully expanded all tasks: ${result.data.message}`);
} else {
log.error(`Failed to expand tasks: ${result.error.message}`);
log.error(`Failed to expand all tasks: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error expanding tasks');
return handleApiResult(result, log, 'Error expanding all tasks');
} catch (error) {
log.error(`Error in expandAll tool: ${error.message}`);
log.error(`Error in expand-all tool: ${error.message}`);
return createErrorResponse(error.message);
}
},

View File

@@ -36,7 +36,7 @@ export function registerExpandTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Expanding task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -48,20 +48,20 @@ export function registerExpandTaskTool(server) {
const result = await expandTaskDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully expanded task ID: ${args.id} with ${result.data.subtasksAdded} new subtasks${result.data.hasExistingSubtasks ? ' (appended to existing subtasks)' : ''}`);
log.info(`Successfully expanded task with ID ${args.id}`);
} else {
log.error(`Failed to expand task: ${result.error.message}`);
log.error(`Failed to expand task: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error expanding task');
} catch (error) {
log.error(`Error in expand-task tool: ${error.message}`);
return createErrorResponse(`Failed to expand task: ${error.message}`);
log.error(`Error in expand task tool: ${error.message}`);
return createErrorResponse(error.message);
}
},
});

View File

@@ -32,7 +32,7 @@ export function registerGenerateTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -44,14 +44,14 @@ export function registerGenerateTool(server) {
const result = await generateTaskFilesDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully generated task files: ${result.data.message}`);
} else {
log.error(`Failed to generate task files: ${result.error.message}`);
log.error(`Failed to generate task files: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error generating task files');

View File

@@ -0,0 +1,42 @@
// mcp-server/src/tools/get-operation-status.js
import { z } from 'zod';
import { createErrorResponse, createContentResponse } from './utils.js'; // Assuming these utils exist
/**
* Register the get_operation_status tool.
* @param {FastMCP} server - FastMCP server instance.
* @param {AsyncOperationManager} asyncManager - The async operation manager.
*/
export function registerGetOperationStatusTool(server, asyncManager) {
server.addTool({
name: 'get_operation_status',
description: 'Retrieves the status and result/error of a background operation.',
parameters: z.object({
operationId: z.string().describe('The ID of the operation to check.'),
}),
execute: async (args, { log }) => {
try {
const { operationId } = args;
log.info(`Checking status for operation ID: ${operationId}`);
const status = asyncManager.getStatus(operationId);
// Status will now always return an object, but it might have status='not_found'
if (status.status === 'not_found') {
log.warn(`Operation ID not found: ${operationId}`);
return createErrorResponse(
status.error?.message || `Operation ID not found: ${operationId}`,
status.error?.code || 'OPERATION_NOT_FOUND'
);
}
log.info(`Status for ${operationId}: ${status.status}`);
return createContentResponse(status);
} catch (error) {
log.error(`Error in get_operation_status tool: ${error.message}`, { stack: error.stack });
return createErrorResponse(`Failed to get operation status: ${error.message}`, 'GET_STATUS_ERROR');
}
},
});
}

View File

@@ -36,7 +36,7 @@ export function registerListTasksTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Getting tasks with filters: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -48,9 +48,9 @@ export function registerListTasksTool(server) {
const result = await listTasksDirect({
projectRoot: rootFolder,
...args
}, log);
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
log.info(`Retrieved ${result.success ? (result.data?.tasks?.length || 0) : 0} tasks${result.fromCache ? ' (from cache)' : ''}`);
return handleApiResult(result, log, 'Error getting tasks');

View File

@@ -27,12 +27,15 @@ import { registerComplexityReportTool } from "./complexity-report.js";
import { registerAddDependencyTool } from "./add-dependency.js";
import { registerRemoveTaskTool } from './remove-task.js';
import { registerInitializeProjectTool } from './initialize-project.js';
import { asyncOperationManager } from '../core/utils/async-manager.js';
import { registerGetOperationStatusTool } from './get-operation-status.js';
/**
* Register all Task Master tools with the MCP server
* @param {Object} server - FastMCP server instance
* @param {asyncOperationManager} asyncManager - The async operation manager instance
*/
export function registerTaskMasterTools(server) {
export function registerTaskMasterTools(server, asyncManager) {
try {
// Register each tool
registerListTasksTool(server);
@@ -45,7 +48,7 @@ export function registerTaskMasterTools(server) {
registerShowTaskTool(server);
registerNextTaskTool(server);
registerExpandTaskTool(server);
registerAddTaskTool(server);
registerAddTaskTool(server, asyncManager);
registerAddSubtaskTool(server);
registerRemoveSubtaskTool(server);
registerAnalyzeTool(server);
@@ -58,10 +61,13 @@ export function registerTaskMasterTools(server) {
registerAddDependencyTool(server);
registerRemoveTaskTool(server);
registerInitializeProjectTool(server);
registerGetOperationStatusTool(server, asyncManager);
} catch (error) {
logger.error(`Error registering Task Master tools: ${error.message}`);
throw error;
}
logger.info('Registered Task Master MCP tools');
}
export default {

View File

@@ -31,7 +31,7 @@ export function registerNextTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -43,24 +43,20 @@ export function registerNextTaskTool(server) {
const result = await nextTaskDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
if (result.data.nextTask) {
log.info(`Successfully found next task ID: ${result.data.nextTask.id}${result.fromCache ? ' (from cache)' : ''}`);
} else {
log.info(`No eligible next task found${result.fromCache ? ' (from cache)' : ''}`);
}
log.info(`Successfully found next task: ${result.data?.task?.id || 'No available tasks'}`);
} else {
log.error(`Failed to find next task: ${result.error.message}`);
log.error(`Failed to find next task: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error finding next task');
} catch (error) {
log.error(`Error in next-task tool: ${error.message}`);
return createErrorResponse(`Failed to find next task: ${error.message}`);
log.error(`Error in nextTask tool: ${error.message}`);
return createErrorResponse(error.message);
}
},
});

View File

@@ -33,7 +33,7 @@ export function registerParsePRDTool(server) {
}),
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Parsing PRD document with args: ${JSON.stringify(args)}`);
log.info(`Parsing PRD with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log);
@@ -45,19 +45,19 @@ export function registerParsePRDTool(server) {
const result = await parsePRDDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully generated ${result.data?.taskCount || 0} tasks from PRD at ${result.data?.outputPath}`);
log.info(`Successfully parsed PRD: ${result.data.message}`);
} else {
log.error(`Failed to parse PRD: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error parsing PRD document');
return handleApiResult(result, log, 'Error parsing PRD');
} catch (error) {
log.error(`Error in parse_prd tool: ${error.message}`);
log.error(`Error in parse-prd tool: ${error.message}`);
return createErrorResponse(error.message);
}
},

View File

@@ -28,7 +28,7 @@ export function registerRemoveDependencyTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -40,9 +40,9 @@ export function registerRemoveDependencyTool(server) {
const result = await removeDependencyDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully removed dependency: ${result.data.message}`);

View File

@@ -29,7 +29,7 @@ export function registerRemoveSubtaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -41,9 +41,9 @@ export function registerRemoveSubtaskTool(server) {
const result = await removeSubtaskDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Subtask removed successfully: ${result.data.message}`);

View File

@@ -37,7 +37,7 @@ export function registerSetTaskStatusTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -49,9 +49,9 @@ export function registerSetTaskStatusTool(server) {
const result = await setTaskStatusDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated status for task(s) ${args.id} to "${args.status}": ${result.data.message}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateSubtaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateSubtaskTool(server) {
const result = await updateSubtaskByIdDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated subtask with ID ${args.id}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Updating task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateTaskTool(server) {
const result = await updateTaskByIdDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated task with ID ${args.id}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateTool(server) {
const result = await updateTasksDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated tasks from ID ${args.from}: ${result.data.message}`);