feat(mcp): Refactor MCP tools for direct function calls & add docs
This commit introduces a major refactoring of the MCP server implementation to prioritize direct function calls over CLI execution, enhancing performance and reliability. It also includes substantial updates to documentation for consistency and interlinking.
**MCP Server & Core Logic Refactoring:**
1. **Introduce Direct Function Wrappers ():**
* Created to house direct wrappers for core Task Master functions (imported from ).
* Implemented as the first wrapper, calling .
* Added utility within to centralize file location logic, removing duplication.
* Established the map for registering these wrappers.
2. **Enhance MCP Utilities ():**
* Added : A primary utility function to streamline MCP tool methods. It handles logging, argument processing (incl. project root normalization), calling the direct action function (e.g., ), processing results via , and formatting the final MCP response.
* Added : Standardizes processing of objects returned by direct function wrappers.
* Added : Filters sensitive/large fields (like , ) from responses sent to the MCP client.
* Added , , to support the new workflow.
* Refactored to use internally, simplifying its usage (though it's now primarily a fallback).
3. **Update MCP Tools (, ):**
* Refactored to use with , significantly simplifying the tool's method.
* Updated (initially) to use the improved (Note: further refactoring to use a direct wrapper for would follow the pattern).
**Documentation Enhancements:**
4. **Comprehensive Interlinking:** Added links across rule files (, , , , , ) to connect related guidelines, improving navigation.
5. **Standardize CLI Syntax ():** Removed legacy ℹ️ Initialized Perplexity client with OpenAI compatibility layer examples, reinforcing ℹ️ Initialized Perplexity client with OpenAI compatibility layer
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master as the primary CLI.
6. **Add MCP Architecture & Workflow Sections:** Added detailed sections in and explaining MCP server structure and the workflow for adding new MCP tool integrations using the direct function pattern.
7. **Clarify MCP Role:** Updated and to better explain the MCP server's role and its specific utilities.
Overall, this establishes a cleaner, more performant, and maintainable pattern for integrating Task Master functionality with the MCP server, supported by improved documentation.
This commit is contained in:
@@ -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,18 @@ 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.
|
||||
- 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 +120,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
|
||||
|
||||
@@ -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 <command>` instead of `node scripts/dev.js <command>`
|
||||
- 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=<prd-file.txt>` 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="<context>"` 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=<prd-file.txt>`
|
||||
- CLI Syntax: `task-master parse-prd --input=<prd-file.txt>`
|
||||
- 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=<file>`: 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=<id> --prompt="<prompt>"`
|
||||
- CLI Syntax: `task-master update --from=<id> --prompt="<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=<id> --prompt="<prompt>"`
|
||||
- CLI Syntax: `task-master update-task --id=<id> --prompt="<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=<id> --prompt="<prompt>"`
|
||||
- CLI Syntax: `task-master update-subtask --id=<id> --prompt="<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=<id> --status=<status>`
|
||||
- CLI Syntax: `task-master set-status --id=<id> --status=<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=<id> [--num=<number>] [--research] [--prompt="<context>"]`
|
||||
- CLI Syntax: `task-master expand --id=<id> [--num=<number>] [--research] [--prompt="<context>"]`
|
||||
- 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=<id>`
|
||||
- CLI Syntax: `task-master clear-subtasks --id=<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=<id> --depends-on=<id>`
|
||||
- CLI Syntax: `task-master add-dependency --id=<id> --depends-on=<id>`
|
||||
- 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=<id> --depends-on=<id>`
|
||||
- CLI Syntax: `task-master remove-dependency --id=<id> --depends-on=<id>`
|
||||
- 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:
|
||||
|
||||
73
.cursor/rules/mcp.mdc
Normal file
73
.cursor/rules/mcp.mdc
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
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.
|
||||
|
||||
## 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.
|
||||
- Create an `async function yourCommandDirect(args, log)` wrapper.
|
||||
- Inside the wrapper:
|
||||
- Use `findTasksJsonPath(args, log)` if the command needs the tasks file path.
|
||||
- Extract and validate arguments from `args`.
|
||||
- Call the imported core logic function.
|
||||
- Handle errors using `try/catch`.
|
||||
- Return a standardized object: `{ success: true, data: result }` or `{ success: false, error: { code: '...', message: '...' } }`.
|
||||
- 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`.
|
||||
@@ -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,50 @@ 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.
|
||||
- It returns a standard `{ success: true/false, data/error }` 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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,67 @@ 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`.
|
||||
|
||||
## 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 +365,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.
|
||||
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.
|
||||
2128
docs/mcp-protocol-schema-03262025.json
Normal file
2128
docs/mcp-protocol-schema-03262025.json
Normal file
File diff suppressed because it is too large
Load Diff
119
mcp-server/src/core/task-master-core.js
Normal file
119
mcp-server/src/core/task-master-core.js
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* 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
|
||||
throw new Error(`Tasks file not found in any of the expected locations relative to ${projectRoot}: ${possiblePaths.join(', ')}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct function wrapper for listTasks with error handling
|
||||
*
|
||||
* @param {Object} args - Command arguments (projectRoot is expected to be resolved)
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Object} - Task list result
|
||||
*/
|
||||
export async function listTasksDirect(args, log) {
|
||||
try {
|
||||
log.info(`Listing tasks with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use the helper function to find the tasks file path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
|
||||
// Extract other arguments
|
||||
const statusFilter = args.status || null;
|
||||
const withSubtasks = args.withSubtasks || false;
|
||||
|
||||
log.info(`Using tasks file at: ${tasksPath}`);
|
||||
log.info(`Status filter: ${statusFilter}, withSubtasks: ${withSubtasks}`);
|
||||
|
||||
// Call listTasks with json format
|
||||
const result = listTasks(tasksPath, statusFilter, withSubtasks, 'json');
|
||||
|
||||
if (!result || !result.tasks) {
|
||||
throw new Error('Invalid or empty response from listTasks function');
|
||||
}
|
||||
|
||||
log.info(`Successfully retrieved ${result.tasks.length} tasks`);
|
||||
|
||||
// Return the raw result directly
|
||||
return {
|
||||
success: true,
|
||||
data: result // Return the actual object, not a stringified version
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(`Error in listTasksDirect: ${error.message}`);
|
||||
|
||||
// Ensure we always return a properly structured error object
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'LIST_TASKS_ERROR',
|
||||
message: error.message || 'Unknown error occurred'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps Task Master functions to their direct implementation
|
||||
*/
|
||||
export const directFunctions = {
|
||||
list: listTasksDirect,
|
||||
// Add more functions as we implement them
|
||||
};
|
||||
@@ -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)"
|
||||
),
|
||||
@@ -35,31 +36,18 @@ export function registerListTasksTool(server) {
|
||||
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}`);
|
||||
// Call core function - args contains projectRoot which is handled internally
|
||||
const result = await listTasksDirect(args, log);
|
||||
|
||||
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);
|
||||
// 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,22 +4,67 @@
|
||||
*/
|
||||
|
||||
import { spawnSync } from "child_process";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* 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}`;
|
||||
log.error(`${errorPrefix}: ${errorMsg}`);
|
||||
return createErrorResponse(errorMsg);
|
||||
}
|
||||
|
||||
// Process the result data if needed and if we have a processor function
|
||||
const processedData = processFunction ? processFunction(result.data) : result.data;
|
||||
|
||||
// Return formatted response
|
||||
return createContentResponse(processedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +121,152 @@ export function executeTaskMasterCommand(
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates standard content response for tools
|
||||
* @param {string} text - Text content to include in response
|
||||
* @returns {Object} - Content response object
|
||||
* Executes a Task Master tool action with standardized error handling, logging, and response formatting
|
||||
* @param {Object} options - Options for executing the tool action
|
||||
* @param {Function} options.actionFn - The core action function to execute (must return {success, data, error})
|
||||
* @param {Object} options.args - Arguments for the action
|
||||
* @param {Object} options.log - Logger object from FastMCP
|
||||
* @param {string} options.actionName - Name of the action for logging purposes
|
||||
* @param {Function} options.processResult - Optional function to process the result before returning
|
||||
* @returns {Promise<Object>} - Standardized response for FastMCP
|
||||
*/
|
||||
export function createContentResponse(text) {
|
||||
export async function executeMCPToolAction({
|
||||
actionFn,
|
||||
args,
|
||||
log,
|
||||
actionName,
|
||||
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}`);
|
||||
|
||||
// Execute the core action function with normalized arguments
|
||||
const result = await actionFn({...args, projectRoot}, log);
|
||||
|
||||
// Handle error case
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error?.message || `Unknown error during ${actionName.toLowerCase()}`;
|
||||
log.error(`Error during ${actionName.toLowerCase()}: ${errorMsg}`);
|
||||
return createErrorResponse(errorMsg);
|
||||
}
|
||||
|
||||
// Log success
|
||||
log.info(`Successfully completed ${actionName.toLowerCase()}`);
|
||||
|
||||
// Process the result data if needed
|
||||
const processedData = processResult ? processResult(result.data) : result.data;
|
||||
|
||||
// Return formatted response
|
||||
return createContentResponse(processedData);
|
||||
} catch (error) {
|
||||
// Handle unexpected errors
|
||||
log.error(`Unexpected error during ${actionName.toLowerCase()}: ${error.message}`);
|
||||
return createErrorResponse(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",
|
||||
},
|
||||
],
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -987,10 +987,16 @@ 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 {
|
||||
// Only display banner for text output
|
||||
if (outputFormat === 'text') {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
@@ -1030,6 +1036,46 @@ 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 => {
|
||||
// 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, // Return the modified tasks
|
||||
filter: statusFilter,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
191
tests/integration/mcp-server/direct-functions.test.js
Normal file
191
tests/integration/mcp-server/direct-functions.test.js
Normal file
@@ -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, '../../fixtures/test-project');
|
||||
const testTasksPath = path.join(testProjectRoot, '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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user