docs: document MCP server naming conventions and implement set-status

- Update architecture.mdc with file/function naming standards for MCP server components

- Update mcp.mdc with detailed naming conventions section

- Update task 23 to include naming convention details

- Update changeset to capture documentation changes

- Rename MCP tool files to follow kebab-case convention

- Implement set-task-status MCP command
This commit is contained in:
Eyal Toledano
2025-03-31 03:35:14 -04:00
parent 6d01ae3d47
commit 9582c0a91f
13 changed files with 110 additions and 348 deletions

View File

@@ -7,3 +7,4 @@
- Implement update-subtask MCP command for appending information to specific subtasks
- Implement generate MCP command for creating individual task files from tasks.json
- Implement set-status MCP command for updating task status
- Document MCP server naming conventions in architecture.mdc and mcp.mdc files (file names use kebab-case, direct functions use camelCase with Direct suffix, tool registration functions use camelCase with Tool suffix, and MCP tool names use snake_case)

View File

@@ -119,6 +119,11 @@ alwaysApply: false
- `mcp-server/src/core/direct-functions/`: Directory containing individual files for each direct function wrapper (`*Direct`). These files contain the primary logic, including path resolution, core function calls, and caching.
- [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js): Acts as an import/export hub, collecting and exporting direct functions from the `direct-functions` directory and utility functions.
- `mcp-server/src/tools/utils.js`: Provides utilities like `handleApiResult`, `processMCPResponseData`, and `getCachedOrExecute`.
- **Naming Conventions**:
- **Files** use **kebab-case**: `list-tasks.js`, `set-task-status.js`, `parse-prd.js`
- **Direct Functions** use **camelCase** with `Direct` suffix: `listTasksDirect`, `setTaskStatusDirect`, `parsePRDDirect`
- **Tool Registration Functions** use **camelCase** with `Tool` suffix: `registerListTasksTool`, `registerSetTaskStatusTool`
- **MCP Tool Names** use **snake_case**: `list_tasks`, `set_task_status`, `parse_prd_document`
- **Data Flow and Module Dependencies**:

View File

@@ -46,50 +46,38 @@ The MCP server acts as a bridge between external tools (like Cursor) and the cor
Follow these steps to add MCP support for an existing Task Master command (see [`new_features.mdc`](mdc:.cursor/rules/new_features.mdc) for more detail):
1. **Ensure Core Logic Exists**: Verify the core functionality is implemented and exported from the relevant module in `scripts/modules/`.
2. **Create Direct Wrapper (`task-master-core.js`)**:
- Create an `async function yourCommandDirect(args, log)` in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js).
- Inside the wrapper:
- Import necessary core functions and utilities (`findTasksJsonPath`, `getCachedOrExecute` if caching).
2. **Create Direct Function File in `mcp-server/src/core/direct-functions/`**:
- Create a new file (e.g., `your-command.js`) in the `direct-functions` directory using **kebab-case** for file naming.
- Import necessary core functions from Task Master modules (e.g., `../../../../scripts/modules/task-manager.js`).
- Import utilities: `findTasksJsonPath` from `../utils/path-utils.js` and `getCachedOrExecute` from `../../tools/utils.js` if needed.
- Implement `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix:
- Parse `args` and determine necessary inputs (e.g., `tasksPath` via `findTasksJsonPath`).
- **If Caching**:
- Generate a unique `cacheKey` based on arguments defining the operation.
- Define an `async` function `coreActionFn` containing the actual call to the core logic, formatting its return as `{ success: true/false, data/error }`.
- Define an `async` function `coreActionFn` containing the call to the core logic.
- Call `const result = await getCachedOrExecute({ cacheKey, actionFn: coreActionFn, log });`.
- `return result;` (which includes the `fromCache` flag).
- **If Not Caching**:
- Directly call the core logic function within a try/catch block.
- Format the return as `{ success: true/false, data/error, fromCache: false }`.
- Export the wrapper function and add it to the `directFunctions` map.
3. **Create MCP Tool (`mcp-server/src/tools/`)**:
- Create a new file (e.g., `yourCommand.js`).
- Format the return as `{ success: true/false, data/error, fromCache: boolean }`.
- Export the wrapper function.
3. **Update `task-master-core.js` with Import/Export**:
- Import your direct function: `import { yourCommandDirect } from './direct-functions/your-command.js';`
- Re-export it in the exports section.
- Add it to the `directFunctions` map: `yourCommand: yourCommandDirect`.
4. **Create MCP Tool (`mcp-server/src/tools/`)**:
- Create a new file (e.g., `your-command.js`) using **kebab-case**.
- Import `z` for schema definition.
- Import `handleApiResult` from [`./utils.js`](mdc:mcp-server/src/tools/utils.js).
- Import `handleApiResult` from `./utils.js`.
- Import the `yourCommandDirect` wrapper function from `../core/task-master-core.js`.
- Implement `registerYourCommandTool(server)`:
- Call `server.addTool`.
- Define `name`, `description`, and `parameters` using `zod` (include optional `projectRoot`, `file` if needed).
- Define the `async execute(args, log)` function:
```javascript
async execute(args, log) {
try {
log.info(`Executing Your Command with args: ${JSON.stringify(args)}`);
// Call the direct function wrapper
const result = await yourCommandDirect(args, log);
// Let handleApiResult format the final MCP response
return handleApiResult(result, log, 'Error during Your Command');
// Optionally pass a custom processor to handleApiResult if default filtering isn't sufficient:
// return handleApiResult(result, log, 'Error...', customDataProcessor);
} catch (error) {
// Catch unexpected errors during the direct call itself
log.error(`Unexpected error in tool execute: ${error.message}`);
// Use createErrorResponse for unexpected errors
return createErrorResponse(`Tool execution failed: ${error.message}`);
}
}
```
4. **Register Tool**: Import and call `registerYourCommandTool` in [`mcp-server/src/tools/index.js`](mdc:mcp-server/src/tools/index.js).
5. **Update `mcp.json`**: Add the new tool definition to the `tools` array in `.cursor/mcp.json`.
- Implement `registerYourCommandTool(server)` using **camelCase** with `Tool` suffix.
- Define the tool `name` using **snake_case** (e.g., `your_command`).
5. **Register Tool**: Import and call `registerYourCommandTool` in `mcp-server/src/tools/index.js`.
6. **Update `mcp.json`**: Add the new tool definition to the `tools` array in `.cursor/mcp.json`.
## Handling Responses
@@ -185,3 +173,34 @@ Follow these steps to add MCP support for an existing Task Master command (see [
- Centralized error handling
- Performance optimization through caching (for read operations)
- Standardized response formatting
## MCP Naming Conventions
- **Files and Directories**:
- ✅ DO: Use **kebab-case** for all file names: `list-tasks.js`, `set-task-status.js`
- ✅ DO: Use consistent directory structure: `mcp-server/src/tools/` for tool definitions, `mcp-server/src/core/direct-functions/` for direct function implementations
- **JavaScript Functions**:
- ✅ DO: Use **camelCase** with `Direct` suffix for direct function implementations: `listTasksDirect`, `setTaskStatusDirect`
- ✅ DO: Use **camelCase** with `Tool` suffix for tool registration functions: `registerListTasksTool`, `registerSetTaskStatusTool`
- ✅ DO: Use consistent action function naming inside direct functions: `coreActionFn` or similar descriptive name
- **MCP Tool Names**:
- ✅ DO: Use **snake_case** for tool names exposed to MCP clients: `list_tasks`, `set_task_status`, `parse_prd_document`
- ✅ DO: Include the core action in the tool name without redundant words: Use `list_tasks` instead of `list_all_tasks`
- **Examples**:
- File: `list-tasks.js`
- Direct Function: `listTasksDirect`
- Tool Registration: `registerListTasksTool`
- MCP Tool Name: `list_tasks`
- **Mapping**:
- The `directFunctions` map in `task-master-core.js` maps the core function name (in camelCase) to its direct implementation:
```javascript
export const directFunctions = {
list: listTasksDirect,
setStatus: setTaskStatusDirect,
// Add more functions as implemented
};
```

View File

@@ -1,66 +0,0 @@
/**
* tools/addTask.js
* Tool to add a new task using AI
*/
import { z } from "zod";
import {
executeTaskMasterCommand,
createContentResponse,
createErrorResponse,
} from "./utils.js";
/**
* Register the addTask tool with the MCP server
* @param {FastMCP} server - FastMCP server instance
*/
export function registerAddTaskTool(server) {
server.addTool({
name: "addTask",
description: "Add a new task using AI",
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"),
priority: z
.string()
.optional()
.describe("Task priority (high, medium, low)"),
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z
.string()
.describe(
"Root directory of the project (default: current working directory)"
),
}),
execute: async (args, { log }) => {
try {
log.info(`Adding new task: ${args.prompt}`);
const cmdArgs = [`--prompt="${args.prompt}"`];
if (args.dependencies)
cmdArgs.push(`--dependencies=${args.dependencies}`);
if (args.priority) cmdArgs.push(`--priority=${args.priority}`);
if (args.file) cmdArgs.push(`--file=${args.file}`);
const result = executeTaskMasterCommand(
"add-task",
log,
cmdArgs,
projectRoot
);
if (!result.success) {
throw new Error(result.error);
}
return createContentResponse(result.stdout);
} catch (error) {
log.error(`Error adding task: ${error.message}`);
return createErrorResponse(`Error adding task: ${error.message}`);
}
},
});
}

View File

@@ -1,78 +0,0 @@
/**
* tools/expandTask.js
* Tool to break down a task into detailed subtasks
*/
import { z } from "zod";
import {
executeTaskMasterCommand,
createContentResponse,
createErrorResponse,
} from "./utils.js";
/**
* Register the expandTask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerExpandTaskTool(server) {
server.addTool({
name: "expandTask",
description: "Break down a task into detailed subtasks",
parameters: z.object({
id: z.string().describe("Task ID to expand"),
num: z.number().optional().describe("Number of subtasks to generate"),
research: z
.boolean()
.optional()
.describe(
"Enable Perplexity AI for research-backed subtask generation"
),
prompt: z
.string()
.optional()
.describe("Additional context to guide subtask generation"),
force: z
.boolean()
.optional()
.describe(
"Force regeneration of subtasks for tasks that already have them"
),
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z
.string()
.describe(
"Root directory of the project (default: current working directory)"
),
}),
execute: async (args, { log }) => {
try {
log.info(`Expanding task ${args.id}`);
const cmdArgs = [`--id=${args.id}`];
if (args.num) cmdArgs.push(`--num=${args.num}`);
if (args.research) cmdArgs.push("--research");
if (args.prompt) cmdArgs.push(`--prompt="${args.prompt}"`);
if (args.force) cmdArgs.push("--force");
if (args.file) cmdArgs.push(`--file=${args.file}`);
const projectRoot = args.projectRoot;
const result = executeTaskMasterCommand(
"expand",
log,
cmdArgs,
projectRoot
);
if (!result.success) {
throw new Error(result.error);
}
return createContentResponse(result.stdout);
} catch (error) {
log.error(`Error expanding task: ${error.message}`);
return createErrorResponse(`Error expanding task: ${error.message}`);
}
},
});
}

View File

@@ -4,13 +4,9 @@
*/
import logger from "../logger.js";
import { registerListTasksTool } from "./listTasks.js";
import { registerShowTaskTool } from "./showTask.js";
import { registerSetTaskStatusTool } from "./setTaskStatus.js";
import { registerExpandTaskTool } from "./expandTask.js";
import { registerNextTaskTool } from "./nextTask.js";
import { registerAddTaskTool } from "./addTask.js";
import { registerParsePRDTool } from "./parsePRD.js";
import { registerListTasksTool } from "./list-tasks.js";
import { registerSetTaskStatusTool } from "./set-task-status.js";
import { registerParsePRDTool } from "./parse-prd.js";
import { registerUpdateTool } from "./update.js";
import { registerUpdateTaskTool } from "./update-task.js";
import { registerUpdateSubtaskTool } from "./update-subtask.js";
@@ -22,11 +18,7 @@ import { registerGenerateTool } from "./generate.js";
*/
export function registerTaskMasterTools(server) {
registerListTasksTool(server);
registerShowTaskTool(server);
registerSetTaskStatusTool(server);
registerExpandTaskTool(server);
registerNextTaskTool(server);
registerAddTaskTool(server);
registerParsePRDTool(server);
registerUpdateTool(server);
registerUpdateTaskTool(server);

View File

@@ -16,7 +16,7 @@ import { listTasksDirect } from "../core/task-master-core.js";
*/
export function registerListTasksTool(server) {
server.addTool({
name: "listTasks",
name: "list-tasks",
description: "List all tasks from Task Master",
parameters: z.object({
status: z.string().optional().describe("Filter tasks by status"),

View File

@@ -1,57 +0,0 @@
/**
* tools/nextTask.js
* Tool to show the next task to work on based on dependencies and status
*/
import { z } from "zod";
import {
executeTaskMasterCommand,
createContentResponse,
createErrorResponse,
} from "./utils.js";
/**
* Register the nextTask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerNextTaskTool(server) {
server.addTool({
name: "nextTask",
description:
"Show the next task to work on based on dependencies and status",
parameters: z.object({
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z
.string()
.describe(
"Root directory of the project (default: current working directory)"
),
}),
execute: async (args, { log }) => {
try {
log.info(`Finding next task to work on`);
const cmdArgs = [];
if (args.file) cmdArgs.push(`--file=${args.file}`);
const projectRoot = args.projectRoot;
const result = executeTaskMasterCommand(
"next",
log,
cmdArgs,
projectRoot
);
if (!result.success) {
throw new Error(result.error);
}
return createContentResponse(result.stdout);
} catch (error) {
log.error(`Error finding next task: ${error.message}`);
return createErrorResponse(`Error finding next task: ${error.message}`);
}
},
});
}

View File

@@ -16,7 +16,7 @@ import { parsePRDDirect } from "../core/task-master-core.js";
*/
export function registerParsePRDTool(server) {
server.addTool({
name: "parsePRD",
name: "parse_prd_document",
description: "Parse PRD document and generate tasks",
parameters: z.object({
input: z.string().describe("Path to the PRD document file"),

View File

@@ -16,7 +16,7 @@ import { setTaskStatusDirect } from "../core/task-master-core.js";
*/
export function registerSetTaskStatusTool(server) {
server.addTool({
name: "setTaskStatus",
name: "set_task_status",
description: "Set the status of a task",
parameters: z.object({
id: z

View File

@@ -1,78 +0,0 @@
/**
* tools/showTask.js
* Tool to show detailed information about a specific task
*/
import { z } from "zod";
import {
executeTaskMasterCommand,
createErrorResponse,
handleApiResult
} from "./utils.js";
/**
* Register the showTask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerShowTaskTool(server) {
server.addTool({
name: "showTask",
description: "Show detailed information about a specific task",
parameters: z.object({
id: z.string().describe("Task ID to show"),
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 }) => {
try {
log.info(`Showing task details for ID: ${args.id}`);
// Prepare arguments for CLI command
const cmdArgs = [`--id=${args.id}`];
if (args.file) cmdArgs.push(`--file=${args.file}`);
// Execute the command - function now handles project root internally
const result = executeTaskMasterCommand(
"show",
log,
cmdArgs,
args.projectRoot // Pass raw project root, function will normalize it
);
// Process CLI result into API result format for handleApiResult
if (result.success) {
try {
// Try to parse response as JSON
const data = JSON.parse(result.stdout);
// Return equivalent of a successful API call with data
return handleApiResult({ success: true, data }, log, 'Error showing task');
} catch (e) {
// If parsing fails, still return success but with raw string data
return handleApiResult(
{ success: true, data: result.stdout },
log,
'Error showing task',
// Skip data processing for string data
null
);
}
} else {
// Return equivalent of a failed API call
return handleApiResult(
{ success: false, error: { message: result.error } },
log,
'Error showing task'
);
}
} catch (error) {
log.error(`Error showing task: ${error.message}`);
return createErrorResponse(error.message);
}
},
});
}

View File

@@ -17,6 +17,7 @@ This task involves completing the Model Context Protocol (MCP) server implementa
8. Identify and address missing components or functionalities to meet FastMCP best practices, such as robust error handling, monitoring endpoints, and concurrency support.
9. Update documentation to include examples of using the MCP server with FastMCP, detailed setup instructions, and client integration guides.
10. Organize direct function implementations in a modular structure within the mcp-server/src/core/direct-functions/ directory for improved maintainability and organization.
11. Follow consistent naming conventions: file names use kebab-case (like-this.js), direct functions use camelCase with Direct suffix (functionNameDirect), tool registration functions use camelCase with Tool suffix (registerToolNameTool), and MCP tool names exposed to clients use snake_case (tool_name).
The implementation must ensure compatibility with existing MCP clients and follow RESTful API design principles, while supporting concurrent requests and maintaining robust error handling.
@@ -529,7 +530,7 @@ Following MCP implementation standards:
- Implement registerShowTaskTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'show_task'
5. Add to .cursor/mcp.json with appropriate schema
@@ -562,7 +563,7 @@ Following MCP implementation standards:
- Implement registerNextTaskTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'next_task'
5. Add to .cursor/mcp.json with appropriate schema
@@ -595,7 +596,7 @@ Following MCP implementation standards:
- Implement registerExpandTaskTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'expand_task'
5. Add to .cursor/mcp.json with appropriate schema
@@ -628,7 +629,7 @@ Following MCP implementation standards:
- Implement registerAddTaskTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'add_task'
5. Add to .cursor/mcp.json with appropriate schema
@@ -661,7 +662,7 @@ Following MCP implementation standards:
- Implement registerAddSubtaskTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'add_subtask'
5. Add to .cursor/mcp.json with appropriate schema
@@ -694,7 +695,7 @@ Following MCP implementation standards:
- Implement registerRemoveSubtaskTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'remove_subtask'
5. Add to .cursor/mcp.json with appropriate schema
@@ -727,7 +728,7 @@ Following MCP implementation standards:
- Implement registerAnalyzeTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'analyze'
5. Add to .cursor/mcp.json with appropriate schema
@@ -760,7 +761,7 @@ Following MCP implementation standards:
- Implement registerClearSubtasksTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'clear_subtasks'
5. Add to .cursor/mcp.json with appropriate schema
@@ -793,7 +794,7 @@ Following MCP implementation standards:
- Implement registerExpandAllTool(server) with server.addTool
- Use executeMCPToolAction in execute method
4. Register in tools/index.js
4. Register in tools/index.js with tool name 'expand_all'
5. Add to .cursor/mcp.json with appropriate schema
@@ -827,3 +828,17 @@ Following MCP implementation standards:
7. Ensure all MCP tools reference the functions through task-master-core.js
8. Verify backward compatibility with existing code
## 33. Implement Naming Convention Standards [pending]
### Dependencies: None
### Description: Update all MCP server components to follow the standardized naming conventions for files, functions, and tools.
### Details:
1. Audit all existing MCP server files and update file names to use kebab-case (like-this.js)
2. Refactor direct function names to use camelCase with Direct suffix (functionNameDirect)
3. Update tool registration functions to use camelCase with Tool suffix (registerToolNameTool)
4. Ensure all MCP tool names exposed to clients use snake_case (tool_name)
5. Create a naming convention documentation file for future reference
6. Update imports/exports in all files to reflect the new naming conventions
7. Verify that all tools are properly registered with the correct naming pattern
8. Update tests to reflect the new naming conventions
9. Create a linting rule to enforce naming conventions in future development

File diff suppressed because one or more lines are too long