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.
This commit is contained in:
Eyal Toledano
2025-04-11 02:27:02 -04:00
parent b1b46e38da
commit 5fb302c95b
4 changed files with 79 additions and 13 deletions

View File

@@ -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"

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,