From 5fb302c95bc21c5664866b0a5137dea8e648e495 Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Fri, 11 Apr 2025 02:27:02 -0400 Subject: [PATCH] feat(mcp): Fix parse-prd tool path resolution Refactors parse-prd MCP tool to properly handle project root and path resolution, fixing the 'Input file not found: /scripts/prd.txt' error. Key changes include: Made projectRoot a required parameter, prioritized args.projectRoot over session-derived paths, added validation to prevent parsing in invalid directories (/, home dir), improved error handling with detailed messages, and added creation of output directory if needed. This resolves issues similar to those fixed in initialize-project, where the tool was incorrectly resolving paths when session context was incomplete. --- .cursor/mcp.json | 2 +- .../initialize-project-direct.js | 2 +- .../src/core/direct-functions/parse-prd.js | 61 +++++++++++++++++-- mcp-server/src/tools/parse-prd.js | 27 +++++--- 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/.cursor/mcp.json b/.cursor/mcp.json index 9e952651..e5433f19 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -8,7 +8,7 @@ "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE", "MODEL": "claude-3-7-sonnet-20250219", "PERPLEXITY_MODEL": "sonar-pro", - "MAX_TOKENS": 128000, + "MAX_TOKENS": 64000, "TEMPERATURE": 0.2, "DEFAULT_SUBTASKS": 5, "DEFAULT_PRIORITY": "medium" diff --git a/mcp-server/src/core/direct-functions/initialize-project-direct.js b/mcp-server/src/core/direct-functions/initialize-project-direct.js index 6b5d97d1..088d124d 100644 --- a/mcp-server/src/core/direct-functions/initialize-project-direct.js +++ b/mcp-server/src/core/direct-functions/initialize-project-direct.js @@ -109,7 +109,7 @@ export async function initializeProjectDirect(args, log, context = {}) { resultData = { message: 'Project initialized successfully.', next_step: - 'Now that the project is initialized, create a PRD file at scripts/prd.txt and use the parse-prd tool to generate initial tasks.', + 'Now that the project is initialized, the next step is to create the tasks by parsing a PRD. This will create the tasks folder and the initial task files (tasks folder will be created when parse-prd is run). The parse-prd tool will require a prd.txt file as input (typically found in the project root directory, scripts/ directory). You can create a prd.txt file by asking the user about their idea, and then using the scripts/example_prd.txt file as a template to genrate a prd.txt file in scripts/. You may skip all of this if the user already has a prd.txt file. You can THEN use the parse-prd tool to create the tasks. So: step 1 after initialization is to create a prd.txt file in scripts/prd.txt or confirm the user already has one. Step 2 is to use the parse-prd tool to create the tasks. Do not bother looking for tasks after initialization, just use the parse-prd tool to create the tasks after creating a prd.txt from which to parse the tasks. You do NOT need to reinitialize the project to parse-prd.', ...result // Include details returned by initializeProject }; success = true; diff --git a/mcp-server/src/core/direct-functions/parse-prd.js b/mcp-server/src/core/direct-functions/parse-prd.js index 2b76bf37..9ab7aad3 100644 --- a/mcp-server/src/core/direct-functions/parse-prd.js +++ b/mcp-server/src/core/direct-functions/parse-prd.js @@ -5,6 +5,7 @@ import path from 'path'; import fs from 'fs'; +import os from 'os'; // Import os module for home directory check import { parsePRD } from '../../../../scripts/modules/task-manager.js'; import { findTasksJsonPath } from '../utils/path-utils.js'; import { @@ -46,7 +47,7 @@ export async function parsePRDDirect(args, log, context = {}) { }; } - // Parameter validation and path resolution + // --- Parameter validation and path resolution --- if (!args.input) { const errorMessage = 'No input file specified. Please provide an input PRD document path.'; @@ -58,12 +59,51 @@ export async function parsePRDDirect(args, log, context = {}) { }; } - // Resolve input path (relative to project root if provided) - const projectRoot = args.projectRoot || process.cwd(); + // Validate projectRoot + if (!args.projectRoot) { + const errorMessage = 'Project root is required but was not provided'; + log.error(errorMessage); + return { + success: false, + error: { code: 'MISSING_PROJECT_ROOT', message: errorMessage }, + fromCache: false + }; + } + + const homeDir = os.homedir(); + // Disallow invalid projectRoot values + if (args.projectRoot === '/' || args.projectRoot === homeDir) { + const errorMessage = `Invalid project root: ${args.projectRoot}. Cannot use root or home directory.`; + log.error(errorMessage); + return { + success: false, + error: { code: 'INVALID_PROJECT_ROOT', message: errorMessage }, + fromCache: false + }; + } + + // Resolve input path (relative to validated project root) + const projectRoot = args.projectRoot; + log.info(`Using validated project root: ${projectRoot}`); + + // Make sure the project root directory exists + if (!fs.existsSync(projectRoot)) { + const errorMessage = `Project root directory does not exist: ${projectRoot}`; + log.error(errorMessage); + return { + success: false, + error: { code: 'PROJECT_ROOT_NOT_FOUND', message: errorMessage }, + fromCache: false + }; + } + + // Resolve input path relative to validated project root const inputPath = path.isAbsolute(args.input) ? args.input : path.resolve(projectRoot, args.input); + log.info(`Resolved input path: ${inputPath}`); + // Determine output path let outputPath; if (args.output) { @@ -75,13 +115,19 @@ export async function parsePRDDirect(args, log, context = {}) { outputPath = path.resolve(projectRoot, 'tasks', 'tasks.json'); } + log.info(`Resolved output path: ${outputPath}`); + // Verify input file exists if (!fs.existsSync(inputPath)) { const errorMessage = `Input file not found: ${inputPath}`; log.error(errorMessage); return { success: false, - error: { code: 'INPUT_FILE_NOT_FOUND', message: errorMessage }, + error: { + code: 'INPUT_FILE_NOT_FOUND', + message: errorMessage, + details: `Checked path: ${inputPath}\nProject root: ${projectRoot}\nInput argument: ${args.input}` + }, fromCache: false }; } @@ -118,6 +164,13 @@ export async function parsePRDDirect(args, log, context = {}) { // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); try { + // Make sure the output directory exists + const outputDir = path.dirname(outputPath); + if (!fs.existsSync(outputDir)) { + log.info(`Creating output directory: ${outputDir}`); + fs.mkdirSync(outputDir, { recursive: true }); + } + // Execute core parsePRD function with AI client await parsePRD( inputPath, diff --git a/mcp-server/src/tools/parse-prd.js b/mcp-server/src/tools/parse-prd.js index e5e4732d..1abf1816 100644 --- a/mcp-server/src/tools/parse-prd.js +++ b/mcp-server/src/tools/parse-prd.js @@ -19,7 +19,7 @@ export function registerParsePRDTool(server) { server.addTool({ name: 'parse_prd', description: - 'Parse a Product Requirements Document (PRD) text file to automatically generate initial tasks.', + "Parse a Product Requirements Document (PRD) text file to automatically generate initial tasks. Reinitializing the project is not necessary to run this tool. It is recommended to run parse-prd after initializing the project and creating/importing a prd.txt file in the project root's scripts/ directory.", parameters: z.object({ input: z .string() @@ -43,22 +43,35 @@ export function registerParsePRDTool(server) { .describe('Allow overwriting an existing tasks.json file.'), projectRoot: z .string() - .optional() .describe( - 'Absolute path to the root directory of the project (default: automatically detected from session or CWD)' + 'Absolute path to the root directory of the project. Required - ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY.' ) }), execute: async (args, { log, session }) => { try { log.info(`Parsing PRD with args: ${JSON.stringify(args)}`); - let rootFolder = getProjectRootFromSession(session, log); + // Make sure projectRoot is passed directly in args or derive from session + // We prioritize projectRoot from args over session-derived path + let rootFolder = args.projectRoot; - if (!rootFolder && args.projectRoot) { - rootFolder = args.projectRoot; - log.info(`Using project root from args as fallback: ${rootFolder}`); + // Only if args.projectRoot is undefined or null, try to get it from session + if (!rootFolder) { + log.warn( + 'projectRoot not provided in args, attempting to derive from session' + ); + rootFolder = getProjectRootFromSession(session, log); + + if (!rootFolder) { + const errorMessage = + 'Could not determine project root directory. Please provide projectRoot parameter.'; + log.error(errorMessage); + return createErrorResponse(errorMessage); + } } + log.info(`Using project root: ${rootFolder} for PRD parsing`); + const result = await parsePRDDirect( { projectRoot: rootFolder,