From 684ae5254224f543b18b7894d28c4b61f223024f Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Thu, 3 Apr 2025 00:55:58 -0400 Subject: [PATCH] feat: Adds initialize-project to the MCP tools to enable onboarding to Taskmaster directly from MCP only. --- .cursor/rules/taskmaster.mdc | 13 ++++- mcp-server/src/tools/index.js | 2 + mcp-server/src/tools/initialize-project.js | 62 ++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 mcp-server/src/tools/initialize-project.js diff --git a/.cursor/rules/taskmaster.mdc b/.cursor/rules/taskmaster.mdc index bd0c7cfd..23c4d60c 100644 --- a/.cursor/rules/taskmaster.mdc +++ b/.cursor/rules/taskmaster.mdc @@ -16,7 +16,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov ### 1. Initialize Project (`init`) -* **MCP Tool:** N/A (Note: MCP equivalent is not currently practical, this is a CLI only action) +* **MCP Tool:** `initialize_project` * **CLI Command:** `task-master init [options]` * **Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project.` * **Key CLI Options:** @@ -25,6 +25,17 @@ This document provides a detailed reference for interacting with Taskmaster, cov * `--version `: `Set the initial version for your project (e.g., '0.1.0').` * `-y, --yes`: `Initialize Taskmaster quickly using default settings without interactive prompts.` * **Usage:** Run this once at the beginning of a new project. +* **MCP Variant Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project by running the 'task-master init' command.` +* **Key MCP Parameters/Options:** + * `projectName`: `Set the name for your project.` (CLI: `--name `) + * `projectDescription`: `Provide a brief description for your project.` (CLI: `--description `) + * `projectVersion`: `Set the initial version for your project (e.g., '0.1.0').` (CLI: `--version `) + * `authorName`: `Author name.` (CLI: `--author `) + * `skipInstall`: `Skip installing dependencies (default: false).` (CLI: `--skip-install`) + * `addAliases`: `Add shell aliases (tm, taskmaster) (default: false).` (CLI: `--aliases`) + * `yes`: `Skip prompts and use defaults/provided arguments (default: false).` (CLI: `-y, --yes`) +* **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Cursor. Operates on the current working directory of the MCP server. + ### 2. Parse PRD (`parse_prd`) diff --git a/mcp-server/src/tools/index.js b/mcp-server/src/tools/index.js index f023c051..310d1e24 100644 --- a/mcp-server/src/tools/index.js +++ b/mcp-server/src/tools/index.js @@ -26,6 +26,7 @@ import { registerFixDependenciesTool } from "./fix-dependencies.js"; import { registerComplexityReportTool } from "./complexity-report.js"; import { registerAddDependencyTool } from "./add-dependency.js"; import { registerRemoveTaskTool } from './remove-task.js'; +import { registerInitializeProjectTool } from './initialize-project.js'; /** * Register all Task Master tools with the MCP server @@ -56,6 +57,7 @@ export function registerTaskMasterTools(server) { registerComplexityReportTool(server); registerAddDependencyTool(server); registerRemoveTaskTool(server); + registerInitializeProjectTool(server); } catch (error) { logger.error(`Error registering Task Master tools: ${error.message}`); throw error; diff --git a/mcp-server/src/tools/initialize-project.js b/mcp-server/src/tools/initialize-project.js new file mode 100644 index 00000000..9b7e03b2 --- /dev/null +++ b/mcp-server/src/tools/initialize-project.js @@ -0,0 +1,62 @@ +import { z } from "zod"; +import { execSync } from 'child_process'; +import { createContentResponse, createErrorResponse } from "./utils.js"; // Only need response creators + +export function registerInitializeProjectTool(server) { + server.addTool({ + name: "initialize_project", // snake_case for tool name + description: "Initializes a new Task Master project structure in the current working directory by running 'task-master init'.", + parameters: z.object({ + projectName: z.string().optional().describe("The name for the new project."), + projectDescription: z.string().optional().describe("A brief description for the project."), + projectVersion: z.string().optional().describe("The initial version for the project (e.g., '0.1.0')."), + authorName: z.string().optional().describe("The author's name."), + skipInstall: z.boolean().optional().default(false).describe("Skip installing dependencies automatically."), + addAliases: z.boolean().optional().default(false).describe("Add shell aliases (tm, taskmaster) to shell config file."), + yes: z.boolean().optional().default(false).describe("Skip prompts and use default values or provided arguments."), + // projectRoot is not needed here as 'init' works on the current directory + }), + execute: async (args, { log }) => { // Destructure context to get log + try { + log.info(`Executing initialize_project with args: ${JSON.stringify(args)}`); + + // Construct the command arguments carefully + // Using npx ensures it uses the locally installed version if available, or fetches it + let command = 'npx task-master init'; + const cliArgs = []; + if (args.projectName) cliArgs.push(`--name "${args.projectName.replace(/"/g, '\\"')}"`); // Escape quotes + if (args.projectDescription) cliArgs.push(`--description "${args.projectDescription.replace(/"/g, '\\"')}"`); + if (args.projectVersion) cliArgs.push(`--version "${args.projectVersion.replace(/"/g, '\\"')}"`); + if (args.authorName) cliArgs.push(`--author "${args.authorName.replace(/"/g, '\\"')}"`); + if (args.skipInstall) cliArgs.push('--skip-install'); + if (args.addAliases) cliArgs.push('--aliases'); + if (args.yes) cliArgs.push('--yes'); + + command += ' ' + cliArgs.join(' '); + + log.info(`Constructed command: ${command}`); + + // Execute the command in the current working directory of the server process + // Capture stdout/stderr. Use a reasonable timeout (e.g., 5 minutes) + const output = execSync(command, { encoding: 'utf8', stdio: 'pipe', timeout: 300000 }); + + log.info(`Initialization output:\n${output}`); + + // Return a standard success response manually + return createContentResponse( + "Project initialized successfully.", + { output: output } // Include output in the data payload + ); + + } catch (error) { + // Catch errors from execSync or timeouts + const errorMessage = `Project initialization failed: ${error.message}`; + const errorDetails = error.stderr?.toString() || error.stdout?.toString() || error.message; // Provide stderr/stdout if available + log.error(`${errorMessage}\nDetails: ${errorDetails}`); + + // Return a standard error response manually + return createErrorResponse(errorMessage, { details: errorDetails }); + } + } + }); +} \ No newline at end of file