diff --git a/.cursor/rules/architecture.mdc b/.cursor/rules/architecture.mdc index f060606e..b05b9d35 100644 --- a/.cursor/rules/architecture.mdc +++ b/.cursor/rules/architecture.mdc @@ -12,7 +12,7 @@ alwaysApply: false - **[`commands.js`](mdc:scripts/modules/commands.js): Command Handling** - **Purpose**: Defines and registers all CLI commands using Commander.js. - - **Responsibilities**: + - **Responsibilities** (See also: [`commands.mdc`](mdc:.cursor/rules/commands.mdc)): - Parses command-line arguments and options. - Invokes appropriate functions from other modules to execute commands. - Handles user input and output related to command execution. @@ -86,7 +86,7 @@ alwaysApply: false - **[`utils.js`](mdc:scripts/modules/utils.js): Utility Functions and Configuration** - **Purpose**: Provides reusable utility functions and global configuration settings used across the application. - - **Responsibilities**: + - **Responsibilities** (See also: [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc)): - Manages global configuration settings loaded from environment variables and defaults. - Implements logging utility with different log levels and output formatting. - Provides file system operation utilities (read/write JSON files). @@ -101,6 +101,19 @@ alwaysApply: false - `formatTaskId(id)` / `findTaskById(tasks, taskId)`: Task ID and search utilities. - `findCycles(subtaskId, dependencyMap)`: Cycle detection algorithm. + - **[`mcp-server/`](mdc:mcp-server/): MCP Server Integration** + - **Purpose**: Provides an MCP (Model Context Protocol) interface for Task Master, allowing integration with external tools like Cursor. Uses FastMCP framework. + - **Responsibilities** (See also: [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc)): + - Registers Task Master functionalities as tools consumable via MCP. + - Handles MCP requests and translates them into calls to the Task Master core logic. + - Prefers direct function calls to core modules via [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js) for performance. + - Uses CLI execution via `executeTaskMasterCommand` as a fallback. + - **Implements Caching**: Utilizes a caching layer (`ContextManager` with `lru-cache`) invoked via `getCachedOrExecute` within direct function wrappers ([`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js)) to optimize performance for specific read operations (e.g., listing tasks). + - Standardizes response formatting for MCP clients using utilities in [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js). + - **Key Components**: + - `mcp-server/src/server.js`: Main server setup and initialization. + - `mcp-server/src/tools/`: Directory containing individual tool definitions, each registering a specific Task Master command for MCP. + - **Data Flow and Module Dependencies**: - **Commands Initiate Actions**: User commands entered via the CLI (handled by [`commands.js`](mdc:scripts/modules/commands.js)) are the entry points for most operations. @@ -108,10 +121,11 @@ alwaysApply: false - **UI for Presentation**: [`ui.js`](mdc:scripts/modules/ui.js) is used by command handlers and task/dependency managers to display information to the user. UI functions primarily consume data and format it for output, without modifying core application state. - **Utilities for Common Tasks**: [`utils.js`](mdc:scripts/modules/utils.js) provides helper functions used by all other modules for configuration, logging, file operations, and common data manipulations. - **AI Services Integration**: AI functionalities (complexity analysis, task expansion, PRD parsing) are invoked from [`task-manager.js`](mdc:scripts/modules/task-manager.js) and potentially [`commands.js`](mdc:scripts/modules/commands.js), likely using functions that would reside in a dedicated `ai-services.js` module or be integrated within `utils.js` or `task-manager.js`. + - **MCP Server Interaction**: External tools interact with the `mcp-server`, which then calls direct function wrappers in `task-master-core.js` or falls back to `executeTaskMasterCommand`. Responses are formatted by `mcp-server/src/tools/utils.js`. See [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc) for details. - **Testing Architecture**: - - **Test Organization Structure**: + - **Test Organization Structure** (See also: [`tests.mdc`](mdc:.cursor/rules/tests.mdc)): - **Unit Tests**: Located in `tests/unit/`, reflect the module structure with one test file per module - **Integration Tests**: Located in `tests/integration/`, test interactions between modules - **End-to-End Tests**: Located in `tests/e2e/`, test complete workflows from a user perspective diff --git a/.cursor/rules/dev_workflow.mdc b/.cursor/rules/dev_workflow.mdc index 6ae53acc..5822d8c8 100644 --- a/.cursor/rules/dev_workflow.mdc +++ b/.cursor/rules/dev_workflow.mdc @@ -5,16 +5,16 @@ alwaysApply: true --- - **Global CLI Commands** - - Task Master now provides a global CLI through the `task-master` command + - Task Master now provides a global CLI through the `task-master` command (See [`commands.mdc`](mdc:.cursor/rules/commands.mdc) for details) - All functionality from `scripts/dev.js` is available through this interface - Install globally with `npm install -g claude-task-master` or use locally via `npx` - Use `task-master ` instead of `node scripts/dev.js ` - Examples: - - `task-master list` instead of `node scripts/dev.js list` - - `task-master next` instead of `node scripts/dev.js next` - - `task-master expand --id=3` instead of `node scripts/dev.js expand --id=3` + - `task-master list` + - `task-master next` + - `task-master expand --id=3` - All commands accept the same options as their script equivalents - - The CLI provides additional commands like `task-master init` for project setup + - The CLI (`task-master`) is the **primary** way for users to interact with the application. - **Development Workflow Process** - Start new projects by running `task-master init` or `node scripts/dev.js parse-prd --input=` to generate initial tasks.json @@ -32,6 +32,7 @@ alwaysApply: true - Generate task files with `task-master generate` after updating tasks.json - Maintain valid dependency structure with `task-master fix-dependencies` when needed - Respect dependency chains and task priorities when selecting work + - **MCP Server**: For integrations (like Cursor), interact via the MCP server which prefers direct function calls. Restart the MCP server if core logic in `scripts/modules` changes. See [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc). - Report progress regularly using the list command - **Task Complexity Analysis** @@ -49,7 +50,7 @@ alwaysApply: true - Use `--prompt=""` to provide additional context when needed - Review and adjust generated subtasks as necessary - Use `--all` flag to expand multiple pending tasks at once - - If subtasks need regeneration, clear them first with `clear-subtasks` command + - If subtasks need regeneration, clear them first with `clear-subtasks` command (See Command Reference below) - **Implementation Drift Handling** - When implementation differs significantly from planned approach @@ -79,16 +80,14 @@ alwaysApply: true ``` - **Command Reference: parse-prd** - - Legacy Syntax: `node scripts/dev.js parse-prd --input=` - CLI Syntax: `task-master parse-prd --input=` - - Description: Parses a PRD document and generates a tasks.json file with structured tasks + - Description: Parses a PRD document and generates a `tasks.json` file with structured tasks - Parameters: - `--input=`: Path to the PRD text file (default: sample-prd.txt) - Example: `task-master parse-prd --input=requirements.txt` - Notes: Will overwrite existing tasks.json file. Use with caution. - **Command Reference: update** - - Legacy Syntax: `node scripts/dev.js update --from= --prompt=""` - CLI Syntax: `task-master update --from= --prompt=""` - Description: Updates tasks with ID >= specified ID based on the provided prompt - Parameters: @@ -98,7 +97,6 @@ alwaysApply: true - Notes: Only updates tasks not marked as 'done'. Completed tasks remain unchanged. - **Command Reference: update-task** - - Legacy Syntax: `node scripts/dev.js update-task --id= --prompt=""` - CLI Syntax: `task-master update-task --id= --prompt=""` - Description: Updates a single task by ID with new information - Parameters: @@ -109,7 +107,6 @@ alwaysApply: true - Notes: Only updates tasks not marked as 'done'. Preserves completed subtasks. - **Command Reference: update-subtask** - - Legacy Syntax: `node scripts/dev.js update-subtask --id= --prompt=""` - CLI Syntax: `task-master update-subtask --id= --prompt=""` - Description: Appends additional information to a specific subtask without replacing existing content - Parameters: @@ -124,7 +121,6 @@ alwaysApply: true - Will not update subtasks marked as 'done' or 'completed' - **Command Reference: generate** - - Legacy Syntax: `node scripts/dev.js generate` - CLI Syntax: `task-master generate` - Description: Generates individual task files in tasks/ directory based on tasks.json - Parameters: @@ -134,7 +130,6 @@ alwaysApply: true - Notes: Overwrites existing task files. Creates tasks/ directory if needed. - **Command Reference: set-status** - - Legacy Syntax: `node scripts/dev.js set-status --id= --status=` - CLI Syntax: `task-master set-status --id= --status=` - Description: Updates the status of a specific task in tasks.json - Parameters: @@ -144,7 +139,6 @@ alwaysApply: true - Notes: Common values are 'done', 'pending', and 'deferred', but any string is accepted. - **Command Reference: list** - - Legacy Syntax: `node scripts/dev.js list` - CLI Syntax: `task-master list` - Description: Lists all tasks in tasks.json with IDs, titles, and status - Parameters: @@ -155,7 +149,6 @@ alwaysApply: true - Notes: Provides quick overview of project progress. Use at start of sessions. - **Command Reference: expand** - - Legacy Syntax: `node scripts/dev.js expand --id= [--num=] [--research] [--prompt=""]` - CLI Syntax: `task-master expand --id= [--num=] [--research] [--prompt=""]` - Description: Expands a task with subtasks for detailed implementation - Parameters: @@ -169,7 +162,6 @@ alwaysApply: true - Notes: Uses complexity report recommendations if available. - **Command Reference: analyze-complexity** - - Legacy Syntax: `node scripts/dev.js analyze-complexity [options]` - CLI Syntax: `task-master analyze-complexity [options]` - Description: Analyzes task complexity and generates expansion recommendations - Parameters: @@ -182,7 +174,6 @@ alwaysApply: true - Notes: Report includes complexity scores, recommended subtasks, and tailored prompts. - **Command Reference: clear-subtasks** - - Legacy Syntax: `node scripts/dev.js clear-subtasks --id=` - CLI Syntax: `task-master clear-subtasks --id=` - Description: Removes subtasks from specified tasks to allow regeneration - Parameters: @@ -256,7 +247,6 @@ alwaysApply: true - Dependencies are visualized with status indicators in task listings and files - **Command Reference: add-dependency** - - Legacy Syntax: `node scripts/dev.js add-dependency --id= --depends-on=` - CLI Syntax: `task-master add-dependency --id= --depends-on=` - Description: Adds a dependency relationship between two tasks - Parameters: @@ -266,7 +256,6 @@ alwaysApply: true - Notes: Prevents circular dependencies and duplicates; updates task files automatically - **Command Reference: remove-dependency** - - Legacy Syntax: `node scripts/dev.js remove-dependency --id= --depends-on=` - CLI Syntax: `task-master remove-dependency --id= --depends-on=` - Description: Removes a dependency relationship between two tasks - Parameters: @@ -276,7 +265,6 @@ alwaysApply: true - Notes: Checks if dependency actually exists; updates task files automatically - **Command Reference: validate-dependencies** - - Legacy Syntax: `node scripts/dev.js validate-dependencies [options]` - CLI Syntax: `task-master validate-dependencies [options]` - Description: Checks for and identifies invalid dependencies in tasks.json and task files - Parameters: @@ -288,7 +276,6 @@ alwaysApply: true - Use before fix-dependencies to audit your task structure - **Command Reference: fix-dependencies** - - Legacy Syntax: `node scripts/dev.js fix-dependencies [options]` - CLI Syntax: `task-master fix-dependencies [options]` - Description: Finds and fixes all invalid dependencies in tasks.json and task files - Parameters: @@ -301,7 +288,6 @@ alwaysApply: true - Provides detailed report of all fixes made - **Command Reference: complexity-report** - - Legacy Syntax: `node scripts/dev.js complexity-report [options]` - CLI Syntax: `task-master complexity-report [options]` - Description: Displays the task complexity analysis report in a formatted, easy-to-read way - Parameters: diff --git a/.cursor/rules/mcp.mdc b/.cursor/rules/mcp.mdc new file mode 100644 index 00000000..0789ddcc --- /dev/null +++ b/.cursor/rules/mcp.mdc @@ -0,0 +1,87 @@ +--- +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/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. + +## Key Principles + +- **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)). +- **Use `executeMCPToolAction`**: This utility function in [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) is the standard wrapper for executing the main logic within an MCP tool's `execute` function. It handles common boilerplate like logging, argument processing, calling the core action (`*Direct` function), and formatting the response. +- **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 (used internally by other utils). + - `handleApiResult`: Standardizes handling results from direct function calls (success/error). + - `createContentResponse`/`createErrorResponse`: Formats successful/error MCP responses. + - `processMCPResponseData`: Filters/cleans data for MCP responses (e.g., removing `details`, `testStrategy`). This is the default processor used by `executeMCPToolAction`. + - `executeMCPToolAction`: The primary wrapper function for tool execution logic. + - `executeTaskMasterCommand`: Fallback for executing CLI commands. +- **Caching**: To improve performance for frequently called read operations (like `listTasks`), a caching layer using `lru-cache` is implemented. + - Caching logic should be added *inside* 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. + - Responses will include a `fromCache` flag. + - Cache statistics can be monitored using the `cacheStats` MCP tool (implemented via `getCacheStatsDirect`). + +## 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 Wrapper**: In [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js): + - Import the core function. + - Import `getCachedOrExecute` from `../tools/utils.js`. + - Create an `async function yourCommandDirect(args, log)` wrapper. + - Inside the wrapper: + - Determine arguments needed for both the core logic and the cache key (e.g., `tasksPath`, filters). Use `findTasksJsonPath(args, log)` if needed. + - **Generate a unique `cacheKey`** based on the arguments that define a distinct operation (e.g., `\`yourCommand:${tasksPath}:${filter}\``). + - **Define the `coreActionFn`**: An `async` function that contains the actual call to the imported core logic function, handling its specific errors and returning `{ success: true/false, data/error }`. + - **Call `getCachedOrExecute`**: + ```javascript + const result = await getCachedOrExecute({ + cacheKey, + actionFn: coreActionFn, // The function wrapping the core logic call + log + }); + return result; // Returns { success, data/error, fromCache } + ``` + - Export the wrapper function and add it to the `directFunctions` map. +3. **Create MCP Tool**: In `mcp-server/src/tools/`: + - Create a new file (e.g., `yourCommand.js`). + - Import `z` for parameter schema definition. + - Import `executeMCPToolAction` from [`./utils.js`](mdc:mcp-server/src/tools/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 `projectRoot` and `file` as optional parameters if relevant. + - Define the `async execute(args, log)` function. + - Inside `execute`, call `executeMCPToolAction`: + ```javascript + return executeMCPToolAction({ + actionFn: yourCommandDirect, // The direct function wrapper + args, // Arguments from the tool call + log, // MCP logger instance + actionName: 'Your Command Description', // For logging + // processResult: customProcessor // Optional: if default filtering isn't enough + }); + ``` +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`. + +## Handling Responses + +- MCP tools should return data formatted by `createContentResponse` (which stringifies objects) or `createErrorResponse`. +- The `processMCPResponseData` utility automatically removes potentially large fields like `details` and `testStrategy` from task objects before they are returned. This is the default behavior when using `executeMCPToolAction`. If specific fields need to be preserved or different fields removed, a custom `processResult` function can be passed to `executeMCPToolAction`. +- The `handleApiResult` utility (used by `executeMCPToolAction`) now expects the result object from the direct function wrapper to include a `fromCache` boolean flag. This flag is included in the final JSON response sent to the MCP client, nested alongside the actual data (e.g., `{ "fromCache": true, "data": { ... } }`). diff --git a/.cursor/rules/new_features.mdc b/.cursor/rules/new_features.mdc index 65287305..51037d35 100644 --- a/.cursor/rules/new_features.mdc +++ b/.cursor/rules/new_features.mdc @@ -8,14 +8,14 @@ alwaysApply: false ## Feature Placement Decision Process -- **Identify Feature Type**: - - **Data Manipulation**: Features that create, read, update, or delete tasks belong in [`task-manager.js`](mdc:scripts/modules/task-manager.js) - - **Dependency Management**: Features that handle task relationships belong in [`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js) - - **User Interface**: Features that display information to users belong in [`ui.js`](mdc:scripts/modules/ui.js) - - **AI Integration**: Features that use AI models belong in [`ai-services.js`](mdc:scripts/modules/ai-services.js) +- **Identify Feature Type** (See [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc) for module details): + - **Data Manipulation**: Features that create, read, update, or delete tasks belong in [`task-manager.js`](mdc:scripts/modules/task-manager.js). Follow guidelines in [`tasks.mdc`](mdc:.cursor/rules/tasks.mdc). + - **Dependency Management**: Features that handle task relationships belong in [`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js). Follow guidelines in [`dependencies.mdc`](mdc:.cursor/rules/dependencies.mdc). + - **User Interface**: Features that display information to users belong in [`ui.js`](mdc:scripts/modules/ui.js). Follow guidelines in [`ui.mdc`](mdc:.cursor/rules/ui.mdc). + - **AI Integration**: Features that use AI models belong in [`ai-services.js`](mdc:scripts/modules/ai-services.js). - **Cross-Cutting**: Features that don't fit one category may need components in multiple modules -- **Command-Line Interface**: +- **Command-Line Interface** (See [`commands.mdc`](mdc:.cursor/rules/commands.mdc)): - All new user-facing commands should be added to [`commands.js`](mdc:scripts/modules/commands.js) - Use consistent patterns for option naming and help text - Follow the Commander.js model for subcommand structure @@ -24,11 +24,11 @@ alwaysApply: false The standard pattern for adding a feature follows this workflow: -1. **Core Logic**: Implement the business logic in the appropriate module -2. **UI Components**: Add any display functions to [`ui.js`](mdc:scripts/modules/ui.js) -3. **Command Integration**: Add the CLI command to [`commands.js`](mdc:scripts/modules/commands.js) +1. **Core Logic**: Implement the business logic in the appropriate module (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)). +2. **UI Components**: Add any display functions to [`ui.js`](mdc:scripts/modules/ui.js) following [`ui.mdc`](mdc:.cursor/rules/ui.mdc). +3. **Command Integration**: Add the CLI command to [`commands.js`](mdc:scripts/modules/commands.js) following [`commands.mdc`](mdc:.cursor/rules/commands.mdc). 4. **Testing**: Write tests for all components of the feature (following [`tests.mdc`](mdc:.cursor/rules/tests.mdc)) -5. **Configuration**: Update any configuration in [`utils.js`](mdc:scripts/modules/utils.js) if needed +5. **Configuration**: Update any configuration in [`utils.js`](mdc:scripts/modules/utils.js) if needed, following [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc). 6. **Documentation**: Update help text and documentation in [dev_workflow.mdc](mdc:scripts/modules/dev_workflow.mdc) ```javascript @@ -294,7 +294,7 @@ For each new feature: 1. Add help text to the command definition 2. Update [`dev_workflow.mdc`](mdc:scripts/modules/dev_workflow.mdc) with command reference -3. Add examples to the appropriate sections in [`MODULE_PLAN.md`](mdc:scripts/modules/MODULE_PLAN.md) +3. Consider updating [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc) if the feature significantly changes module responsibilities. Follow the existing command reference format: ```markdown @@ -309,3 +309,51 @@ Follow the existing command reference format: ``` For more information on module structure, see [`MODULE_PLAN.md`](mdc:scripts/modules/MODULE_PLAN.md) and follow [`self_improve.mdc`](mdc:scripts/modules/self_improve.mdc) for best practices on updating documentation. + +## Adding MCP Server Support for Commands + +Integrating Task Master commands with the MCP server (for use by tools like Cursor) follows a specific pattern distinct from the CLI command implementation. + +- **Goal**: Leverage direct function calls for performance and reliability, avoiding CLI overhead. +- **Reference**: See [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc) for full details. + +**MCP Integration Workflow**: + +1. **Core Logic**: Ensure the command's core logic exists in the appropriate module (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)). +2. **Direct Function Wrapper**: + - In [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js), create an `async function yourCommandDirect(args, log)`. + - This function imports and calls the core logic. + - It uses utilities like `findTasksJsonPath` if needed. + - It handles argument parsing and validation specific to the direct call. + - **Implement Caching (if applicable)**: For read operations that benefit from caching, use the `getCachedOrExecute` utility here to wrap the core logic call. Generate a unique cache key based on relevant arguments. + - It returns a standard `{ success: true/false, data/error, fromCache: boolean }` object. + - Export the function and add it to the `directFunctions` map. +3. **MCP Tool File**: + - Create a new file in `mcp-server/src/tools/` (e.g., `yourCommand.js`). + - Import `zod`, `executeMCPToolAction` from `./utils.js`, and your `yourCommandDirect` function. + - Implement `registerYourCommandTool(server)` which calls `server.addTool`: + - Define the tool `name`, `description`, and `parameters` using `zod`. Include optional `projectRoot` and `file` if relevant, following patterns in existing tools. + - Define the `async execute(args, log)` method for the tool. + - **Crucially**, the `execute` method should primarily call `executeMCPToolAction`: + ```javascript + // In mcp-server/src/tools/yourCommand.js + import { executeMCPToolAction } from "./utils.js"; + import { yourCommandDirect } from "../core/task-master-core.js"; + import { z } from "zod"; + + export function registerYourCommandTool(server) { + server.addTool({ + name: "yourCommand", + description: "Description of your command.", + parameters: z.object({ /* zod schema */ }), + async execute(args, log) { + return executeMCPToolAction({ + actionFn: yourCommandDirect, // Pass the direct function wrapper + args, log, actionName: "Your Command Description" + }); + } + }); + } + ``` +4. **Register in Tool Index**: Import and call `registerYourCommandTool` in [`mcp-server/src/tools/index.js`](mdc:mcp-server/src/tools/index.js). +5. **Update `mcp.json`**: Add the tool definition to `.cursor/mcp.json`. diff --git a/.cursor/rules/tests.mdc b/.cursor/rules/tests.mdc index b533c89f..60681478 100644 --- a/.cursor/rules/tests.mdc +++ b/.cursor/rules/tests.mdc @@ -7,7 +7,7 @@ globs: "**/*.test.js,tests/**/*" ## Test Organization Structure -- **Unit Tests** +- **Unit Tests** (See [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc) for module breakdown) - Located in `tests/unit/` - Test individual functions and utilities in isolation - Mock all external dependencies @@ -324,7 +324,7 @@ When testing ES modules (`"type": "module"` in package.json), traditional mockin ## Testing Common Components - **CLI Commands** - - Mock the action handlers and verify they're called with correct arguments + - Mock the action handlers (defined in [`commands.js`](mdc:scripts/modules/commands.js)) and verify they're called with correct arguments - Test command registration and option parsing - Use `commander` test utilities or custom mocks diff --git a/.cursor/rules/utilities.mdc b/.cursor/rules/utilities.mdc index a8f7108c..7368be15 100644 --- a/.cursor/rules/utilities.mdc +++ b/.cursor/rules/utilities.mdc @@ -1,6 +1,6 @@ --- description: Guidelines for implementing utility functions -globs: scripts/modules/utils.js +globs: scripts/modules/utils.js, mcp-server/src/**/* alwaysApply: false --- @@ -273,13 +273,81 @@ alwaysApply: false } ``` +## MCP Server Utilities (`mcp-server/src/tools/utils.js`) + +- **Purpose**: These utilities specifically support the MCP server tools, handling communication patterns and data formatting for MCP clients. Refer to [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc) for usage patterns. + +-(See also: [`tests.mdc`](mdc:.cursor/rules/tests.mdc) for testing these utilities) + +- **`getProjectRoot(projectRootRaw, log)`**: + - Normalizes a potentially relative project root path into an absolute path. + - Defaults to `process.cwd()` if `projectRootRaw` is not provided. + - Primarily used *internally* by `executeMCPToolAction` and `executeTaskMasterCommand`. Tools usually don't need to call this directly. + +- **`executeMCPToolAction({ actionFn, args, log, actionName, processResult })`**: + - ✅ **DO**: Use this as the main wrapper inside an MCP tool's `execute` method when calling a direct function wrapper. + - Handles standard workflow: logs action start, normalizes `projectRoot`, calls the `actionFn` (e.g., `listTasksDirect`), processes the result (using `handleApiResult`), logs success/error, and returns a formatted MCP response (`createContentResponse`/`createErrorResponse`). + - Simplifies tool implementation significantly by handling boilerplate. + - Accepts an optional `processResult` function to customize data filtering/transformation before sending the response (defaults to `processMCPResponseData`). + +- **`handleApiResult(result, log, errorPrefix, processFunction)`**: + - Takes the standard `{ success, data/error }` object returned by direct function wrappers (like `listTasksDirect`). + - Checks the `success` flag. + - If successful, processes the `data` using `processFunction` (defaults to `processMCPResponseData`). + - Returns a formatted MCP response object using `createContentResponse` or `createErrorResponse`. + - Typically called *internally* by `executeMCPToolAction`. + +- **`executeTaskMasterCommand(command, log, args, projectRootRaw)`**: + - Executes a Task Master command using `child_process.spawnSync`. + - Tries the global `task-master` command first, then falls back to `node scripts/dev.js`. + - Handles project root normalization internally. + - Returns `{ success, stdout, stderr }` or `{ success: false, error }`. + - ❌ **DON'T**: Use this as the primary method for MCP tools. Prefer `executeMCPToolAction` with direct function calls. Use only as a fallback for commands not yet refactored or those requiring CLI execution. + +- **`processMCPResponseData(taskOrData, fieldsToRemove = ['details', 'testStrategy'])`**: + - Filters task data before sending it to the MCP client. + - By default, removes the `details` and `testStrategy` fields from task objects and their subtasks to reduce payload size. + - Can handle single task objects or data structures containing a `tasks` array (like from `listTasks`). + - This is the default processor used by `executeMCPToolAction`. + + ```javascript + // Example usage (typically done inside executeMCPToolAction): + const rawResult = { success: true, data: { tasks: [ { id: 1, title: '...', details: '...', subtasks: [...] } ] } }; + const filteredData = processMCPResponseData(rawResult.data); + // filteredData.tasks[0] will NOT have the 'details' field. + ``` + +- **`createContentResponse(content)`**: + - ✅ **DO**: Use this (usually via `handleApiResult` or `executeMCPToolAction`) to format successful MCP responses. + - Wraps the `content` (stringifies objects to JSON) in the standard FastMCP `{ content: [{ type: "text", text: ... }] }` structure. + +- **`createErrorResponse(errorMessage)`**: + - ✅ **DO**: Use this (usually via `handleApiResult` or `executeMCPToolAction`) to format error responses for MCP. + - Wraps the `errorMessage` in the standard FastMCP error structure, including `isError: true`. + +- **`getCachedOrExecute({ cacheKey, actionFn, log })`**: + - ✅ **DO**: Use this utility *inside direct function wrappers* (like `listTasksDirect` in `task-master-core.js`) to implement caching for MCP operations. + - Checks the `ContextManager` cache using `cacheKey`. + - If a hit occurs, returns the cached result directly. + - If a miss occurs, it executes the provided `actionFn` (which should be an async function returning `{ success, data/error }`). + - If `actionFn` succeeds, its result is stored in the cache under `cacheKey`. + - Returns the result (either cached or fresh) wrapped in the standard structure `{ success, data/error, fromCache: boolean }`. + +- **`executeMCPToolAction({ actionFn, args, log, actionName, processResult })`**: + - Update: While this function *can* technically coordinate caching if provided a `cacheKeyGenerator`, the current preferred pattern involves implementing caching *within* the `actionFn` (the direct wrapper) using `getCachedOrExecute`. `executeMCPToolAction` primarily orchestrates the call to `actionFn` and handles processing its result (including the `fromCache` flag) via `handleApiResult`. + +- **`handleApiResult(result, log, errorPrefix, processFunction)`**: + - Update: Now expects the `result` object to potentially contain a `fromCache` boolean flag. If present, this flag is included in the final response payload generated by `createContentResponse` (e.g., `{ fromCache: true, data: ... }`). + ## Export Organization - **Grouping Related Functions**: - - ✅ DO: Export all utility functions in a single statement - - ✅ DO: Group related exports together - - ✅ DO: Export configuration constants - - ❌ DON'T: Use default exports + - ✅ DO: Keep utilities relevant to their location (e.g., core utils in `scripts/modules/utils.js`, MCP utils in `mcp-server/src/tools/utils.js`). + - ✅ DO: Export all utility functions in a single statement per file. + - ✅ DO: Group related exports together. + - ✅ DO: Export configuration constants. + - ❌ DON'T: Use default exports. + - ❌ DON'T: Create circular dependencies between utility files or between utilities and the modules that use them (See [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc)). ```javascript // ✅ DO: Organize exports logically @@ -311,4 +379,4 @@ alwaysApply: false }; ``` -Refer to [`utils.js`](mdc:scripts/modules/utils.js) for implementation examples and [`new_features.mdc`](mdc:.cursor/rules/new_features.mdc) for integration guidelines. \ No newline at end of file +Refer to [`utils.js`](mdc:scripts/modules/utils.js) for implementation examples and [`new_features.mdc`](mdc:.cursor/rules/new_features.mdc) for integration guidelines. Use [`commands.mdc`](mdc:.cursor/rules/commands.mdc) for CLI integration details. \ No newline at end of file diff --git a/docs/mcp-protocol-schema-03262025.json b/docs/mcp-protocol-schema-03262025.json new file mode 100644 index 00000000..e7f730e5 --- /dev/null +++ b/docs/mcp-protocol-schema-03262025.json @@ -0,0 +1,2128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "description": "Describes who the intended customer of this object or data is.\n\nIt can include multiple entries to indicate content useful for multiple audiences (e.g., `[\"user\", \"assistant\"]`).", + "items": { + "$ref": "#/definitions/Role" + }, + "type": "array" + }, + "priority": { + "description": "Describes how important this data is for operating the server.\n\nA value of 1 means \"most important,\" and indicates that the data is\neffectively required, while 0 means \"least important,\" and indicates that\nthe data is entirely optional.", + "maximum": 1, + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "annotations": { + "$ref": "#/definitions/Annotations", + "description": "Optional annotations for the client." + }, + "data": { + "description": "The base64-encoded audio data.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of the audio. Different providers may support different audio types.", + "type": "string" + }, + "type": { + "const": "audio", + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "blob": { + "description": "A base64-encoded string representing the binary data of the item.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "CallToolRequest": { + "description": "Used by the client to invoke a tool provided by the server.", + "properties": { + "method": { + "const": "tools/call", + "type": "string" + }, + "params": { + "properties": { + "arguments": { + "additionalProperties": {}, + "type": "object" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "content": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "type": "array" + }, + "isError": { + "description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).", + "type": "boolean" + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "CancelledNotification": { + "description": "This notification can be sent by either side to indicate that it is cancelling a previously-issued request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.\n\nA client MUST NOT attempt to cancel its `initialize` request.", + "properties": { + "method": { + "const": "notifications/cancelled", + "type": "string" + }, + "params": { + "properties": { + "reason": { + "description": "An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.", + "type": "string" + }, + "requestId": { + "$ref": "#/definitions/RequestId", + "description": "The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request previously issued in the same direction." + } + }, + "required": [ + "requestId" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "ClientCapabilities": { + "description": "Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.", + "properties": { + "experimental": { + "additionalProperties": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "description": "Experimental, non-standard capabilities that the client supports.", + "type": "object" + }, + "roots": { + "description": "Present if the client supports listing roots.", + "properties": { + "listChanged": { + "description": "Whether the client supports notifications for changes to the roots list.", + "type": "boolean" + } + }, + "type": "object" + }, + "sampling": { + "additionalProperties": true, + "description": "Present if the client supports sampling from an LLM.", + "properties": {}, + "type": "object" + } + }, + "type": "object" + }, + "ClientNotification": { + "anyOf": [ + { + "$ref": "#/definitions/CancelledNotification" + }, + { + "$ref": "#/definitions/InitializedNotification" + }, + { + "$ref": "#/definitions/ProgressNotification" + }, + { + "$ref": "#/definitions/RootsListChangedNotification" + } + ] + }, + "ClientRequest": { + "anyOf": [ + { + "$ref": "#/definitions/InitializeRequest" + }, + { + "$ref": "#/definitions/PingRequest" + }, + { + "$ref": "#/definitions/ListResourcesRequest" + }, + { + "$ref": "#/definitions/ReadResourceRequest" + }, + { + "$ref": "#/definitions/SubscribeRequest" + }, + { + "$ref": "#/definitions/UnsubscribeRequest" + }, + { + "$ref": "#/definitions/ListPromptsRequest" + }, + { + "$ref": "#/definitions/GetPromptRequest" + }, + { + "$ref": "#/definitions/ListToolsRequest" + }, + { + "$ref": "#/definitions/CallToolRequest" + }, + { + "$ref": "#/definitions/SetLevelRequest" + }, + { + "$ref": "#/definitions/CompleteRequest" + } + ] + }, + "ClientResult": { + "anyOf": [ + { + "$ref": "#/definitions/Result" + }, + { + "$ref": "#/definitions/CreateMessageResult" + }, + { + "$ref": "#/definitions/ListRootsResult" + } + ] + }, + "CompleteRequest": { + "description": "A request from the client to the server, to ask for completion options.", + "properties": { + "method": { + "const": "completion/complete", + "type": "string" + }, + "params": { + "properties": { + "argument": { + "description": "The argument's information", + "properties": { + "name": { + "description": "The name of the argument", + "type": "string" + }, + "value": { + "description": "The value of the argument to use for completion matching.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "ref": { + "anyOf": [ + { + "$ref": "#/definitions/PromptReference" + }, + { + "$ref": "#/definitions/ResourceReference" + } + ] + } + }, + "required": [ + "argument", + "ref" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "CompleteResult": { + "description": "The server's response to a completion/complete request", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "completion": { + "properties": { + "hasMore": { + "description": "Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.", + "type": "boolean" + }, + "total": { + "description": "The total number of completion options available. This can exceed the number of values actually sent in the response.", + "type": "integer" + }, + "values": { + "description": "An array of completion values. Must not exceed 100 items.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "values" + ], + "type": "object" + } + }, + "required": [ + "completion" + ], + "type": "object" + }, + "CreateMessageRequest": { + "description": "A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.", + "properties": { + "method": { + "const": "sampling/createMessage", + "type": "string" + }, + "params": { + "properties": { + "includeContext": { + "description": "A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request.", + "enum": [ + "allServers", + "none", + "thisServer" + ], + "type": "string" + }, + "maxTokens": { + "description": "The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested.", + "type": "integer" + }, + "messages": { + "items": { + "$ref": "#/definitions/SamplingMessage" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "description": "Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.", + "properties": {}, + "type": "object" + }, + "modelPreferences": { + "$ref": "#/definitions/ModelPreferences", + "description": "The server's preferences for which model to select. The client MAY ignore these preferences." + }, + "stopSequences": { + "items": { + "type": "string" + }, + "type": "array" + }, + "systemPrompt": { + "description": "An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.", + "type": "string" + }, + "temperature": { + "type": "number" + } + }, + "required": [ + "maxTokens", + "messages" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "CreateMessageResult": { + "description": "The client's response to a sampling/create_message request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "content": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + } + ] + }, + "model": { + "description": "The name of the model that generated the message.", + "type": "string" + }, + "role": { + "$ref": "#/definitions/Role" + }, + "stopReason": { + "description": "The reason why sampling stopped, if known.", + "type": "string" + } + }, + "required": [ + "content", + "model", + "role" + ], + "type": "object" + }, + "Cursor": { + "description": "An opaque token used to represent a cursor for pagination.", + "type": "string" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", + "properties": { + "annotations": { + "$ref": "#/definitions/Annotations", + "description": "Optional annotations for the client." + }, + "resource": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "type": { + "const": "resource", + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmptyResult": { + "$ref": "#/definitions/Result" + }, + "GetPromptRequest": { + "description": "Used by the client to get a prompt provided by the server.", + "properties": { + "method": { + "const": "prompts/get", + "type": "string" + }, + "params": { + "properties": { + "arguments": { + "additionalProperties": { + "type": "string" + }, + "description": "Arguments to use for templating the prompt.", + "type": "object" + }, + "name": { + "description": "The name of the prompt or prompt template.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "GetPromptResult": { + "description": "The server's response to a prompts/get request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "description": { + "description": "An optional description for the prompt.", + "type": "string" + }, + "messages": { + "items": { + "$ref": "#/definitions/PromptMessage" + }, + "type": "array" + } + }, + "required": [ + "messages" + ], + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "annotations": { + "$ref": "#/definitions/Annotations", + "description": "Optional annotations for the client." + }, + "data": { + "description": "The base64-encoded image data.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of the image. Different providers may support different image types.", + "type": "string" + }, + "type": { + "const": "image", + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "Implementation": { + "description": "Describes the name and version of an MCP implementation.", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "InitializeRequest": { + "description": "This request is sent from the client to the server when it first connects, asking it to begin initialization.", + "properties": { + "method": { + "const": "initialize", + "type": "string" + }, + "params": { + "properties": { + "capabilities": { + "$ref": "#/definitions/ClientCapabilities" + }, + "clientInfo": { + "$ref": "#/definitions/Implementation" + }, + "protocolVersion": { + "description": "The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.", + "type": "string" + } + }, + "required": [ + "capabilities", + "clientInfo", + "protocolVersion" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "InitializeResult": { + "description": "After receiving an initialize request from the client, the server sends this response.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "capabilities": { + "$ref": "#/definitions/ServerCapabilities" + }, + "instructions": { + "description": "Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a \"hint\" to the model. For example, this information MAY be added to the system prompt.", + "type": "string" + }, + "protocolVersion": { + "description": "The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect.", + "type": "string" + }, + "serverInfo": { + "$ref": "#/definitions/Implementation" + } + }, + "required": [ + "capabilities", + "protocolVersion", + "serverInfo" + ], + "type": "object" + }, + "InitializedNotification": { + "description": "This notification is sent from the client to the server after initialization has finished.", + "properties": { + "method": { + "const": "notifications/initialized", + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "JSONRPCBatchRequest": { + "description": "A JSON-RPC batch request, as described in https://www.jsonrpc.org/specification#batch.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCRequest" + }, + { + "$ref": "#/definitions/JSONRPCNotification" + } + ] + }, + "type": "array" + }, + "JSONRPCBatchResponse": { + "description": "A JSON-RPC batch response, as described in https://www.jsonrpc.org/specification#batch.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCResponse" + }, + { + "$ref": "#/definitions/JSONRPCError" + } + ] + }, + "type": "array" + }, + "JSONRPCError": { + "description": "A response to a request that indicates an error occurred.", + "properties": { + "error": { + "properties": { + "code": { + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "id": { + "$ref": "#/definitions/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + } + }, + "required": [ + "error", + "id", + "jsonrpc" + ], + "type": "object" + }, + "JSONRPCMessage": { + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCRequest" + }, + { + "$ref": "#/definitions/JSONRPCNotification" + }, + { + "description": "A JSON-RPC batch request, as described in https://www.jsonrpc.org/specification#batch.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCRequest" + }, + { + "$ref": "#/definitions/JSONRPCNotification" + } + ] + }, + "type": "array" + }, + { + "$ref": "#/definitions/JSONRPCResponse" + }, + { + "$ref": "#/definitions/JSONRPCError" + }, + { + "description": "A JSON-RPC batch response, as described in https://www.jsonrpc.org/specification#batch.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCResponse" + }, + { + "$ref": "#/definitions/JSONRPCError" + } + ] + }, + "type": "array" + } + ], + "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent." + }, + "JSONRPCNotification": { + "description": "A notification which does not expect a response.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "JSONRPCRequest": { + "description": "A request that expects a response.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "properties": { + "progressToken": { + "$ref": "#/definitions/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "JSONRPCResponse": { + "description": "A successful (non-error) response to a request.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/definitions/Result" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "ListPromptsRequest": { + "description": "Sent from the client to request a list of prompts and prompt templates the server has.", + "properties": { + "method": { + "const": "prompts/list", + "type": "string" + }, + "params": { + "properties": { + "cursor": { + "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ListPromptsResult": { + "description": "The server's response to a prompts/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "prompts": { + "items": { + "$ref": "#/definitions/Prompt" + }, + "type": "array" + } + }, + "required": [ + "prompts" + ], + "type": "object" + }, + "ListResourceTemplatesRequest": { + "description": "Sent from the client to request a list of resource templates the server has.", + "properties": { + "method": { + "const": "resources/templates/list", + "type": "string" + }, + "params": { + "properties": { + "cursor": { + "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ListResourceTemplatesResult": { + "description": "The server's response to a resources/templates/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resourceTemplates": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + } + }, + "required": [ + "resourceTemplates" + ], + "type": "object" + }, + "ListResourcesRequest": { + "description": "Sent from the client to request a list of resources the server has.", + "properties": { + "method": { + "const": "resources/list", + "type": "string" + }, + "params": { + "properties": { + "cursor": { + "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ListResourcesResult": { + "description": "The server's response to a resources/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resources": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + } + }, + "required": [ + "resources" + ], + "type": "object" + }, + "ListRootsRequest": { + "description": "Sent from the server to request a list of root URIs from the client. Roots allow\nservers to ask for specific directories or files to operate on. A common example\nfor roots is providing a set of repositories or directories a server should operate\non.\n\nThis request is typically used when the server needs to understand the file system\nstructure or access specific locations that the client has permission to read from.", + "properties": { + "method": { + "const": "roots/list", + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "properties": { + "progressToken": { + "$ref": "#/definitions/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ListRootsResult": { + "description": "The client's response to a roots/list request from the server.\nThis result contains an array of Root objects, each representing a root directory\nor file that the server can operate on.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "roots": { + "items": { + "$ref": "#/definitions/Root" + }, + "type": "array" + } + }, + "required": [ + "roots" + ], + "type": "object" + }, + "ListToolsRequest": { + "description": "Sent from the client to request a list of tools the server has.", + "properties": { + "method": { + "const": "tools/list", + "type": "string" + }, + "params": { + "properties": { + "cursor": { + "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ListToolsResult": { + "description": "The server's response to a tools/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "tools": { + "items": { + "$ref": "#/definitions/Tool" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "LoggingLevel": { + "description": "The severity of a log message.\n\nThese map to syslog message severities, as specified in RFC-5424:\nhttps://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1", + "enum": [ + "alert", + "critical", + "debug", + "emergency", + "error", + "info", + "notice", + "warning" + ], + "type": "string" + }, + "LoggingMessageNotification": { + "description": "Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.", + "properties": { + "method": { + "const": "notifications/message", + "type": "string" + }, + "params": { + "properties": { + "data": { + "description": "The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here." + }, + "level": { + "$ref": "#/definitions/LoggingLevel", + "description": "The severity of this log message." + }, + "logger": { + "description": "An optional name of the logger issuing this message.", + "type": "string" + } + }, + "required": [ + "data", + "level" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "ModelHint": { + "description": "Hints to use for model selection.\n\nKeys not declared here are currently left unspecified by the spec and are up\nto the client to interpret.", + "properties": { + "name": { + "description": "A hint for a model name.\n\nThe client SHOULD treat this as a substring of a model name; for example:\n - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022`\n - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc.\n - `claude` should match any Claude model\n\nThe client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example:\n - `gemini-1.5-flash` could match `claude-3-haiku-20240307`", + "type": "string" + } + }, + "type": "object" + }, + "ModelPreferences": { + "description": "The server's preferences for model selection, requested of the client during sampling.\n\nBecause LLMs can vary along multiple dimensions, choosing the \"best\" model is\nrarely straightforward. Different models excel in different areas—some are\nfaster but less capable, others are more capable but more expensive, and so\non. This interface allows servers to express their priorities across multiple\ndimensions to help clients make an appropriate selection for their use case.\n\nThese preferences are always advisory. The client MAY ignore them. It is also\nup to the client to decide how to interpret these preferences and how to\nbalance them against other considerations.", + "properties": { + "costPriority": { + "description": "How much to prioritize cost when selecting a model. A value of 0 means cost\nis not important, while a value of 1 means cost is the most important\nfactor.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "hints": { + "description": "Optional hints to use for model selection.\n\nIf multiple hints are specified, the client MUST evaluate them in order\n(such that the first match is taken).\n\nThe client SHOULD prioritize these hints over the numeric priorities, but\nMAY still use the priorities to select from ambiguous matches.", + "items": { + "$ref": "#/definitions/ModelHint" + }, + "type": "array" + }, + "intelligencePriority": { + "description": "How much to prioritize intelligence and capabilities when selecting a\nmodel. A value of 0 means intelligence is not important, while a value of 1\nmeans intelligence is the most important factor.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "speedPriority": { + "description": "How much to prioritize sampling speed (latency) when selecting a model. A\nvalue of 0 means speed is not important, while a value of 1 means speed is\nthe most important factor.", + "maximum": 1, + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "Notification": { + "properties": { + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "PaginatedRequest": { + "properties": { + "method": { + "type": "string" + }, + "params": { + "properties": { + "cursor": { + "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "PaginatedResult": { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + } + }, + "type": "object" + }, + "PingRequest": { + "description": "A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.", + "properties": { + "method": { + "const": "ping", + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "properties": { + "progressToken": { + "$ref": "#/definitions/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ProgressNotification": { + "description": "An out-of-band notification used to inform the receiver of a progress update for a long-running request.", + "properties": { + "method": { + "const": "notifications/progress", + "type": "string" + }, + "params": { + "properties": { + "message": { + "description": "An optional message describing the current progress.", + "type": "string" + }, + "progress": { + "description": "The progress thus far. This should increase every time progress is made, even if the total is unknown.", + "type": "number" + }, + "progressToken": { + "$ref": "#/definitions/ProgressToken", + "description": "The progress token which was given in the initial request, used to associate this notification with the request that is proceeding." + }, + "total": { + "description": "Total number of items to process (or total progress required), if known.", + "type": "number" + } + }, + "required": [ + "progress", + "progressToken" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "ProgressToken": { + "description": "A progress token, used to associate progress notifications with the original request.", + "type": [ + "string", + "integer" + ] + }, + "Prompt": { + "description": "A prompt or prompt template that the server offers.", + "properties": { + "arguments": { + "description": "A list of arguments to use for templating the prompt.", + "items": { + "$ref": "#/definitions/PromptArgument" + }, + "type": "array" + }, + "description": { + "description": "An optional description of what this prompt provides", + "type": "string" + }, + "name": { + "description": "The name of the prompt or prompt template.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "PromptArgument": { + "description": "Describes an argument that a prompt can accept.", + "properties": { + "description": { + "description": "A human-readable description of the argument.", + "type": "string" + }, + "name": { + "description": "The name of the argument.", + "type": "string" + }, + "required": { + "description": "Whether this argument must be provided.", + "type": "boolean" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "PromptListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "method": { + "const": "notifications/prompts/list_changed", + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "PromptMessage": { + "description": "Describes a message returned as part of a prompt.\n\nThis is similar to `SamplingMessage`, but also supports the embedding of\nresources from the MCP server.", + "properties": { + "content": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "role": { + "$ref": "#/definitions/Role" + } + }, + "required": [ + "content", + "role" + ], + "type": "object" + }, + "PromptReference": { + "description": "Identifies a prompt.", + "properties": { + "name": { + "description": "The name of the prompt or prompt template", + "type": "string" + }, + "type": { + "const": "ref/prompt", + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "ReadResourceRequest": { + "description": "Sent from the client to the server, to read a specific resource URI.", + "properties": { + "method": { + "const": "resources/read", + "type": "string" + }, + "params": { + "properties": { + "uri": { + "description": "The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "ReadResourceResult": { + "description": "The server's response to a resources/read request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "contents": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/TextResourceContents" + }, + { + "$ref": "#/definitions/BlobResourceContents" + } + ] + }, + "type": "array" + } + }, + "required": [ + "contents" + ], + "type": "object" + }, + "Request": { + "properties": { + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "properties": { + "progressToken": { + "$ref": "#/definitions/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "RequestId": { + "description": "A uniquely identifying ID for a request in JSON-RPC.", + "type": [ + "string", + "integer" + ] + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "annotations": { + "$ref": "#/definitions/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "name": { + "description": "A human-readable name for this resource.\n\nThis can be used by clients to populate UI elements.", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceContents": { + "description": "The contents of a specific resource or sub-resource.", + "properties": { + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "ResourceListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "method": { + "const": "notifications/resources/list_changed", + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ResourceReference": { + "description": "A reference to a resource or resource template definition.", + "properties": { + "type": { + "const": "ref/resource", + "type": "string" + }, + "uri": { + "description": "The URI or URI template of the resource.", + "format": "uri-template", + "type": "string" + } + }, + "required": [ + "type", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": { + "$ref": "#/definitions/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this template is for.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "type": "string" + }, + "mimeType": { + "description": "The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.", + "type": "string" + }, + "name": { + "description": "A human-readable name for the type of resource this template refers to.\n\nThis can be used by clients to populate UI elements.", + "type": "string" + }, + "uriTemplate": { + "description": "A URI template (according to RFC 6570) that can be used to construct resource URIs.", + "format": "uri-template", + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResourceUpdatedNotification": { + "description": "A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.", + "properties": { + "method": { + "const": "notifications/resources/updated", + "type": "string" + }, + "params": { + "properties": { + "uri": { + "description": "The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "Result": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + } + }, + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "Root": { + "description": "Represents a root directory or file that the server can operate on.", + "properties": { + "name": { + "description": "An optional name for the root. This can be used to provide a human-readable\nidentifier for the root, which may be useful for display purposes or for\nreferencing the root in other parts of the application.", + "type": "string" + }, + "uri": { + "description": "The URI identifying the root. This *must* start with file:// for now.\nThis restriction may be relaxed in future versions of the protocol to allow\nother URI schemes.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "RootsListChangedNotification": { + "description": "A notification from the client to the server, informing it that the list of roots has changed.\nThis notification should be sent whenever the client adds, removes, or modifies any root.\nThe server should then request an updated list of roots using the ListRootsRequest.", + "properties": { + "method": { + "const": "notifications/roots/list_changed", + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "SamplingMessage": { + "description": "Describes a message issued to or received from an LLM API.", + "properties": { + "content": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + } + ] + }, + "role": { + "$ref": "#/definitions/Role" + } + }, + "required": [ + "content", + "role" + ], + "type": "object" + }, + "ServerCapabilities": { + "description": "Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.", + "properties": { + "completions": { + "additionalProperties": true, + "description": "Present if the server supports argument autocompletion suggestions.", + "properties": {}, + "type": "object" + }, + "experimental": { + "additionalProperties": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "description": "Experimental, non-standard capabilities that the server supports.", + "type": "object" + }, + "logging": { + "additionalProperties": true, + "description": "Present if the server supports sending log messages to the client.", + "properties": {}, + "type": "object" + }, + "prompts": { + "description": "Present if the server offers any prompt templates.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the prompt list.", + "type": "boolean" + } + }, + "type": "object" + }, + "resources": { + "description": "Present if the server offers any resources to read.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the resource list.", + "type": "boolean" + }, + "subscribe": { + "description": "Whether this server supports subscribing to resource updates.", + "type": "boolean" + } + }, + "type": "object" + }, + "tools": { + "description": "Present if the server offers any tools to call.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the tool list.", + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ServerNotification": { + "anyOf": [ + { + "$ref": "#/definitions/CancelledNotification" + }, + { + "$ref": "#/definitions/ProgressNotification" + }, + { + "$ref": "#/definitions/ResourceListChangedNotification" + }, + { + "$ref": "#/definitions/ResourceUpdatedNotification" + }, + { + "$ref": "#/definitions/PromptListChangedNotification" + }, + { + "$ref": "#/definitions/ToolListChangedNotification" + }, + { + "$ref": "#/definitions/LoggingMessageNotification" + } + ] + }, + "ServerRequest": { + "anyOf": [ + { + "$ref": "#/definitions/PingRequest" + }, + { + "$ref": "#/definitions/CreateMessageRequest" + }, + { + "$ref": "#/definitions/ListRootsRequest" + } + ] + }, + "ServerResult": { + "anyOf": [ + { + "$ref": "#/definitions/Result" + }, + { + "$ref": "#/definitions/InitializeResult" + }, + { + "$ref": "#/definitions/ListResourcesResult" + }, + { + "$ref": "#/definitions/ReadResourceResult" + }, + { + "$ref": "#/definitions/ListPromptsResult" + }, + { + "$ref": "#/definitions/GetPromptResult" + }, + { + "$ref": "#/definitions/ListToolsResult" + }, + { + "$ref": "#/definitions/CallToolResult" + }, + { + "$ref": "#/definitions/CompleteResult" + } + ] + }, + "SetLevelRequest": { + "description": "A request from the client to the server, to enable or adjust logging.", + "properties": { + "method": { + "const": "logging/setLevel", + "type": "string" + }, + "params": { + "properties": { + "level": { + "$ref": "#/definitions/LoggingLevel", + "description": "The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message." + } + }, + "required": [ + "level" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "SubscribeRequest": { + "description": "Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.", + "properties": { + "method": { + "const": "resources/subscribe", + "type": "string" + }, + "params": { + "properties": { + "uri": { + "description": "The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "annotations": { + "$ref": "#/definitions/Annotations", + "description": "Optional annotations for the client." + }, + "text": { + "description": "The text content of the message.", + "type": "string" + }, + "type": { + "const": "text", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "text": { + "description": "The text of the item. This must only be set if the item can actually be represented as text (not binary data).", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "annotations": { + "$ref": "#/definitions/ToolAnnotations", + "description": "Optional additional tool information." + }, + "description": { + "description": "A human-readable description of the tool.\n\nThis can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a \"hint\" to the model.", + "type": "string" + }, + "inputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "properties": { + "additionalProperties": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "type": "object" + }, + "required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "object", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "name": { + "description": "The name of the tool.", + "type": "string" + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. \nThey are not guaranteed to provide a faithful description of \ntool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations\nreceived from untrusted servers.", + "properties": { + "destructiveHint": { + "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: true", + "type": "boolean" + }, + "idempotentHint": { + "description": "If true, calling the tool repeatedly with the same arguments \nwill have no additional effect on the its environment.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: false", + "type": "boolean" + }, + "openWorldHint": { + "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true", + "type": "boolean" + }, + "readOnlyHint": { + "description": "If true, the tool does not modify its environment.\n\nDefault: false", + "type": "boolean" + }, + "title": { + "description": "A human-readable title for the tool.", + "type": "string" + } + }, + "type": "object" + }, + "ToolListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "method": { + "const": "notifications/tools/list_changed", + "type": "string" + }, + "params": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "UnsubscribeRequest": { + "description": "Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.", + "properties": { + "method": { + "const": "resources/unsubscribe", + "type": "string" + }, + "params": { + "properties": { + "uri": { + "description": "The URI of the resource to unsubscribe from.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + } + } +} diff --git a/mcp-server/src/core/__tests__/context-manager.test.js b/mcp-server/src/core/__tests__/context-manager.test.js new file mode 100644 index 00000000..6e1b1805 --- /dev/null +++ b/mcp-server/src/core/__tests__/context-manager.test.js @@ -0,0 +1,85 @@ +import { jest } from '@jest/globals'; +import { ContextManager } from '../context-manager.js'; + +describe('ContextManager', () => { + let contextManager; + + beforeEach(() => { + contextManager = new ContextManager({ + maxCacheSize: 10, + ttl: 1000, // 1 second for testing + maxContextSize: 1000 + }); + }); + + describe('getContext', () => { + it('should create a new context when not in cache', async () => { + const context = await contextManager.getContext('test-id', { test: true }); + expect(context.id).toBe('test-id'); + expect(context.metadata.test).toBe(true); + expect(contextManager.stats.misses).toBe(1); + expect(contextManager.stats.hits).toBe(0); + }); + + it('should return cached context when available', async () => { + // First call creates the context + await contextManager.getContext('test-id', { test: true }); + + // Second call should hit cache + const context = await contextManager.getContext('test-id', { test: true }); + expect(context.id).toBe('test-id'); + expect(context.metadata.test).toBe(true); + expect(contextManager.stats.hits).toBe(1); + expect(contextManager.stats.misses).toBe(1); + }); + + it('should respect TTL settings', async () => { + // Create context + await contextManager.getContext('test-id', { test: true }); + + // Wait for TTL to expire + await new Promise(resolve => setTimeout(resolve, 1100)); + + // Should create new context + await contextManager.getContext('test-id', { test: true }); + expect(contextManager.stats.misses).toBe(2); + expect(contextManager.stats.hits).toBe(0); + }); + }); + + describe('updateContext', () => { + it('should update existing context metadata', async () => { + await contextManager.getContext('test-id', { initial: true }); + const updated = await contextManager.updateContext('test-id', { updated: true }); + + expect(updated.metadata.initial).toBe(true); + expect(updated.metadata.updated).toBe(true); + }); + }); + + describe('invalidateContext', () => { + it('should remove context from cache', async () => { + await contextManager.getContext('test-id', { test: true }); + contextManager.invalidateContext('test-id', { test: true }); + + // Should be a cache miss + await contextManager.getContext('test-id', { test: true }); + expect(contextManager.stats.invalidations).toBe(1); + expect(contextManager.stats.misses).toBe(2); + }); + }); + + describe('getStats', () => { + it('should return current cache statistics', async () => { + await contextManager.getContext('test-id', { test: true }); + const stats = contextManager.getStats(); + + expect(stats.hits).toBe(0); + expect(stats.misses).toBe(1); + expect(stats.invalidations).toBe(0); + expect(stats.size).toBe(1); + expect(stats.maxSize).toBe(10); + expect(stats.ttl).toBe(1000); + }); + }); +}); \ No newline at end of file diff --git a/mcp-server/src/core/context-manager.js b/mcp-server/src/core/context-manager.js new file mode 100644 index 00000000..ccf98d02 --- /dev/null +++ b/mcp-server/src/core/context-manager.js @@ -0,0 +1,170 @@ +/** + * context-manager.js + * Context and cache management for Task Master MCP Server + */ + +import { FastMCP } from 'fastmcp'; +import { LRUCache } from 'lru-cache'; + +/** + * Configuration options for the ContextManager + * @typedef {Object} ContextManagerConfig + * @property {number} maxCacheSize - Maximum number of items in the cache + * @property {number} ttl - Time to live for cached items in milliseconds + * @property {number} maxContextSize - Maximum size of context window in tokens + */ + +export class ContextManager { + /** + * Create a new ContextManager instance + * @param {ContextManagerConfig} config - Configuration options + */ + constructor(config = {}) { + this.config = { + maxCacheSize: config.maxCacheSize || 1000, + ttl: config.ttl || 1000 * 60 * 5, // 5 minutes default + maxContextSize: config.maxContextSize || 4000 + }; + + // Initialize LRU cache for context data + this.cache = new LRUCache({ + max: this.config.maxCacheSize, + ttl: this.config.ttl, + updateAgeOnGet: true + }); + + // Cache statistics + this.stats = { + hits: 0, + misses: 0, + invalidations: 0 + }; + } + + /** + * Create a new context or retrieve from cache + * @param {string} contextId - Unique identifier for the context + * @param {Object} metadata - Additional metadata for the context + * @returns {Object} Context object with metadata + */ + async getContext(contextId, metadata = {}) { + const cacheKey = this._getCacheKey(contextId, metadata); + + // Try to get from cache first + const cached = this.cache.get(cacheKey); + if (cached) { + this.stats.hits++; + return cached; + } + + this.stats.misses++; + + // Create new context if not in cache + const context = { + id: contextId, + metadata: { + ...metadata, + created: new Date().toISOString() + } + }; + + // Cache the new context + this.cache.set(cacheKey, context); + + return context; + } + + /** + * Update an existing context + * @param {string} contextId - Context identifier + * @param {Object} updates - Updates to apply to the context + * @returns {Object} Updated context + */ + async updateContext(contextId, updates) { + const context = await this.getContext(contextId); + + // Apply updates to context + Object.assign(context.metadata, updates); + + // Update cache + const cacheKey = this._getCacheKey(contextId, context.metadata); + this.cache.set(cacheKey, context); + + return context; + } + + /** + * Invalidate a context in the cache + * @param {string} contextId - Context identifier + * @param {Object} metadata - Metadata used in the cache key + */ + invalidateContext(contextId, metadata = {}) { + const cacheKey = this._getCacheKey(contextId, metadata); + this.cache.delete(cacheKey); + this.stats.invalidations++; + } + + /** + * Get cached data associated with a specific key. + * Increments cache hit stats if found. + * @param {string} key - The cache key. + * @returns {any | undefined} The cached data or undefined if not found/expired. + */ + getCachedData(key) { + const cached = this.cache.get(key); + if (cached !== undefined) { // Check for undefined specifically, as null/false might be valid cached values + this.stats.hits++; + return cached; + } + this.stats.misses++; + return undefined; + } + + /** + * Set data in the cache with a specific key. + * @param {string} key - The cache key. + * @param {any} data - The data to cache. + */ + setCachedData(key, data) { + this.cache.set(key, data); + } + + /** + * Invalidate a specific cache key. + * Increments invalidation stats. + * @param {string} key - The cache key to invalidate. + */ + invalidateCacheKey(key) { + this.cache.delete(key); + this.stats.invalidations++; + } + + /** + * Get cache statistics + * @returns {Object} Cache statistics + */ + getStats() { + return { + hits: this.stats.hits, + misses: this.stats.misses, + invalidations: this.stats.invalidations, + size: this.cache.size, + maxSize: this.config.maxCacheSize, + ttl: this.config.ttl + }; + } + + /** + * Generate a cache key from context ID and metadata + * @private + * @deprecated No longer used for direct cache key generation outside the manager. + * Prefer generating specific keys in calling functions. + */ + _getCacheKey(contextId, metadata) { + // Kept for potential backward compatibility or internal use if needed later. + return `${contextId}:${JSON.stringify(metadata)}`; + } +} + +// Export a singleton instance with default config +export const contextManager = new ContextManager(); \ No newline at end of file diff --git a/mcp-server/src/core/task-master-core.js b/mcp-server/src/core/task-master-core.js new file mode 100644 index 00000000..472cee77 --- /dev/null +++ b/mcp-server/src/core/task-master-core.js @@ -0,0 +1,167 @@ +/** + * task-master-core.js + * Direct function imports from Task Master modules + * + * This module provides direct access to Task Master core functions + * for improved performance and error handling compared to CLI execution. + */ + +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import fs from 'fs'; + +// Get the current module's directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Import Task Master modules +import { + listTasks, + // We'll import more functions as we continue implementation +} from '../../../scripts/modules/task-manager.js'; + +// Import context manager +import { contextManager } from './context-manager.js'; +import { getCachedOrExecute } from '../tools/utils.js'; // Import the utility here + +/** + * Finds the absolute path to the tasks.json file based on project root and arguments. + * @param {Object} args - Command arguments, potentially including 'projectRoot' and 'file'. + * @param {Object} log - Logger object. + * @returns {string} - Absolute path to the tasks.json file. + * @throws {Error} - If tasks.json cannot be found. + */ +function findTasksJsonPath(args, log) { + // Assume projectRoot is already normalized absolute path if passed in args + // Or use getProjectRoot if we decide to centralize that logic + const projectRoot = args.projectRoot || process.cwd(); + log.info(`Searching for tasks.json within project root: ${projectRoot}`); + + const possiblePaths = []; + + // 1. If a file is explicitly provided relative to projectRoot + if (args.file) { + possiblePaths.push(path.resolve(projectRoot, args.file)); + } + + // 2. Check the standard locations relative to projectRoot + possiblePaths.push( + path.join(projectRoot, 'tasks.json'), + path.join(projectRoot, 'tasks', 'tasks.json') + ); + + log.info(`Checking potential task file paths: ${possiblePaths.join(', ')}`); + + // Find the first existing path + for (const p of possiblePaths) { + if (fs.existsSync(p)) { + log.info(`Found tasks file at: ${p}`); + return p; + } + } + + // If no file was found, throw an error + const error = new Error(`Tasks file not found in any of the expected locations relative to ${projectRoot}: ${possiblePaths.join(', ')}`); + error.code = 'TASKS_FILE_NOT_FOUND'; + throw error; +} + +/** + * Direct function wrapper for listTasks with error handling and caching. + * + * @param {Object} args - Command arguments (projectRoot is expected to be resolved). + * @param {Object} log - Logger object. + * @returns {Promise} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }. + */ +export async function listTasksDirect(args, log) { + let tasksPath; + try { + // Find the tasks path first - needed for cache key and execution + tasksPath = findTasksJsonPath(args, log); + } catch (error) { + if (error.code === 'TASKS_FILE_NOT_FOUND') { + log.error(`Tasks file not found: ${error.message}`); + // Return the error structure expected by the calling tool/handler + return { success: false, error: { code: error.code, message: error.message }, fromCache: false }; + } + log.error(`Unexpected error finding tasks file: ${error.message}`); + // Re-throw for outer catch or return structured error + return { success: false, error: { code: 'FIND_TASKS_PATH_ERROR', message: error.message }, fromCache: false }; + } + + // Generate cache key *after* finding tasksPath + const statusFilter = args.status || 'all'; + const withSubtasks = args.withSubtasks || false; + const cacheKey = `listTasks:${tasksPath}:${statusFilter}:${withSubtasks}`; + + // Define the action function to be executed on cache miss + const coreListTasksAction = async () => { + try { + log.info(`Executing core listTasks function for path: ${tasksPath}, filter: ${statusFilter}, subtasks: ${withSubtasks}`); + const resultData = listTasks(tasksPath, statusFilter, withSubtasks, 'json'); + + if (!resultData || !resultData.tasks) { + log.error('Invalid or empty response from listTasks core function'); + return { success: false, error: { code: 'INVALID_CORE_RESPONSE', message: 'Invalid or empty response from listTasks core function' } }; + } + log.info(`Core listTasks function retrieved ${resultData.tasks.length} tasks`); + return { success: true, data: resultData }; + + } catch (error) { + log.error(`Core listTasks function failed: ${error.message}`); + return { success: false, error: { code: 'LIST_TASKS_CORE_ERROR', message: error.message || 'Failed to list tasks' } }; + } + }; + + // Use the caching utility + try { + const result = await getCachedOrExecute({ + cacheKey, + actionFn: coreListTasksAction, + log + }); + log.info(`listTasksDirect completed. From cache: ${result.fromCache}`); + return result; // Returns { success, data/error, fromCache } + } catch(error) { + // Catch unexpected errors from getCachedOrExecute itself (though unlikely) + log.error(`Unexpected error during getCachedOrExecute for listTasks: ${error.message}`); + console.error(error.stack); + return { success: false, error: { code: 'CACHE_UTIL_ERROR', message: error.message }, fromCache: false }; + } +} + +/** + * Get cache statistics for monitoring + * @param {Object} args - Command arguments + * @param {Object} log - Logger object + * @returns {Object} - Cache statistics + */ +export async function getCacheStatsDirect(args, log) { + try { + log.info('Retrieving cache statistics'); + const stats = contextManager.getStats(); + return { + success: true, + data: stats + }; + } catch (error) { + log.error(`Error getting cache stats: ${error.message}`); + return { + success: false, + error: { + code: 'CACHE_STATS_ERROR', + message: error.message || 'Unknown error occurred' + } + }; + } +} + +/** + * Maps Task Master functions to their direct implementation + */ +export const directFunctions = { + list: listTasksDirect, + cacheStats: getCacheStatsDirect, + // Add more functions as we implement them +}; \ No newline at end of file diff --git a/mcp-server/src/tools/listTasks.js b/mcp-server/src/tools/listTasks.js index af6f4844..35446ba2 100644 --- a/mcp-server/src/tools/listTasks.js +++ b/mcp-server/src/tools/listTasks.js @@ -5,10 +5,10 @@ import { z } from "zod"; import { - executeTaskMasterCommand, - createContentResponse, createErrorResponse, + handleApiResult } from "./utils.js"; +import { listTasksDirect } from "../core/task-master-core.js"; /** * Register the listTasks tool with the MCP server @@ -27,6 +27,7 @@ export function registerListTasksTool(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)" ), @@ -34,32 +35,19 @@ export function registerListTasksTool(server) { execute: async (args, { log }) => { try { log.info(`Listing tasks with filters: ${JSON.stringify(args)}`); - - const cmdArgs = []; - if (args.status) cmdArgs.push(`--status=${args.status}`); - if (args.withSubtasks) cmdArgs.push("--with-subtasks"); - if (args.file) cmdArgs.push(`--file=${args.file}`); - - const projectRoot = args.projectRoot; - - const result = executeTaskMasterCommand( - "list", - log, - cmdArgs, - projectRoot - ); - - if (!result.success) { - throw new Error(result.error); - } - - log.info(`Listing tasks result: ${result.stdout}`, result.stdout); - - return createContentResponse(result.stdout); + + // Call core function - args contains projectRoot which is handled internally + const result = await listTasksDirect(args, log); + + // Log result and use handleApiResult utility + log.info(`Retrieved ${result.success ? (result.data?.tasks?.length || 0) : 0} tasks`); + return handleApiResult(result, log, 'Error listing tasks'); } catch (error) { log.error(`Error listing tasks: ${error.message}`); - return createErrorResponse(`Error listing tasks: ${error.message}`); + return createErrorResponse(error.message); } }, }); } + +// We no longer need the formatTasksResponse function as we're returning raw JSON data diff --git a/mcp-server/src/tools/showTask.js b/mcp-server/src/tools/showTask.js index 86130570..33e4da79 100644 --- a/mcp-server/src/tools/showTask.js +++ b/mcp-server/src/tools/showTask.js @@ -6,8 +6,8 @@ import { z } from "zod"; import { executeTaskMasterCommand, - createContentResponse, createErrorResponse, + handleApiResult } from "./utils.js"; /** @@ -23,6 +23,7 @@ export function registerShowTaskTool(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)" ), @@ -31,26 +32,46 @@ export function registerShowTaskTool(server) { 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}`); - const projectRoot = args.projectRoot; - + // Execute the command - function now handles project root internally const result = executeTaskMasterCommand( "show", log, cmdArgs, - projectRoot + args.projectRoot // Pass raw project root, function will normalize it ); - if (!result.success) { - throw new Error(result.error); + // 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' + ); } - - return createContentResponse(result.stdout); } catch (error) { log.error(`Error showing task: ${error.message}`); - return createErrorResponse(`Error showing task: ${error.message}`); + return createErrorResponse(error.message); } }, }); diff --git a/mcp-server/src/tools/utils.js b/mcp-server/src/tools/utils.js index 872363e0..f448e4e8 100644 --- a/mcp-server/src/tools/utils.js +++ b/mcp-server/src/tools/utils.js @@ -4,22 +4,78 @@ */ import { spawnSync } from "child_process"; +import path from "path"; +import { contextManager } from '../core/context-manager.js'; // Import the singleton + +/** + * Get normalized project root path + * @param {string|undefined} projectRootRaw - Raw project root from arguments + * @param {Object} log - Logger object + * @returns {string} - Normalized absolute path to project root + */ +export function getProjectRoot(projectRootRaw, log) { + // Make sure projectRoot is set + const rootPath = projectRootRaw || process.cwd(); + + // Ensure projectRoot is absolute + const projectRoot = path.isAbsolute(rootPath) + ? rootPath + : path.resolve(process.cwd(), rootPath); + + log.info(`Using project root: ${projectRoot}`); + return projectRoot; +} + +/** + * Handle API result with standardized error handling and response formatting + * @param {Object} result - Result object from API call with success, data, and error properties + * @param {Object} log - Logger object + * @param {string} errorPrefix - Prefix for error messages + * @param {Function} processFunction - Optional function to process successful result data + * @returns {Object} - Standardized MCP response object + */ +export function handleApiResult(result, log, errorPrefix = 'API error', processFunction = processMCPResponseData) { + if (!result.success) { + const errorMsg = result.error?.message || `Unknown ${errorPrefix}`; + // Include cache status in error logs + log.error(`${errorPrefix}: ${errorMsg}. From cache: ${result.fromCache}`); // Keep logging cache status on error + return createErrorResponse(errorMsg); + } + + // Process the result data if needed + const processedData = processFunction ? processFunction(result.data) : result.data; + + // Log success including cache status + log.info(`Successfully completed operation. From cache: ${result.fromCache}`); // Add success log with cache status + + // Create the response payload including the fromCache flag + const responsePayload = { + fromCache: result.fromCache, // Get the flag from the original 'result' + data: processedData // Nest the processed data under a 'data' key + }; + + // Pass this combined payload to createContentResponse + return createContentResponse(responsePayload); +} /** * Execute a Task Master CLI command using child_process * @param {string} command - The command to execute * @param {Object} log - The logger object from FastMCP * @param {Array} args - Arguments for the command - * @param {string} cwd - Working directory for command execution (defaults to current project root) + * @param {string|undefined} projectRootRaw - Optional raw project root path (will be normalized internally) * @returns {Object} - The result of the command execution */ export function executeTaskMasterCommand( command, log, args = [], - cwd = process.cwd() + projectRootRaw = null ) { try { + // Normalize project root internally using the getProjectRoot utility + const cwd = getProjectRoot(projectRootRaw, log); + log.info( `Executing task-master ${command} with args: ${JSON.stringify( args @@ -76,33 +132,236 @@ export function executeTaskMasterCommand( } /** - * Creates standard content response for tools - * @param {string} text - Text content to include in response - * @returns {Object} - Content response object + * Checks cache for a result using the provided key. If not found, executes the action function, + * caches the result upon success, and returns the result. + * + * @param {Object} options - Configuration options. + * @param {string} options.cacheKey - The unique key for caching this operation's result. + * @param {Function} options.actionFn - The async function to execute if the cache misses. + * Should return an object like { success: boolean, data?: any, error?: { code: string, message: string } }. + * @param {Object} options.log - The logger instance. + * @returns {Promise} - An object containing the result, indicating if it was from cache. + * Format: { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } */ -export function createContentResponse(text) { +export async function getCachedOrExecute({ cacheKey, actionFn, log }) { + // Check cache first + const cachedResult = contextManager.getCachedData(cacheKey); + + if (cachedResult !== undefined) { + log.info(`Cache hit for key: ${cacheKey}`); + // Return the cached data in the same structure as a fresh result + return { + ...cachedResult, // Spread the cached result to maintain its structure + fromCache: true // Just add the fromCache flag + }; + } + + log.info(`Cache miss for key: ${cacheKey}. Executing action function.`); + + // Execute the action function if cache missed + const result = await actionFn(); + + // If the action was successful, cache the result (but without fromCache flag) + if (result.success && result.data !== undefined) { + log.info(`Action successful. Caching result for key: ${cacheKey}`); + // Cache the entire result structure (minus the fromCache flag) + const { fromCache, ...resultToCache } = result; + contextManager.setCachedData(cacheKey, resultToCache); + } else if (!result.success) { + log.warn(`Action failed for cache key ${cacheKey}. Result not cached. Error: ${result.error?.message}`); + } else { + log.warn(`Action for cache key ${cacheKey} succeeded but returned no data. Result not cached.`); + } + + // Return the fresh result, indicating it wasn't from cache + return { + ...result, + fromCache: false + }; +} + +/** + * 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} - 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. + * Handles common data structures returned by task commands. + * @param {Object|Array} taskOrData - A single task object or a data object containing a 'tasks' array. + * @param {string[]} fieldsToRemove - An array of field names to remove. + * @returns {Object|Array} - The processed data with specified fields removed. + */ +export function processMCPResponseData(taskOrData, fieldsToRemove = ['details', 'testStrategy']) { + if (!taskOrData) { + return taskOrData; + } + + // Helper function to process a single task object + const processSingleTask = (task) => { + if (typeof task !== 'object' || task === null) { + return task; + } + + const processedTask = { ...task }; + + // Remove specified fields from the task + fieldsToRemove.forEach(field => { + delete processedTask[field]; + }); + + // Recursively process subtasks if they exist and are an array + if (processedTask.subtasks && Array.isArray(processedTask.subtasks)) { + // Use processArrayOfTasks to handle the subtasks array + processedTask.subtasks = processArrayOfTasks(processedTask.subtasks); + } + + return processedTask; + }; + + // Helper function to process an array of tasks + const processArrayOfTasks = (tasks) => { + return tasks.map(processSingleTask); + }; + + // Check if the input is a data structure containing a 'tasks' array (like from listTasks) + if (typeof taskOrData === 'object' && taskOrData !== null && Array.isArray(taskOrData.tasks)) { + return { + ...taskOrData, // Keep other potential fields like 'stats', 'filter' + tasks: processArrayOfTasks(taskOrData.tasks), + }; + } + // Check if the input is likely a single task object (add more checks if needed) + else if (typeof taskOrData === 'object' && taskOrData !== null && 'id' in taskOrData && 'title' in taskOrData) { + return processSingleTask(taskOrData); + } + // Check if the input is an array of tasks directly (less common but possible) + else if (Array.isArray(taskOrData)) { + return processArrayOfTasks(taskOrData); + } + + // If it doesn't match known task structures, return it as is + return taskOrData; +} + +/** + * Creates standard content response for tools + * @param {string|Object} content - Content to include in response + * @returns {Object} - Content response object in FastMCP format + */ +export function createContentResponse(content) { + // FastMCP requires text type, so we format objects as JSON strings return { content: [ { - text, - type: "text", - }, - ], + type: "text", + text: typeof content === 'object' ? + // Format JSON nicely with indentation + JSON.stringify(content, null, 2) : + // Keep other content types as-is + String(content) + } + ] }; } /** * Creates error response for tools * @param {string} errorMessage - Error message to include in response - * @returns {Object} - Error content response object + * @returns {Object} - Error content response object in FastMCP format */ export function createErrorResponse(errorMessage) { return { content: [ { - text: errorMessage, type: "text", - }, + text: `Error: ${errorMessage}` + } ], + isError: true }; } diff --git a/package-lock.json b/package-lock.json index 198d4529..70b2cfca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "gradient-string": "^3.0.0", "helmet": "^8.1.0", "jsonwebtoken": "^9.0.2", + "lru-cache": "^10.2.0", "openai": "^4.89.0", "ora": "^8.2.0" }, @@ -163,6 +164,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", @@ -5505,14 +5516,10 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/make-dir": { "version": "4.0.0", diff --git a/package.json b/package.json index e824a6ff..83ebd7cf 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", + "@model-context-protocol/sdk": "^1.20.5", "boxen": "^8.0.1", "chalk": "^4.1.2", "cli-table3": "^0.6.5", @@ -46,12 +47,13 @@ "express": "^4.21.2", "fastmcp": "^1.20.5", "figlet": "^1.8.0", + "fuse.js": "^7.0.0", "gradient-string": "^3.0.0", "helmet": "^8.1.0", "jsonwebtoken": "^9.0.2", + "lru-cache": "^10.2.0", "openai": "^4.89.0", - "ora": "^8.2.0", - "fuse.js": "^7.0.0" + "ora": "^8.2.0" }, "engines": { "node": ">=14.0.0" diff --git a/scripts/modules/task-manager.js b/scripts/modules/task-manager.js index f40f2795..3df5a44c 100644 --- a/scripts/modules/task-manager.js +++ b/scripts/modules/task-manager.js @@ -987,20 +987,26 @@ async function updateSingleTaskStatus(tasksPath, taskIdInput, newStatus, data) { * @param {string} tasksPath - Path to the tasks.json file * @param {string} statusFilter - Filter by status * @param {boolean} withSubtasks - Whether to show subtasks + * @param {string} outputFormat - Output format (text or json) + * @returns {Object} - Task list result for json format */ -function listTasks(tasksPath, statusFilter, withSubtasks = false) { +function listTasks(tasksPath, statusFilter, withSubtasks = false, outputFormat = 'text') { try { - displayBanner(); - const data = readJSON(tasksPath); + // Only display banner for text output + if (outputFormat === 'text') { + displayBanner(); + } + + const data = readJSON(tasksPath); // Reads the whole tasks.json if (!data || !data.tasks) { throw new Error(`No valid tasks found in ${tasksPath}`); } // Filter tasks by status if specified - const filteredTasks = statusFilter + const filteredTasks = statusFilter && statusFilter.toLowerCase() !== 'all' // <-- Added check for 'all' ? data.tasks.filter(task => task.status && task.status.toLowerCase() === statusFilter.toLowerCase()) - : data.tasks; + : data.tasks; // Default to all tasks if no filter or filter is 'all' // Calculate completion statistics const totalTasks = data.tasks.length; @@ -1029,7 +1035,47 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { const subtaskCompletionPercentage = totalSubtasks > 0 ? (completedSubtasks / totalSubtasks) * 100 : 0; - + + // For JSON output, return structured data + if (outputFormat === 'json') { + // *** Modification: Remove 'details' field for JSON output *** + const tasksWithoutDetails = filteredTasks.map(task => { // <-- USES filteredTasks! + // Omit 'details' from the parent task + const { details, ...taskRest } = task; + + // If subtasks exist, omit 'details' from them too + if (taskRest.subtasks && Array.isArray(taskRest.subtasks)) { + taskRest.subtasks = taskRest.subtasks.map(subtask => { + const { details: subtaskDetails, ...subtaskRest } = subtask; + return subtaskRest; + }); + } + return taskRest; + }); + // *** End of Modification *** + + return { + tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED + filter: statusFilter || 'all', // Return the actual filter used + stats: { + total: totalTasks, + completed: doneCount, + inProgress: inProgressCount, + pending: pendingCount, + blocked: blockedCount, + deferred: deferredCount, + completionPercentage, + subtasks: { + total: totalSubtasks, + completed: completedSubtasks, + completionPercentage: subtaskCompletionPercentage + } + } + }; + } + + // ... existing code for text output ... + // Create progress bars const taskProgressBar = createProgressBar(completionPercentage, 30); const subtaskProgressBar = createProgressBar(subtaskCompletionPercentage, 30); @@ -1460,12 +1506,17 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { )); } catch (error) { log('error', `Error listing tasks: ${error.message}`); - console.error(chalk.red(`Error: ${error.message}`)); - if (CONFIG.debug) { - console.error(error); + if (outputFormat === 'json') { + // Return structured error for JSON output + throw { + code: 'TASK_LIST_ERROR', + message: error.message, + details: error.stack + }; } + console.error(chalk.red(`Error: ${error.message}`)); process.exit(1); } } diff --git a/tasks/task_023.txt b/tasks/task_023.txt index 5842d3c0..849ac4d9 100644 --- a/tasks/task_023.txt +++ b/tasks/task_023.txt @@ -222,7 +222,7 @@ Testing approach: - Validate compatibility with existing MCP clients. - Benchmark performance improvements from SDK integration. -## 8. Implement Direct Function Imports and Replace CLI-based Execution [in-progress] +## 8. Implement Direct Function Imports and Replace CLI-based Execution [done] ### Dependencies: 23.13 ### Description: Refactor the MCP server implementation to use direct Task Master function imports instead of the current CLI-based execution using child_process.spawnSync. This will improve performance, reliability, and enable better error handling. ### Details: @@ -290,7 +290,7 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false, outputFormat = ``` -## 9. Implement Context Management and Caching Mechanisms [deferred] +## 9. Implement Context Management and Caching Mechanisms [done] ### Dependencies: 23.1 ### Description: Enhance the MCP server with proper context management and caching to improve performance and user experience, especially for frequently accessed data and contexts. ### Details: @@ -303,7 +303,7 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false, outputFormat = 7. Add cache statistics for monitoring performance 8. Create unit tests for context management and caching functionality -## 10. Enhance Tool Registration and Resource Management [in-progress] +## 10. Enhance Tool Registration and Resource Management [deferred] ### Dependencies: 23.1, 23.8 ### Description: Refactor tool registration to follow FastMCP best practices, using decorators and improving the overall structure. Implement proper resource management for task templates and other shared resources. ### Details: @@ -316,19 +316,19 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false, outputFormat = 7. Add validation for tool inputs using FastMCP's built-in validation 8. Create comprehensive tests for tool registration and resource access -## 11. Implement Comprehensive Error Handling [pending] +## 11. Implement Comprehensive Error Handling [deferred] ### Dependencies: 23.1, 23.3 ### Description: Implement robust error handling using FastMCP's MCPError, including custom error types for different categories and standardized error responses. ### Details: 1. Create custom error types extending MCPError for different categories (validation, auth, etc.)\n2. Implement standardized error responses following MCP protocol\n3. Add error handling middleware for all MCP endpoints\n4. Ensure proper error propagation from tools to client\n5. Add debug mode with detailed error information\n6. Document error types and handling patterns -## 12. Implement Structured Logging System [pending] +## 12. Implement Structured Logging System [deferred] ### Dependencies: 23.1, 23.3 ### Description: Implement a comprehensive logging system for the MCP server with different log levels, structured logging format, and request/response tracking. ### Details: 1. Design structured log format for consistent parsing\n2. Implement different log levels (debug, info, warn, error)\n3. Add request/response logging middleware\n4. Implement correlation IDs for request tracking\n5. Add performance metrics logging\n6. Configure log output destinations (console, file)\n7. Document logging patterns and usage -## 13. Create Testing Framework and Test Suite [pending] +## 13. Create Testing Framework and Test Suite [deferred] ### Dependencies: 23.1, 23.3 ### Description: Implement a comprehensive testing framework for the MCP server, including unit tests, integration tests, and end-to-end tests. ### Details: @@ -346,3 +346,93 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false, outputFormat = ### 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 +## 16. Implement parse-prd MCP command [pending] +### Dependencies: None +### 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 + +## 17. Implement update MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for updating multiple tasks based on prompt. +### Details: +Following MCP implementation standards:\n\n1. Create updateTasksDirect function in task-master-core.js:\n - Import updateTasks from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: fromId, prompt, useResearch\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create update.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import updateTasksDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerUpdateTool(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 updateTasksDirect\n - Integration test for MCP tool + +## 18. Implement update-task MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for updating a single task by ID with new information. +### Details: +Following MCP implementation standards:\n\n1. Create updateTaskByIdDirect function in task-master-core.js:\n - Import updateTaskById from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: taskId, prompt, useResearch\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create update-task.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import updateTaskByIdDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerUpdateTaskTool(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 updateTaskByIdDirect\n - Integration test for MCP tool + +## 19. Implement update-subtask MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for appending information to a specific subtask. +### Details: +Following MCP implementation standards:\n\n1. Create updateSubtaskByIdDirect function in task-master-core.js:\n - Import updateSubtaskById from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: subtaskId, prompt, useResearch\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create update-subtask.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import updateSubtaskByIdDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerUpdateSubtaskTool(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 updateSubtaskByIdDirect\n - Integration test for MCP tool + +## 20. Implement generate MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for generating task files from tasks.json. +### Details: +Following MCP implementation standards:\n\n1. Create generateTaskFilesDirect function in task-master-core.js:\n - Import generateTaskFiles from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: tasksPath, outputDir\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create generate.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import generateTaskFilesDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerGenerateTool(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 generateTaskFilesDirect\n - Integration test for MCP tool + +## 21. Implement set-status MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for setting task status. +### Details: +Following MCP implementation standards:\n\n1. Create setTaskStatusDirect function in task-master-core.js:\n - Import setTaskStatus from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: taskId, status\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create set-status.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import setTaskStatusDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerSetStatusTool(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 setTaskStatusDirect\n - Integration test for MCP tool + +## 22. Implement show-task MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for showing task details. +### Details: +Following MCP implementation standards:\n\n1. Create showTaskDirect function in task-master-core.js:\n - Import showTask from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: taskId\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create show-task.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import showTaskDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerShowTaskTool(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 showTaskDirect\n - Integration test for MCP tool + +## 23. Implement next-task MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for finding the next task to work on. +### Details: +Following MCP implementation standards:\n\n1. Create nextTaskDirect function in task-master-core.js:\n - Import nextTask from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments (no specific args needed except projectRoot/file)\n - Handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create next-task.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import nextTaskDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerNextTaskTool(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 nextTaskDirect\n - Integration test for MCP tool + +## 24. Implement expand-task MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for expanding a task into subtasks. +### Details: +Following MCP implementation standards:\n\n1. Create expandTaskDirect function in task-master-core.js:\n - Import expandTask from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: taskId, prompt, num, force, research\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create expand-task.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import expandTaskDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerExpandTaskTool(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 expandTaskDirect\n - Integration test for MCP tool + +## 25. Implement add-task MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for adding new tasks. +### Details: +Following MCP implementation standards:\n\n1. Create addTaskDirect function in task-master-core.js:\n - Import addTask from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: prompt, priority, dependencies\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create add-task.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import addTaskDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerAddTaskTool(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 addTaskDirect\n - Integration test for MCP tool + +## 26. Implement add-subtask MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for adding subtasks to existing tasks. +### Details: +Following MCP implementation standards:\n\n1. Create addSubtaskDirect function in task-master-core.js:\n - Import addSubtask from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: parentTaskId, title, description, details\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create add-subtask.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import addSubtaskDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerAddSubtaskTool(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 addSubtaskDirect\n - Integration test for MCP tool + +## 27. Implement remove-subtask MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for removing subtasks from tasks. +### Details: +Following MCP implementation standards:\n\n1. Create removeSubtaskDirect function in task-master-core.js:\n - Import removeSubtask from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: parentTaskId, subtaskId\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create remove-subtask.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import removeSubtaskDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerRemoveSubtaskTool(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 removeSubtaskDirect\n - Integration test for MCP tool + +## 28. Implement analyze MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for analyzing task complexity. +### Details: +Following MCP implementation standards:\n\n1. Create analyzeTaskComplexityDirect function in task-master-core.js:\n - Import analyzeTaskComplexity from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: taskId\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create analyze.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import analyzeTaskComplexityDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerAnalyzeTool(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 analyzeTaskComplexityDirect\n - Integration test for MCP tool + +## 29. Implement clear-subtasks MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for clearing subtasks from a parent task. +### Details: +Following MCP implementation standards:\n\n1. Create clearSubtasksDirect function in task-master-core.js:\n - Import clearSubtasks from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: taskId\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create clear-subtasks.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import clearSubtasksDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerClearSubtasksTool(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 clearSubtasksDirect\n - Integration test for MCP tool + +## 30. Implement expand-all MCP command [pending] +### Dependencies: None +### Description: Create direct function wrapper and MCP tool for expanding all tasks into subtasks. +### Details: +Following MCP implementation standards:\n\n1. Create expandAllTasksDirect function in task-master-core.js:\n - Import expandAllTasks from task-manager.js\n - Handle file paths using findTasksJsonPath utility\n - Process arguments: prompt, num, force, research\n - Validate inputs and handle errors with try/catch\n - Return standardized { success, data/error } object\n - Add to directFunctions map\n\n2. Create expand-all.js MCP tool in mcp-server/src/tools/:\n - Import z from zod for parameter schema\n - Import executeMCPToolAction from ./utils.js\n - Import expandAllTasksDirect from task-master-core.js\n - Define parameters matching CLI options using zod schema\n - Implement registerExpandAllTool(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 expandAllTasksDirect\n - Integration test for MCP tool + diff --git a/tasks/tasks.json b/tasks/tasks.json index 340205bd..a3f4c4f6 100644 --- a/tasks/tasks.json +++ b/tasks/tasks.json @@ -1400,7 +1400,7 @@ "23.13" ], "details": "\n\n\n```\n# Refactoring Strategy for Direct Function Imports\n\n## Core Approach\n1. Create a clear separation between data retrieval/processing and presentation logic\n2. Modify function signatures to accept `outputFormat` parameter ('cli'|'json', default: 'cli')\n3. Implement early returns for JSON format to bypass CLI-specific code\n\n## Implementation Details for `listTasks`\n```javascript\nfunction listTasks(tasksPath, statusFilter, withSubtasks = false, outputFormat = 'cli') {\n try {\n // Existing data retrieval logic\n const filteredTasks = /* ... */;\n \n // Early return for JSON format\n if (outputFormat === 'json') return filteredTasks;\n \n // Existing CLI output logic\n } catch (error) {\n if (outputFormat === 'json') {\n throw {\n code: 'TASK_LIST_ERROR',\n message: error.message,\n details: error.stack\n };\n } else {\n console.error(error);\n process.exit(1);\n }\n }\n}\n```\n\n## Testing Strategy\n- Create integration tests in `tests/integration/mcp-server/`\n- Use FastMCP InMemoryTransport for direct client-server testing\n- Test both JSON and CLI output formats\n- Verify structure consistency with schema validation\n\n## Additional Considerations\n- Update JSDoc comments to document new parameters and return types\n- Ensure backward compatibility with default CLI behavior\n- Add JSON schema validation for consistent output structure\n- Apply similar pattern to other core functions (expandTask, updateTaskById, etc.)\n\n## Error Handling Improvements\n- Standardize error format for JSON returns:\n```javascript\n{\n code: 'ERROR_CODE',\n message: 'Human-readable message',\n details: {}, // Additional context when available\n stack: process.env.NODE_ENV === 'development' ? error.stack : undefined\n}\n```\n- Enrich JSON errors with error codes and debug info\n- Ensure validation failures return proper objects in JSON mode\n```\n", - "status": "in-progress", + "status": "done", "parentTaskId": 23 }, { @@ -1411,7 +1411,7 @@ 1 ], "details": "1. Implement a context manager class that leverages FastMCP's Context object\n2. Add caching for frequently accessed task data with configurable TTL settings\n3. Implement context tagging for better organization of context data\n4. Add methods to efficiently handle large context windows\n5. Create helper functions for storing and retrieving context data\n6. Implement cache invalidation strategies for task updates\n7. Add cache statistics for monitoring performance\n8. Create unit tests for context management and caching functionality", - "status": "deferred", + "status": "done", "parentTaskId": 23 }, { @@ -1423,7 +1423,7 @@ "23.8" ], "details": "1. Update registerTaskMasterTools function to use FastMCP's decorator pattern\n2. Implement @mcp.tool() decorators for all existing tools\n3. Add proper type annotations and documentation for all tools\n4. Create resource handlers for task templates using @mcp.resource()\n5. Implement resource templates for common task patterns\n6. Update the server initialization to properly register all tools and resources\n7. Add validation for tool inputs using FastMCP's built-in validation\n8. Create comprehensive tests for tool registration and resource access", - "status": "in-progress", + "status": "deferred", "parentTaskId": 23 }, { @@ -1431,7 +1431,7 @@ "title": "Implement Comprehensive Error Handling", "description": "Implement robust error handling using FastMCP's MCPError, including custom error types for different categories and standardized error responses.", "details": "1. Create custom error types extending MCPError for different categories (validation, auth, etc.)\\n2. Implement standardized error responses following MCP protocol\\n3. Add error handling middleware for all MCP endpoints\\n4. Ensure proper error propagation from tools to client\\n5. Add debug mode with detailed error information\\n6. Document error types and handling patterns", - "status": "pending", + "status": "deferred", "dependencies": [ "23.1", "23.3" @@ -1443,7 +1443,7 @@ "title": "Implement Structured Logging System", "description": "Implement a comprehensive logging system for the MCP server with different log levels, structured logging format, and request/response tracking.", "details": "1. Design structured log format for consistent parsing\\n2. Implement different log levels (debug, info, warn, error)\\n3. Add request/response logging middleware\\n4. Implement correlation IDs for request tracking\\n5. Add performance metrics logging\\n6. Configure log output destinations (console, file)\\n7. Document logging patterns and usage", - "status": "pending", + "status": "deferred", "dependencies": [ "23.1", "23.3" @@ -1455,7 +1455,7 @@ "title": "Create Testing Framework and Test Suite", "description": "Implement a comprehensive testing framework for the MCP server, including unit tests, integration tests, and end-to-end tests.", "details": "1. Set up Jest testing framework with proper configuration\\n2. Create MCPTestClient for testing FastMCP server interaction\\n3. Implement unit tests for individual tool functions\\n4. Create integration tests for end-to-end request/response cycles\\n5. Set up test fixtures and mock data\\n6. Implement test coverage reporting\\n7. Document testing guidelines and examples", - "status": "pending", + "status": "deferred", "dependencies": [ "23.1", "23.3" @@ -1486,6 +1486,141 @@ "23.11" ], "parentTaskId": 23 + }, + { + "id": 16, + "title": "Implement parse-prd MCP command", + "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", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 17, + "title": "Implement update MCP command", + "description": "Create direct function wrapper and MCP tool for updating multiple tasks based on prompt.", + "details": "Following MCP implementation standards:\\n\\n1. Create updateTasksDirect function in task-master-core.js:\\n - Import updateTasks from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: fromId, prompt, useResearch\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create update.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import updateTasksDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerUpdateTool(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 updateTasksDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 18, + "title": "Implement update-task MCP command", + "description": "Create direct function wrapper and MCP tool for updating a single task by ID with new information.", + "details": "Following MCP implementation standards:\\n\\n1. Create updateTaskByIdDirect function in task-master-core.js:\\n - Import updateTaskById from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: taskId, prompt, useResearch\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create update-task.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import updateTaskByIdDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerUpdateTaskTool(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 updateTaskByIdDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 19, + "title": "Implement update-subtask MCP command", + "description": "Create direct function wrapper and MCP tool for appending information to a specific subtask.", + "details": "Following MCP implementation standards:\\n\\n1. Create updateSubtaskByIdDirect function in task-master-core.js:\\n - Import updateSubtaskById from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: subtaskId, prompt, useResearch\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create update-subtask.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import updateSubtaskByIdDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerUpdateSubtaskTool(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 updateSubtaskByIdDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 20, + "title": "Implement generate MCP command", + "description": "Create direct function wrapper and MCP tool for generating task files from tasks.json.", + "details": "Following MCP implementation standards:\\n\\n1. Create generateTaskFilesDirect function in task-master-core.js:\\n - Import generateTaskFiles from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: tasksPath, outputDir\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create generate.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import generateTaskFilesDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerGenerateTool(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 generateTaskFilesDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 21, + "title": "Implement set-status MCP command", + "description": "Create direct function wrapper and MCP tool for setting task status.", + "details": "Following MCP implementation standards:\\n\\n1. Create setTaskStatusDirect function in task-master-core.js:\\n - Import setTaskStatus from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: taskId, status\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create set-status.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import setTaskStatusDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerSetStatusTool(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 setTaskStatusDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 22, + "title": "Implement show-task MCP command", + "description": "Create direct function wrapper and MCP tool for showing task details.", + "details": "Following MCP implementation standards:\\n\\n1. Create showTaskDirect function in task-master-core.js:\\n - Import showTask from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: taskId\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create show-task.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import showTaskDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerShowTaskTool(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 showTaskDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 23, + "title": "Implement next-task MCP command", + "description": "Create direct function wrapper and MCP tool for finding the next task to work on.", + "details": "Following MCP implementation standards:\\n\\n1. Create nextTaskDirect function in task-master-core.js:\\n - Import nextTask from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments (no specific args needed except projectRoot/file)\\n - Handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create next-task.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import nextTaskDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerNextTaskTool(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 nextTaskDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 24, + "title": "Implement expand-task MCP command", + "description": "Create direct function wrapper and MCP tool for expanding a task into subtasks.", + "details": "Following MCP implementation standards:\\n\\n1. Create expandTaskDirect function in task-master-core.js:\\n - Import expandTask from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: taskId, prompt, num, force, research\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create expand-task.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import expandTaskDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerExpandTaskTool(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 expandTaskDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 25, + "title": "Implement add-task MCP command", + "description": "Create direct function wrapper and MCP tool for adding new tasks.", + "details": "Following MCP implementation standards:\\n\\n1. Create addTaskDirect function in task-master-core.js:\\n - Import addTask from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: prompt, priority, dependencies\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create add-task.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import addTaskDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerAddTaskTool(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 addTaskDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 26, + "title": "Implement add-subtask MCP command", + "description": "Create direct function wrapper and MCP tool for adding subtasks to existing tasks.", + "details": "Following MCP implementation standards:\\n\\n1. Create addSubtaskDirect function in task-master-core.js:\\n - Import addSubtask from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: parentTaskId, title, description, details\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create add-subtask.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import addSubtaskDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerAddSubtaskTool(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 addSubtaskDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 27, + "title": "Implement remove-subtask MCP command", + "description": "Create direct function wrapper and MCP tool for removing subtasks from tasks.", + "details": "Following MCP implementation standards:\\n\\n1. Create removeSubtaskDirect function in task-master-core.js:\\n - Import removeSubtask from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: parentTaskId, subtaskId\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create remove-subtask.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import removeSubtaskDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerRemoveSubtaskTool(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 removeSubtaskDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 28, + "title": "Implement analyze MCP command", + "description": "Create direct function wrapper and MCP tool for analyzing task complexity.", + "details": "Following MCP implementation standards:\\n\\n1. Create analyzeTaskComplexityDirect function in task-master-core.js:\\n - Import analyzeTaskComplexity from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: taskId\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create analyze.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import analyzeTaskComplexityDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerAnalyzeTool(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 analyzeTaskComplexityDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 29, + "title": "Implement clear-subtasks MCP command", + "description": "Create direct function wrapper and MCP tool for clearing subtasks from a parent task.", + "details": "Following MCP implementation standards:\\n\\n1. Create clearSubtasksDirect function in task-master-core.js:\\n - Import clearSubtasks from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: taskId\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create clear-subtasks.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import clearSubtasksDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerClearSubtasksTool(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 clearSubtasksDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 + }, + { + "id": 30, + "title": "Implement expand-all MCP command", + "description": "Create direct function wrapper and MCP tool for expanding all tasks into subtasks.", + "details": "Following MCP implementation standards:\\n\\n1. Create expandAllTasksDirect function in task-master-core.js:\\n - Import expandAllTasks from task-manager.js\\n - Handle file paths using findTasksJsonPath utility\\n - Process arguments: prompt, num, force, research\\n - Validate inputs and handle errors with try/catch\\n - Return standardized { success, data/error } object\\n - Add to directFunctions map\\n\\n2. Create expand-all.js MCP tool in mcp-server/src/tools/:\\n - Import z from zod for parameter schema\\n - Import executeMCPToolAction from ./utils.js\\n - Import expandAllTasksDirect from task-master-core.js\\n - Define parameters matching CLI options using zod schema\\n - Implement registerExpandAllTool(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 expandAllTasksDirect\\n - Integration test for MCP tool", + "status": "pending", + "dependencies": [], + "parentTaskId": 23 } ] }, diff --git a/tests/integration/mcp-server/direct-functions.test.js b/tests/integration/mcp-server/direct-functions.test.js new file mode 100644 index 00000000..e8c8c427 --- /dev/null +++ b/tests/integration/mcp-server/direct-functions.test.js @@ -0,0 +1,191 @@ +/** + * Integration test for direct function imports in MCP server + */ + +import { jest } from '@jest/globals'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +// Get the current module's directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Import the direct functions +import { listTasksDirect } from '../../../mcp-server/src/core/task-master-core.js'; + +// Mock logger +const mockLogger = { + info: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + warn: jest.fn() +}; + +// Test file paths +const testProjectRoot = path.join(__dirname, '../../fixture'); +const testTasksPath = path.join(testProjectRoot, 'test-tasks.json'); + +describe('MCP Server Direct Functions', () => { + // Create test data before tests + beforeAll(() => { + // Create test directory if it doesn't exist + if (!fs.existsSync(testProjectRoot)) { + fs.mkdirSync(testProjectRoot, { recursive: true }); + } + + // Create a sample tasks.json file for testing + const sampleTasks = { + meta: { + projectName: 'Test Project', + version: '1.0.0' + }, + tasks: [ + { + id: 1, + title: 'Task 1', + description: 'First task', + status: 'done', + dependencies: [], + priority: 'high' + }, + { + id: 2, + title: 'Task 2', + description: 'Second task', + status: 'in-progress', + dependencies: [1], + priority: 'medium', + subtasks: [ + { + id: 1, + title: 'Subtask 2.1', + description: 'First subtask', + status: 'done' + }, + { + id: 2, + title: 'Subtask 2.2', + description: 'Second subtask', + status: 'pending' + } + ] + }, + { + id: 3, + title: 'Task 3', + description: 'Third task', + status: 'pending', + dependencies: [1, 2], + priority: 'low' + } + ] + }; + + fs.writeFileSync(testTasksPath, JSON.stringify(sampleTasks, null, 2)); + }); + + // Clean up after tests + afterAll(() => { + // Remove test tasks file + if (fs.existsSync(testTasksPath)) { + fs.unlinkSync(testTasksPath); + } + + // Try to remove the directory (will only work if empty) + try { + fs.rmdirSync(testProjectRoot); + } catch (error) { + // Ignore errors if the directory isn't empty + } + }); + + // Reset mocks before each test + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('listTasksDirect', () => { + test('should return all tasks when no filter is provided', async () => { + // Arrange + const args = { + projectRoot: testProjectRoot, + file: testTasksPath + }; + + // Act + const result = await listTasksDirect(args, mockLogger); + + // Assert + expect(result.success).toBe(true); + expect(result.data.tasks.length).toBe(3); + expect(result.data.stats.total).toBe(3); + expect(result.data.stats.completed).toBe(1); + expect(result.data.stats.inProgress).toBe(1); + expect(result.data.stats.pending).toBe(1); + expect(mockLogger.info).toHaveBeenCalled(); + }); + + test('should filter tasks by status', async () => { + // Arrange + const args = { + projectRoot: testProjectRoot, + file: testTasksPath, + status: 'pending' + }; + + // Act + const result = await listTasksDirect(args, mockLogger); + + // Assert + expect(result.success).toBe(true); + expect(result.data.tasks.length).toBe(1); + expect(result.data.tasks[0].id).toBe(3); + expect(result.data.filter).toBe('pending'); + }); + + test('should include subtasks when requested', async () => { + // Arrange + const args = { + projectRoot: testProjectRoot, + file: testTasksPath, + withSubtasks: true + }; + + // Act + const result = await listTasksDirect(args, mockLogger); + + // Assert + expect(result.success).toBe(true); + + // Verify subtasks are included + const taskWithSubtasks = result.data.tasks.find(t => t.id === 2); + expect(taskWithSubtasks.subtasks).toBeDefined(); + expect(taskWithSubtasks.subtasks.length).toBe(2); + + // Verify subtask details + expect(taskWithSubtasks.subtasks[0].id).toBe(1); + expect(taskWithSubtasks.subtasks[0].title).toBe('Subtask 2.1'); + expect(taskWithSubtasks.subtasks[0].status).toBe('done'); + }); + + test('should handle errors gracefully', async () => { + // Arrange + const args = { + projectRoot: testProjectRoot, + file: 'non-existent-file.json' + }; + + // Act + const result = await listTasksDirect(args, mockLogger); + + // Assert + expect(result.success).toBe(false); + expect(result.error).toBeDefined(); + expect(result.error.code).toBeDefined(); + expect(result.error.message).toBeDefined(); + expect(mockLogger.error).toHaveBeenCalled(); + }); + }); +}); \ No newline at end of file