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:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user