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",
|
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",
|
||||||
"MODEL": "claude-3-7-sonnet-20250219",
|
"MODEL": "claude-3-7-sonnet-20250219",
|
||||||
"PERPLEXITY_MODEL": "sonar-pro",
|
"PERPLEXITY_MODEL": "sonar-pro",
|
||||||
"MAX_TOKENS": 128000,
|
"MAX_TOKENS": 64000,
|
||||||
"TEMPERATURE": 0.2,
|
"TEMPERATURE": 0.2,
|
||||||
"DEFAULT_SUBTASKS": 5,
|
"DEFAULT_SUBTASKS": 5,
|
||||||
"DEFAULT_PRIORITY": "medium"
|
"DEFAULT_PRIORITY": "medium"
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export async function initializeProjectDirect(args, log, context = {}) {
|
|||||||
resultData = {
|
resultData = {
|
||||||
message: 'Project initialized successfully.',
|
message: 'Project initialized successfully.',
|
||||||
next_step:
|
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
|
...result // Include details returned by initializeProject
|
||||||
};
|
};
|
||||||
success = true;
|
success = true;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import os from 'os'; // Import os module for home directory check
|
||||||
import { parsePRD } from '../../../../scripts/modules/task-manager.js';
|
import { parsePRD } from '../../../../scripts/modules/task-manager.js';
|
||||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||||
import {
|
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) {
|
if (!args.input) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
'No input file specified. Please provide an input PRD document path.';
|
'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)
|
// Validate projectRoot
|
||||||
const projectRoot = args.projectRoot || process.cwd();
|
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)
|
const inputPath = path.isAbsolute(args.input)
|
||||||
? args.input
|
? args.input
|
||||||
: path.resolve(projectRoot, args.input);
|
: path.resolve(projectRoot, args.input);
|
||||||
|
|
||||||
|
log.info(`Resolved input path: ${inputPath}`);
|
||||||
|
|
||||||
// Determine output path
|
// Determine output path
|
||||||
let outputPath;
|
let outputPath;
|
||||||
if (args.output) {
|
if (args.output) {
|
||||||
@@ -75,13 +115,19 @@ export async function parsePRDDirect(args, log, context = {}) {
|
|||||||
outputPath = path.resolve(projectRoot, 'tasks', 'tasks.json');
|
outputPath = path.resolve(projectRoot, 'tasks', 'tasks.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(`Resolved output path: ${outputPath}`);
|
||||||
|
|
||||||
// Verify input file exists
|
// Verify input file exists
|
||||||
if (!fs.existsSync(inputPath)) {
|
if (!fs.existsSync(inputPath)) {
|
||||||
const errorMessage = `Input file not found: ${inputPath}`;
|
const errorMessage = `Input file not found: ${inputPath}`;
|
||||||
log.error(errorMessage);
|
log.error(errorMessage);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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
|
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
|
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||||
enableSilentMode();
|
enableSilentMode();
|
||||||
try {
|
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
|
// Execute core parsePRD function with AI client
|
||||||
await parsePRD(
|
await parsePRD(
|
||||||
inputPath,
|
inputPath,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function registerParsePRDTool(server) {
|
|||||||
server.addTool({
|
server.addTool({
|
||||||
name: 'parse_prd',
|
name: 'parse_prd',
|
||||||
description:
|
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({
|
parameters: z.object({
|
||||||
input: z
|
input: z
|
||||||
.string()
|
.string()
|
||||||
@@ -43,22 +43,35 @@ export function registerParsePRDTool(server) {
|
|||||||
.describe('Allow overwriting an existing tasks.json file.'),
|
.describe('Allow overwriting an existing tasks.json file.'),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
|
||||||
.describe(
|
.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 }) => {
|
execute: async (args, { log, session }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Parsing PRD with args: ${JSON.stringify(args)}`);
|
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) {
|
// Only if args.projectRoot is undefined or null, try to get it from session
|
||||||
rootFolder = args.projectRoot;
|
if (!rootFolder) {
|
||||||
log.info(`Using project root from args as fallback: ${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(
|
const result = await parsePRDDirect(
|
||||||
{
|
{
|
||||||
projectRoot: rootFolder,
|
projectRoot: rootFolder,
|
||||||
|
|||||||
Reference in New Issue
Block a user