This commit refactors how the MCP server determines the project root directory, prioritizing the path provided by the client session (e.g., Cursor) for increased reliability and simplification. Previously, project root discovery relied on a complex chain of fallbacks (environment variables, CWD searching, package path checks) within `findTasksJsonPath`. This could be brittle and less accurate when running within an integrated environment like Cursor. Key changes: - **Prioritize Session Roots:** MCP tools (`add-task`, `add-dependency`, etc.) now first attempt to extract the project root URI directly from `session.roots[0].uri`. - **New Utility `getProjectRootFromSession`:** Added a utility function in `mcp-server/src/tools/utils.js` to encapsulate the logic for extracting and decoding the root URI from the session object. - **Refactor MCP Tools:** Updated tools (`add-task.js`, `add-dependency.js`) to use `getProjectRootFromSession`. - **Simplify `findTasksJsonPath`:** Prioritized `args.projectRoot`, removed checks for `TASK_MASTER_PROJECT_ROOT` env var and package directory fallback. Retained CWD search and cache check for CLI compatibility. - **Fix `reportProgress` Usage:** Corrected parameters in `add-dependency.js`. This change makes project root determination more robust for the MCP server while preserving discovery mechanisms for the standalone CLI.
626 lines
31 KiB
Plaintext
626 lines
31 KiB
Plaintext
---
|
|
description: Guidelines for implementing and interacting with the Task Master MCP Server
|
|
globs: mcp-server/src/**/*, scripts/modules/**/*
|
|
alwaysApply: false
|
|
---
|
|
|
|
# Task Master MCP Server Guidelines
|
|
|
|
This document outlines the architecture and implementation patterns for the Task Master Model Context Protocol (MCP) server, designed for integration with tools like Cursor.
|
|
|
|
## Architecture Overview (See also: [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc))
|
|
|
|
The MCP server acts as a bridge between external tools (like Cursor) and the core Task Master CLI logic. It leverages FastMCP for the server framework.
|
|
|
|
- **Flow**: `External Tool (Cursor)` <-> `FastMCP Server` <-> `MCP Tools` (`mcp-server/src/tools/*.js`) <-> `Core Logic Wrappers` (`mcp-server/src/core/direct-functions/*.js`, exported via `task-master-core.js`) <-> `Core Modules` (`scripts/modules/*.js`)
|
|
- **Goal**: Provide a performant and reliable way for external tools to interact with Task Master functionality without directly invoking the CLI for every operation.
|
|
|
|
## Tool Definition and Execution
|
|
|
|
### Tool Structure
|
|
|
|
MCP tools must follow a specific structure to properly interact with the FastMCP framework:
|
|
|
|
```javascript
|
|
server.addTool({
|
|
name: "tool_name", // Use snake_case for tool names
|
|
description: "Description of what the tool does",
|
|
parameters: z.object({
|
|
// Define parameters using Zod
|
|
param1: z.string().describe("Parameter description"),
|
|
param2: z.number().optional().describe("Optional parameter description"),
|
|
// IMPORTANT: For file operations, always include these optional parameters
|
|
file: z.string().optional().describe("Path to the tasks file"),
|
|
projectRoot: z.string().optional().describe("Root directory of the project")
|
|
}),
|
|
|
|
// The execute function is the core of the tool implementation
|
|
execute: async (args, context) => {
|
|
// Implementation goes here
|
|
// Return response in the appropriate format
|
|
}
|
|
});
|
|
```
|
|
|
|
### Execute Function Signature
|
|
|
|
The `execute` function should follow this exact pattern:
|
|
|
|
```javascript
|
|
execute: async (args, context) => {
|
|
// Tool implementation
|
|
}
|
|
```
|
|
|
|
- **args**: The first parameter contains all the validated parameters defined in the tool's schema
|
|
- You can destructure specific parameters: `const { param1, param2, file, projectRoot } = args;`
|
|
- Always pass the full `args` object to direct functions: `await yourDirectFunction(args, context.log);`
|
|
|
|
- **context**: The second parameter is an object with specific properties provided by FastMCP
|
|
- Contains `{ log, reportProgress, session }` - **always destructure these from the context object**
|
|
- ✅ **DO**: `execute: async (args, { log, reportProgress, session }) => {}`
|
|
- ❌ **DON'T**: `execute: async (args, log) => {}`
|
|
|
|
### Logging Convention
|
|
|
|
The `log` object provides standardized logging methods:
|
|
|
|
```javascript
|
|
// Proper logging usage within a tool's execute method
|
|
execute: async (args, { log, reportProgress, session }) => {
|
|
try {
|
|
// Log the start of execution with key parameters (but sanitize sensitive data)
|
|
log.info(`Starting ${toolName} with parameters: ${JSON.stringify(args, null, 2)}`);
|
|
|
|
// For debugging information
|
|
log.debug("Detailed operation information", { additionalContext: "value" });
|
|
|
|
// For warnings that don't prevent execution
|
|
log.warn("Warning: potential issue detected", { details: "..." });
|
|
|
|
// Call the direct function and handle the result
|
|
const result = await someDirectFunction(args, log);
|
|
|
|
// Log successful completion
|
|
log.info(`Successfully completed ${toolName}`, {
|
|
resultSummary: "brief summary without sensitive data"
|
|
});
|
|
|
|
return handleApiResult(result, log);
|
|
} catch (error) {
|
|
// Log errors with full context
|
|
log.error(`Error in ${toolName}: ${error.message}`, {
|
|
errorDetails: error.stack
|
|
});
|
|
|
|
return createErrorResponse(error.message, "ERROR_CODE");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Progress Reporting Convention
|
|
|
|
Use `reportProgress` for long-running operations to provide feedback to the client:
|
|
|
|
```javascript
|
|
execute: async (args, { log, reportProgress, session }) => {
|
|
// Initialize progress at the start
|
|
await reportProgress({ progress: 0, total: 100 });
|
|
|
|
// For known progress stages, update with specific percentages
|
|
for (let i = 0; i < stages.length; i++) {
|
|
// Do some work...
|
|
|
|
// Report intermediate progress
|
|
await reportProgress({
|
|
progress: Math.floor((i + 1) / stages.length * 100),
|
|
total: 100
|
|
});
|
|
}
|
|
|
|
// For indeterminate progress (no known total)
|
|
await reportProgress({ progress: 1 }); // Just increment without total
|
|
|
|
// When complete
|
|
await reportProgress({ progress: 100, total: 100 });
|
|
|
|
// Return the result
|
|
return result;
|
|
}
|
|
```
|
|
|
|
FYI reportProgress object is not arbitrary. It must be { progress: number, total: number }.
|
|
|
|
### Session Usage Convention
|
|
|
|
The `session` object contains authenticated session data and can be used for:
|
|
|
|
1. **Authentication information**: Access user-specific data that was set during authentication
|
|
2. **Environment variables**: Access environment variables without direct process.env references (if implemented)
|
|
3. **User context**: Check user permissions or preferences
|
|
|
|
```javascript
|
|
execute: async (args, { log, reportProgress, session }) => {
|
|
// Check if session exists (user is authenticated)
|
|
if (!session) {
|
|
log.warn("No session data available, operating in anonymous mode");
|
|
} else {
|
|
// Access authenticated user information
|
|
log.info(`Operation requested by user: ${session.userId}`);
|
|
|
|
// Access environment variables or configuration via session
|
|
const apiKey = session.env?.API_KEY;
|
|
|
|
// Check user-specific permissions
|
|
if (session.permissions?.canUpdateTasks) {
|
|
// Perform privileged operation
|
|
}
|
|
}
|
|
|
|
// Continue with implementation...
|
|
}
|
|
```
|
|
|
|
### Accessing Project Roots through Session
|
|
|
|
The `session` object in FastMCP provides access to filesystem roots via the `session.roots` array, which can be used to get the client's project root directory. This can help bypass some of the path resolution logic in `path-utils.js` when the client explicitly provides its project root.
|
|
|
|
#### What are Session Roots?
|
|
|
|
- The `roots` array contains filesystem root objects provided by the FastMCP client (e.g., Cursor).
|
|
- Each root object typically represents a mounted filesystem or workspace that the client (IDE) has access to.
|
|
- For tools like Cursor, the first root is usually the current project or workspace root.
|
|
|
|
#### Roots Structure
|
|
|
|
Based on the FastMCP core implementation, the roots structure looks like:
|
|
|
|
```javascript
|
|
// Root type from FastMCP
|
|
type Root = {
|
|
uri: string; // URI of the root, e.g., "file:///Users/username/project"
|
|
name: string; // Display name of the root
|
|
capabilities?: { // Optional capabilities this root supports
|
|
// Root-specific capabilities
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Accessing Project Root from Session
|
|
|
|
To properly access and use the project root from the session:
|
|
|
|
```javascript
|
|
execute: async (args, { log, reportProgress, session }) => {
|
|
try {
|
|
// Try to get the project root from session
|
|
let rootFolder = null;
|
|
|
|
if (session && session.roots && session.roots.length > 0) {
|
|
// The first root is typically the main project workspace in clients like Cursor
|
|
const firstRoot = session.roots[0];
|
|
|
|
if (firstRoot && firstRoot.uri) {
|
|
// Convert the URI to a file path (strip the file:// prefix if present)
|
|
const rootUri = firstRoot.uri;
|
|
rootFolder = rootUri.startsWith('file://')
|
|
? decodeURIComponent(rootUri.slice(7)) // Remove 'file://' and decode URI components
|
|
: rootUri;
|
|
|
|
log.info(`Using project root from session: ${rootFolder}`);
|
|
}
|
|
}
|
|
|
|
// Use rootFolder if available, otherwise let path-utils.js handle the detection
|
|
const result = await yourDirectFunction({
|
|
projectRoot: rootFolder,
|
|
...args
|
|
}, log);
|
|
|
|
return handleApiResult(result, log);
|
|
} catch (error) {
|
|
log.error(`Error in tool: ${error.message}`);
|
|
return createErrorResponse(error.message);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Integration with path-utils.js
|
|
|
|
The `rootFolder` extracted from the session should be passed to the direct function as the `projectRoot` parameter. This integrates with `findTasksJsonPath` in `path-utils.js`, which uses a precedence order for finding the project root:
|
|
|
|
1. **TASK_MASTER_PROJECT_ROOT** environment variable
|
|
2. **Explicitly provided `projectRoot`** (from session.roots or args)
|
|
3. Previously found/cached project root
|
|
4. Search from current directory upwards
|
|
5. Package directory fallback
|
|
|
|
By providing the rootFolder from session.roots as the projectRoot parameter, we're using the second option in this hierarchy, allowing the client to explicitly tell us where the project is located rather than having to search for it.
|
|
|
|
#### Example Implementation
|
|
|
|
Here's a complete example for a tool that properly uses session roots:
|
|
|
|
```javascript
|
|
execute: async (args, { log, reportProgress, session }) => {
|
|
try {
|
|
log.info(`Starting tool with args: ${JSON.stringify(args)}`);
|
|
|
|
// Extract project root from session if available
|
|
let rootFolder = null;
|
|
if (session && session.roots && session.roots.length > 0) {
|
|
const firstRoot = session.roots[0];
|
|
if (firstRoot && firstRoot.uri) {
|
|
rootFolder = firstRoot.uri.startsWith('file://')
|
|
? decodeURIComponent(firstRoot.uri.slice(7))
|
|
: firstRoot.uri;
|
|
log.info(`Using project root from session: ${rootFolder}`);
|
|
}
|
|
}
|
|
|
|
// If we couldn't get a root from session but args has projectRoot, use that
|
|
if (!rootFolder && args.projectRoot) {
|
|
rootFolder = args.projectRoot;
|
|
log.info(`Using project root from args: ${rootFolder}`);
|
|
}
|
|
|
|
// Call the direct function with the rootFolder
|
|
const result = await someDirectFunction({
|
|
projectRoot: rootFolder,
|
|
...args
|
|
}, log);
|
|
|
|
log.info(`Completed tool successfully`);
|
|
return handleApiResult(result, log);
|
|
} catch (error) {
|
|
log.error(`Error in tool: ${error.message}`);
|
|
return createErrorResponse(error.message);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Key Principles
|
|
|
|
- **Prefer Direct Function Calls**: For optimal performance and error handling, MCP tools should utilize direct function wrappers (exported via [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js), implemented in [`mcp-server/src/core/direct-functions/`](mdc:mcp-server/src/core/direct-functions/)). These wrappers call the underlying logic from the core modules (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)).
|
|
- **Standard Tool Execution Pattern**:
|
|
- The `execute` method within each MCP tool (in `mcp-server/src/tools/*.js`) should:
|
|
1. Call the corresponding `*Direct` function wrapper (e.g., `listTasksDirect`), passing the *entire* `args` object received from the tool invocation and the `log` object.
|
|
2. Receive the result object (typically `{ success, data/error, fromCache }`) from the `*Direct` function.
|
|
3. Pass this result object to the `handleApiResult` utility (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)) for standardized response formatting and error handling.
|
|
4. Return the formatted response object provided by `handleApiResult`.
|
|
- **CLI Execution as Fallback**: The `executeTaskMasterCommand` utility in [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) allows executing commands via the CLI (`task-master ...`). This should **only** be used as a fallback if a direct function wrapper is not yet implemented or if a specific command intrinsically requires CLI execution.
|
|
- **Robust Project Root Handling**:
|
|
- **Tool Definition**: Any MCP tool that needs to locate the project's `tasks.json` *must* define the `projectRoot` parameter in its `zod` schema as **optional**: `projectRoot: z.string().optional().describe(...)`. This allows clients to optionally specify a root, but doesn't require it.
|
|
- **Path Resolution Utility**: The `findTasksJsonPath` utility (in [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js)) handles the actual detection of the project root and `tasks.json` path using a hierarchical approach (env var, explicit arg, cache, cwd search, package fallback).
|
|
- **Direct Function Usage**: The `*Direct` function wrapper (in `mcp-server/src/core/direct-functions/`) is responsible for getting the correct path by calling `const tasksPath = findTasksJsonPath(args, log);`. It passes the *entire `args` object* received by the tool (which may or may not contain `projectRoot` or `file` properties) and the `log` object. `findTasksJsonPath` will use the values within `args` according to its precedence rules.
|
|
- **Centralized Utilities** (See also: [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc)):
|
|
- **Leverage MCP Utilities**: The file [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) contains essential helpers for MCP tool implementation:
|
|
- `handleApiResult`: Takes the raw result from a `*Direct` function and formats it into a standard MCP success or error response, automatically handling data processing via `processMCPResponseData`. This is called by the tool's `execute` method.
|
|
- `createContentResponse`/`createErrorResponse`: Used by `handleApiResult` to format successful/error MCP responses.
|
|
- `processMCPResponseData`: Filters/cleans data (e.g., removing `details`, `testStrategy`) before it's sent in the MCP response. Called by `handleApiResult`.
|
|
- `getCachedOrExecute`: **Used inside `*Direct` functions** to implement caching logic.
|
|
- `executeTaskMasterCommand`: Fallback for executing CLI commands.
|
|
- **Caching**: To improve performance for frequently called read operations (like `get_tasks`, `get_task`, `next_task`), a caching layer using `lru-cache` is implemented.
|
|
- **Caching logic resides *within* the direct function wrappers** using the `getCachedOrExecute` utility from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js).
|
|
- Generate unique cache keys based on function arguments that define a distinct call (e.g., file path, filters).
|
|
- The `getCachedOrExecute` utility handles checking the cache, executing the core logic function on a cache miss, storing the result, and returning the data along with a `fromCache` flag.
|
|
- Cache statistics can be monitored using the `cache_stats` MCP tool.
|
|
- **Caching should generally be applied to read-only operations** that don't modify the `tasks.json` state. Commands like `set_task_status`, `add_task`, `update_task`, `parse_prd`, `add_dependency` should *not* be cached as they change the underlying data.
|
|
|
|
## Resources and Resource Templates
|
|
|
|
Resources and resource templates are an important part of the FastMCP architecture that allow for exposing data to LLM clients without needing to execute tools.
|
|
|
|
### Resource Implementation
|
|
|
|
- **Purpose**: Resources provide LLMs with static or dynamic data that can be referenced directly without executing a tool.
|
|
- **Current Implementation**: In [`mcp-server/src/index.js`](mdc:mcp-server/src/index.js), resources are currently initialized as empty objects:
|
|
```javascript
|
|
this.server.addResource({});
|
|
this.server.addResourceTemplate({});
|
|
```
|
|
|
|
- **Proper Implementation**: Resources should be implemented using the `@mcp.resource()` decorator pattern in a dedicated directory (e.g., `mcp-server/src/core/resources/`).
|
|
|
|
- **Resource Types for Task Master**:
|
|
- **Task Templates**: Predefined task structures that can be used as starting points
|
|
- **Workflow Definitions**: Reusable workflow patterns for common task sequences
|
|
- **Project Metadata**: Information about active projects and their attributes
|
|
- **User Preferences**: Stored user settings for task management
|
|
|
|
- **Resource Implementation Example**:
|
|
```javascript
|
|
// mcp-server/src/core/resources/task-templates.js
|
|
import { taskTemplates } from '../data/templates.js';
|
|
|
|
export function registerTaskTemplateResources(server) {
|
|
server.addResource({
|
|
name: "tasks://templates/{templateId}",
|
|
description: "Access predefined task templates",
|
|
parameters: {
|
|
templateId: {
|
|
type: "string",
|
|
description: "ID of the task template"
|
|
}
|
|
},
|
|
execute: async ({ templateId }) => {
|
|
const template = taskTemplates[templateId];
|
|
if (!template) {
|
|
return {
|
|
status: 404,
|
|
content: { error: `Template ${templateId} not found` }
|
|
};
|
|
}
|
|
return {
|
|
status: 200,
|
|
content: template
|
|
};
|
|
}
|
|
});
|
|
|
|
// Register all templates as a collection resource
|
|
server.addResource({
|
|
name: "tasks://templates",
|
|
description: "List all available task templates",
|
|
execute: async () => {
|
|
return {
|
|
status: 200,
|
|
content: Object.keys(taskTemplates).map(id => ({
|
|
id,
|
|
name: taskTemplates[id].name,
|
|
description: taskTemplates[id].description
|
|
}))
|
|
};
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
### Resource Templates Implementation
|
|
|
|
- **Purpose**: Resource templates allow for dynamic generation of resources based on patterns.
|
|
- **Implementation Example**:
|
|
```javascript
|
|
// mcp-server/src/core/resources/task-templates.js
|
|
export function registerTaskTemplateResourceTemplates(server) {
|
|
server.addResourceTemplate({
|
|
pattern: "tasks://create/{taskType}",
|
|
description: "Generate a task creation template based on task type",
|
|
parameters: {
|
|
taskType: {
|
|
type: "string",
|
|
description: "Type of task to create (e.g., 'feature', 'bug', 'docs')"
|
|
}
|
|
},
|
|
execute: async ({ taskType }) => {
|
|
// Generate a dynamic template based on taskType
|
|
const template = generateTemplateForTaskType(taskType);
|
|
|
|
if (!template) {
|
|
return {
|
|
status: 404,
|
|
content: { error: `No template available for task type: ${taskType}` }
|
|
};
|
|
}
|
|
|
|
return {
|
|
status: 200,
|
|
content: template
|
|
};
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
### Resource Registration in Server Initialization
|
|
|
|
Resources and resource templates should be registered during server initialization:
|
|
|
|
```javascript
|
|
// mcp-server/src/index.js
|
|
import { registerTaskTemplateResources, registerTaskTemplateResourceTemplates } from './core/resources/task-templates.js';
|
|
import { registerWorkflowResources } from './core/resources/workflow-definitions.js';
|
|
import { registerProjectMetadataResources } from './core/resources/project-metadata.js';
|
|
|
|
class TaskMasterMCPServer {
|
|
constructor() {
|
|
// ... existing constructor code ...
|
|
|
|
this.server = new FastMCP(this.options);
|
|
this.initialized = false;
|
|
|
|
// Resource registration will be done in init()
|
|
|
|
// ... rest of constructor ...
|
|
}
|
|
|
|
async init() {
|
|
if (this.initialized) return;
|
|
|
|
// Register resources before tools
|
|
registerTaskTemplateResources(this.server);
|
|
registerTaskTemplateResourceTemplates(this.server);
|
|
registerWorkflowResources(this.server);
|
|
registerProjectMetadataResources(this.server);
|
|
|
|
// Register Task Master tools
|
|
registerTaskMasterTools(this.server);
|
|
|
|
this.initialized = true;
|
|
return this;
|
|
}
|
|
|
|
// ... rest of class ...
|
|
}
|
|
```
|
|
|
|
### Best Practices for Resources
|
|
|
|
1. **Organization**: Create a dedicated `resources` directory with separate modules for each resource category.
|
|
2. **Validation**: Validate input parameters and return appropriate error responses.
|
|
3. **Caching**: Consider caching resource responses if they are expensive to generate.
|
|
4. **Documentation**: Include clear descriptions for all resources and their parameters.
|
|
5. **URI Structure**: Use consistent URI patterns (`resource-type://path/to/resource`) for all resources.
|
|
6. **Error Handling**: Return standard HTTP-like status codes (200, 404, etc.) for resource responses.
|
|
7. **Resource Registration**: Register resources before tools during server initialization.
|
|
|
|
Resources enable LLMs to access contextual information without needing to execute tools, which can significantly improve performance and user experience. Properly implemented resources complement tools to create a comprehensive MCP server.
|
|
|
|
## Implementing MCP Support for a Command
|
|
|
|
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 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.
|
|
- 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:
|
|
- **Path Resolution**: Obtain the tasks file path using `const tasksPath = findTasksJsonPath(args, log);`. This handles project root detection automatically.
|
|
- Parse other `args` and perform necessary validation.
|
|
- **If Caching**: Implement caching using `getCachedOrExecute` as described above.
|
|
- **If Not Caching**: Directly call the core logic function within a try/catch block.
|
|
- 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 and re-export your `*Direct` function and add it to the `directFunctions` map.
|
|
|
|
4. **Create MCP Tool (`mcp-server/src/tools/`)**:
|
|
- Create a new file (e.g., `your-command.js`) using **kebab-case**.
|
|
- Import `zod`, `handleApiResult`, `createErrorResponse`, and your `yourCommandDirect` function.
|
|
- Implement `registerYourCommandTool(server)`.
|
|
- Define the tool `name` using **snake_case** (e.g., `your_command`).
|
|
- Define the `parameters` using `zod`. **Crucially, if the tool needs project context, include `projectRoot: z.string().optional().describe(...)` and potentially `file: z.string().optional().describe(...)`**. Make `projectRoot` optional.
|
|
- Implement the standard `async execute(args, { log, reportProgress, session })` method: call `yourCommandDirect(args, log)` and pass the result to `handleApiResult(result, log, 'Error Message')`.
|
|
|
|
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
|
|
|
|
- MCP tools should return the object generated by `handleApiResult`.
|
|
- `handleApiResult` uses `createContentResponse` or `createErrorResponse` internally.
|
|
- `handleApiResult` also uses `processMCPResponseData` by default to filter potentially large fields (`details`, `testStrategy`) from task data. Provide a custom processor function to `handleApiResult` if different filtering is needed.
|
|
- The final JSON response sent to the MCP client will include the `fromCache` boolean flag (obtained from the `*Direct` function's result) alongside the actual data (e.g., `{ "fromCache": true, "data": { ... } }` or `{ "fromCache": false, "data": { ... } }`).
|
|
|
|
## Parameter Type Handling
|
|
|
|
- **Prefer Direct Function Calls**: For optimal performance and error handling, MCP tools should utilize direct function wrappers defined in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js). These wrappers call the underlying logic from the core modules (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)).
|
|
- **Standard Tool Execution Pattern**:
|
|
- The `execute` method within each MCP tool (in `mcp-server/src/tools/*.js`) should:
|
|
1. Call the corresponding `*Direct` function wrapper (e.g., `listTasksDirect`) from [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js), passing necessary arguments and the logger.
|
|
2. Receive the result object (typically `{ success, data/error, fromCache }`).
|
|
3. Pass this result object to the `handleApiResult` utility (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)) for standardized response formatting and error handling.
|
|
4. Return the formatted response object provided by `handleApiResult`.
|
|
- **CLI Execution as Fallback**: The `executeTaskMasterCommand` utility in [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) allows executing commands via the CLI (`task-master ...`). This should **only** be used as a fallback if a direct function wrapper is not yet implemented or if a specific command intrinsically requires CLI execution.
|
|
- **Centralized Utilities** (See also: [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc)):
|
|
- Use `findTasksJsonPath` (in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js)) *within direct function wrappers* to locate the `tasks.json` file consistently.
|
|
- **Leverage MCP Utilities**: The file [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) contains essential helpers for MCP tool implementation:
|
|
- `getProjectRoot`: Normalizes project paths.
|
|
- `handleApiResult`: Takes the raw result from a `*Direct` function and formats it into a standard MCP success or error response, automatically handling data processing via `processMCPResponseData`. This is called by the tool's `execute` method.
|
|
- `createContentResponse`/`createErrorResponse`: Used by `handleApiResult` to format successful/error MCP responses.
|
|
- `processMCPResponseData`: Filters/cleans data (e.g., removing `details`, `testStrategy`) before it's sent in the MCP response. Called by `handleApiResult`.
|
|
- `getCachedOrExecute`: **Used inside `*Direct` functions** in `task-master-core.js` to implement caching logic.
|
|
- `executeTaskMasterCommand`: Fallback for executing CLI commands.
|
|
- **Caching**: To improve performance for frequently called read operations (like `listTasks`, `showTask`, `nextTask`), a caching layer using `lru-cache` is implemented.
|
|
- **Caching logic resides *within* the direct function wrappers** in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js) using the `getCachedOrExecute` utility from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js).
|
|
- Generate unique cache keys based on function arguments that define a distinct call (e.g., file path, filters).
|
|
- The `getCachedOrExecute` utility handles checking the cache, executing the core logic function on a cache miss, storing the result, and returning the data along with a `fromCache` flag.
|
|
- Cache statistics can be monitored using the `cacheStats` MCP tool (implemented via `getCacheStatsDirect`).
|
|
- **Caching should generally be applied to read-only operations** that don't modify the `tasks.json` state. Commands like `set-status`, `add-task`, `update-task`, `parse-prd`, `add-dependency` should *not* be cached as they change the underlying data.
|
|
|
|
**MCP Tool Implementation Checklist**:
|
|
|
|
1. **Core Logic Verification**:
|
|
- [ ] Confirm the core function is properly exported from its module (e.g., `task-manager.js`)
|
|
- [ ] Identify all required parameters and their types
|
|
|
|
2. **Direct Function Wrapper**:
|
|
- [ ] Create the `*Direct` function in `task-master-core.js`
|
|
- [ ] Handle all parameter validations and type conversions
|
|
- [ ] Implement path resolving for relative paths
|
|
- [ ] Add appropriate error handling with standardized error codes
|
|
- [ ] Add to `directFunctions` map
|
|
|
|
3. **MCP Tool Implementation**:
|
|
- [ ] Create new file in `mcp-server/src/tools/` with kebab-case naming
|
|
- [ ] Define zod schema for all parameters
|
|
- [ ] Implement the `execute` method following the standard pattern
|
|
- [ ] Register tool in `mcp-server/src/tools/index.js`
|
|
|
|
4. **Testing**:
|
|
- [ ] Write unit tests for the direct function wrapper
|
|
- [ ] Write integration tests for the MCP tool
|
|
|
|
## Standard Error Codes
|
|
|
|
- **Standard Error Codes**: Use consistent error codes across direct function wrappers
|
|
- `INPUT_VALIDATION_ERROR`: For missing or invalid required parameters
|
|
- `FILE_NOT_FOUND_ERROR`: For file system path issues
|
|
- `CORE_FUNCTION_ERROR`: For errors thrown by the core function
|
|
- `UNEXPECTED_ERROR`: For all other unexpected errors
|
|
|
|
- **Error Object Structure**:
|
|
```javascript
|
|
{
|
|
success: false,
|
|
error: {
|
|
code: 'ERROR_CODE',
|
|
message: 'Human-readable error message'
|
|
},
|
|
fromCache: false
|
|
}
|
|
```
|
|
|
|
- **MCP Tool Logging Pattern**:
|
|
- ✅ DO: Log the start of execution with arguments (sanitized if sensitive)
|
|
- ✅ DO: Log successful completion with result summary
|
|
- ✅ DO: Log all error conditions with appropriate log levels
|
|
- ✅ DO: Include the cache status in result logs
|
|
- ❌ DON'T: Log entire large data structures or sensitive information
|
|
|
|
- The MCP server integrates with Task Master core functions through three layers:
|
|
1. Tool Definitions (`mcp-server/src/tools/*.js`) - Define parameters and validation
|
|
2. Direct Functions (`mcp-server/src/core/direct-functions/*.js`) - Handle core logic integration
|
|
3. Core Functions (`scripts/modules/*.js`) - Implement the actual functionality
|
|
|
|
- This layered approach provides:
|
|
- Clear separation of concerns
|
|
- Consistent parameter validation
|
|
- 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
|
|
};
|
|
```
|