refactor(mcp): Remove unused executeMCPToolAction utility

The  function aimed to abstract the common flow within MCP tool  methods (logging, calling direct function, handling result).

However, the established pattern (e.g., in ) involves the  method directly calling the  function (which handles its own caching via ) and then passing the result to . This pattern is clear, functional, and leverages the core utilities effectively.

Removing the unused  simplifies , eliminates a redundant abstraction layer, and clarifies the standard implementation pattern for MCP tools.
This commit is contained in:
Eyal Toledano
2025-03-30 23:37:24 -04:00
parent a56a3628b3
commit bc9707f813
6 changed files with 107 additions and 91 deletions

View File

@@ -18,6 +18,7 @@ const __dirname = dirname(__filename);
// Import Task Master modules // Import Task Master modules
import { import {
listTasks, listTasks,
parsePRD,
// We'll import more functions as we continue implementation // We'll import more functions as we continue implementation
} from '../../../scripts/modules/task-manager.js'; } from '../../../scripts/modules/task-manager.js';
@@ -157,11 +158,72 @@ export async function getCacheStatsDirect(args, log) {
} }
} }
/**
* Direct function wrapper for parsePRD with error handling.
*
* @param {Object} args - Command arguments (input file path, output path, numTasks).
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }.
*/
export async function parsePRDDirect(args, log) {
try {
// Normalize paths based on projectRoot
const projectRoot = args.projectRoot || process.cwd();
// Get the input file path (PRD file)
const inputPath = args.input
? path.resolve(projectRoot, args.input)
: path.resolve(projectRoot, 'sample-prd.txt');
log.info(`Using PRD file: ${inputPath}`);
// Determine tasks output path
let tasksPath;
try {
// Try to find existing tasks.json first
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
// If not found, use default path
tasksPath = path.resolve(projectRoot, 'tasks', 'tasks.json');
log.info(`No existing tasks.json found, will create at: ${tasksPath}`);
}
// Get number of tasks to generate
const numTasks = args.numTasks ? parseInt(args.numTasks, 10) : undefined;
log.info(`Parsing PRD file ${inputPath} to generate tasks in ${tasksPath}`);
// Call the core parsePRD function
await parsePRD(inputPath, tasksPath, numTasks);
return {
success: true,
data: {
message: `Successfully parsed PRD and generated tasks in ${tasksPath}`,
inputFile: inputPath,
outputFile: tasksPath
},
fromCache: false // PRD parsing is never cached
};
} catch (error) {
log.error(`Error parsing PRD: ${error.message}`);
return {
success: false,
error: {
code: 'PARSE_PRD_ERROR',
message: error.message
},
fromCache: false
};
}
}
/** /**
* Maps Task Master functions to their direct implementation * Maps Task Master functions to their direct implementation
*/ */
export const directFunctions = { export const directFunctions = {
list: listTasksDirect, list: listTasksDirect,
cacheStats: getCacheStatsDirect, cacheStats: getCacheStatsDirect,
parsePRD: parsePRDDirect,
// Add more functions as we implement them // Add more functions as we implement them
}; };

View File

@@ -10,6 +10,7 @@ import { registerSetTaskStatusTool } from "./setTaskStatus.js";
import { registerExpandTaskTool } from "./expandTask.js"; import { registerExpandTaskTool } from "./expandTask.js";
import { registerNextTaskTool } from "./nextTask.js"; import { registerNextTaskTool } from "./nextTask.js";
import { registerAddTaskTool } from "./addTask.js"; import { registerAddTaskTool } from "./addTask.js";
import { registerParsePRDTool } from "./parsePRD.js";
/** /**
* Register all Task Master tools with the MCP server * Register all Task Master tools with the MCP server
@@ -22,6 +23,7 @@ export function registerTaskMasterTools(server) {
registerExpandTaskTool(server); registerExpandTaskTool(server);
registerNextTaskTool(server); registerNextTaskTool(server);
registerAddTaskTool(server); registerAddTaskTool(server);
registerParsePRDTool(server);
} }
export default { export default {

View File

@@ -0,0 +1,41 @@
/**
* tools/parsePRD.js
* Tool to parse PRD documents and generate Task Master tasks
*/
import { z } from "zod";
import { executeMCPToolAction } from "./utils.js";
import { parsePRDDirect } from "../core/task-master-core.js";
/**
* Register the parsePRD tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerParsePRDTool(server) {
server.addTool({
name: "parsePRD",
description: "Parse a PRD document and generate Task Master tasks",
parameters: z.object({
input: z
.string()
.optional()
.describe("Path to the PRD text file (default: sample-prd.txt)"),
numTasks: z
.number()
.optional()
.describe("Number of tasks to generate"),
projectRoot: z
.string()
.optional()
.describe("Root directory of the project (default: current working directory)")
}),
execute: async (args, { log }) => {
return executeMCPToolAction({
actionFn: parsePRDDirect,
args,
log,
actionName: "Parse PRD and generate tasks"
});
},
});
}

View File

@@ -180,95 +180,6 @@ export async function getCachedOrExecute({ cacheKey, actionFn, log }) {
}; };
} }
/**
* Executes a Task Master tool action with standardized error handling, logging, and response formatting.
* Integrates caching logic via getCachedOrExecute if a cacheKeyGenerator is provided.
*
* @param {Object} options - Options for executing the tool action
* @param {Function} options.actionFn - The core action function (e.g., listTasksDirect) to execute. Should return {success, data, error}.
* @param {Object} options.args - Arguments for the action, passed to actionFn and cacheKeyGenerator.
* @param {Object} options.log - Logger object from FastMCP.
* @param {string} options.actionName - Name of the action for logging purposes.
* @param {Function} [options.cacheKeyGenerator] - Optional function to generate a cache key based on args. If provided, caching is enabled.
* @param {Function} [options.processResult=processMCPResponseData] - Optional function to process the result data before returning.
* @returns {Promise<Object>} - Standardized response for FastMCP.
*/
export async function executeMCPToolAction({
actionFn,
args,
log,
actionName,
cacheKeyGenerator, // Note: We decided not to use this for listTasks for now
processResult = processMCPResponseData
}) {
try {
// Log the action start
log.info(`${actionName} with args: ${JSON.stringify(args)}`);
// Normalize project root path - common to almost all tools
const projectRootRaw = args.projectRoot || process.cwd();
const projectRoot = path.isAbsolute(projectRootRaw)
? projectRootRaw
: path.resolve(process.cwd(), projectRootRaw);
log.info(`Using project root: ${projectRoot}`);
const executionArgs = { ...args, projectRoot };
let result;
const cacheKey = cacheKeyGenerator ? cacheKeyGenerator(executionArgs) : null;
if (cacheKey) {
// Use caching utility
log.info(`Caching enabled for ${actionName} with key: ${cacheKey}`);
const cacheWrappedAction = async () => await actionFn(executionArgs, log);
result = await getCachedOrExecute({
cacheKey,
actionFn: cacheWrappedAction,
log
});
} else {
// Execute directly without caching
log.info(`Caching disabled for ${actionName}. Executing directly.`);
// We need to ensure the result from actionFn has a fromCache field
// Let's assume actionFn now consistently returns { success, data/error, fromCache }
// The current listTasksDirect does this if it calls getCachedOrExecute internally.
result = await actionFn(executionArgs, log);
// If the action function itself doesn't determine caching (like our original listTasksDirect refactor attempt),
// we'd set it here:
// result.fromCache = false;
}
// Handle error case
if (!result.success) {
const errorMsg = result.error?.message || `Unknown error during ${actionName.toLowerCase()}`;
// Include fromCache in error logs too, might be useful
log.error(`Error during ${actionName.toLowerCase()}: ${errorMsg}. From cache: ${result.fromCache}`);
return createErrorResponse(errorMsg);
}
// Log success
log.info(`Successfully completed ${actionName.toLowerCase()}. From cache: ${result.fromCache}`);
// Process the result data if needed
const processedData = processResult ? processResult(result.data) : result.data;
// Create a new object that includes both the processed data and the fromCache flag
const responsePayload = {
fromCache: result.fromCache, // Include the flag here
data: processedData // Embed the actual data under a 'data' key
};
// Pass this combined payload to createContentResponse
return createContentResponse(responsePayload);
} catch (error) {
// Handle unexpected errors during the execution wrapper itself
log.error(`Unexpected error during ${actionName.toLowerCase()} execution wrapper: ${error.message}`);
console.error(error.stack); // Log stack for debugging wrapper errors
return createErrorResponse(`Internal server error during ${actionName.toLowerCase()}: ${error.message}`);
}
}
/** /**
* Recursively removes specified fields from task objects, whether single or in an array. * Recursively removes specified fields from task objects, whether single or in an array.
* Handles common data structures returned by task commands. * Handles common data structures returned by task commands.

View File

@@ -346,7 +346,7 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false, outputFormat =
### Details: ### Details:
1. Research and implement SSE protocol for the MCP server\n2. Create dedicated SSE endpoints for event streaming\n3. Implement event emitter pattern for internal event management\n4. Add support for different event types (task status, logs, errors)\n5. Implement client connection management with proper keep-alive handling\n6. Add filtering capabilities to allow subscribing to specific event types\n7. Create in-memory event buffer for clients reconnecting\n8. Document SSE endpoint usage and client implementation examples\n9. Add robust error handling for dropped connections\n10. Implement rate limiting and backpressure mechanisms\n11. Add authentication for SSE connections 1. Research and implement SSE protocol for the MCP server\n2. Create dedicated SSE endpoints for event streaming\n3. Implement event emitter pattern for internal event management\n4. Add support for different event types (task status, logs, errors)\n5. Implement client connection management with proper keep-alive handling\n6. Add filtering capabilities to allow subscribing to specific event types\n7. Create in-memory event buffer for clients reconnecting\n8. Document SSE endpoint usage and client implementation examples\n9. Add robust error handling for dropped connections\n10. Implement rate limiting and backpressure mechanisms\n11. Add authentication for SSE connections
## 16. Implement parse-prd MCP command [pending] ## 16. Implement parse-prd MCP command [in-progress]
### Dependencies: None ### Dependencies: None
### Description: Create direct function wrapper and MCP tool for parsing PRD documents to generate tasks. ### Description: Create direct function wrapper and MCP tool for parsing PRD documents to generate tasks.
### Details: ### Details:

View File

@@ -1492,7 +1492,7 @@
"title": "Implement parse-prd MCP command", "title": "Implement parse-prd MCP command",
"description": "Create direct function wrapper and MCP tool for parsing PRD documents to generate tasks.", "description": "Create direct function wrapper and MCP tool for parsing PRD documents to generate tasks.",
"details": "Following MCP implementation standards:\\n\\n1. Create parsePRDDirect function in task-master-core.js:\\n - Import parsePRD from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: input file, output path, numTasks\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create parse-prd.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import parsePRDDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerParsePRDTool(server) with server.addTool\\n - Use executeMCPToolAction in execute method\\n\\n3. Register in tools/index.js\\n\\n4. Add to .cursor/mcp.json with appropriate schema\\n\\n5. Write tests following testing guidelines:\\n - Unit test for parsePRDDirect\\n - Integration test for MCP tool", "details": "Following MCP implementation standards:\\n\\n1. Create parsePRDDirect function in task-master-core.js:\\n - Import parsePRD from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: input file, output path, numTasks\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create parse-prd.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import parsePRDDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerParsePRDTool(server) with server.addTool\\n - Use executeMCPToolAction in execute method\\n\\n3. Register in tools/index.js\\n\\n4. Add to .cursor/mcp.json with appropriate schema\\n\\n5. Write tests following testing guidelines:\\n - Unit test for parsePRDDirect\\n - Integration test for MCP tool",
"status": "pending", "status": "in-progress",
"dependencies": [], "dependencies": [],
"parentTaskId": 23 "parentTaskId": 23
}, },