parse_prd
Transform Product Requirements Documents (PRDs) into structured tasks with dependencies, priorities, and complexity estimates. Automatically generate actionable task breakdowns for efficient project management.
Instructions
Parse a Product Requirements Document (PRD) and automatically generate structured tasks with dependencies, priorities, and complexity estimates. Transform high-level requirements into actionable task breakdowns with intelligent analysis.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| defaultPriority | No | Default priority for generated tasks (1-10) | |
| estimateComplexity | No | Whether to estimate complexity for tasks | |
| generateSubtasks | No | Whether to generate subtasks for complex tasks | |
| prdContent | Yes | Content of the Product Requirements Document to parse | |
| projectId | Yes | ID of the project to add tasks to | |
| workingDirectory | Yes | The full absolute path to the working directory where data is stored. MUST be an absolute path, never relative. Windows: "C:\Users\username\project" or "D:\projects\my-app". Unix/Linux/macOS: "/home/username/project" or "/Users/username/project". Do NOT use: ".", "..", "~", "./folder", "../folder" or any relative paths. Ensure the path exists and is accessible before calling this tool. NOTE: When server is started with --claude flag, this parameter is ignored and a global user directory is used instead. |
Implementation Reference
- src/server.ts:903-936 (registration)Registration of the 'parse_prd' tool in the MCP server using server.tool(), which creates a ParsePRDTool instance via createParsePRDTool and wraps its handler.server.tool( 'parse_prd', 'Parse a Product Requirements Document (PRD) and automatically generate structured tasks with dependencies, priorities, and complexity estimates. Transform high-level requirements into actionable task breakdowns with intelligent analysis.', { workingDirectory: z.string().describe(getWorkingDirectoryDescription(config)), projectId: z.string().describe('ID of the project to add tasks to'), prdContent: z.string().describe('Content of the Product Requirements Document to parse'), generateSubtasks: z.boolean().optional().default(true).describe('Whether to generate subtasks for complex tasks'), defaultPriority: z.number().min(1).max(10).optional().default(5).describe('Default priority for generated tasks (1-10)'), estimateComplexity: z.boolean().optional().default(true).describe('Whether to estimate complexity for tasks') }, async ({ workingDirectory, projectId, prdContent, generateSubtasks, defaultPriority, estimateComplexity }: { workingDirectory: string; projectId: string; prdContent: string; generateSubtasks?: boolean; defaultPriority?: number; estimateComplexity?: boolean; }) => { try { const storage = await createStorage(workingDirectory, config); const tool = createParsePRDTool(storage, getWorkingDirectoryDescription, config); return await tool.handler({ workingDirectory, projectId, prdContent, generateSubtasks, defaultPriority, estimateComplexity }); } catch (error) { return { content: [{ type: 'text' as const, text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } } );
- The main execution logic of the parse_prd tool: validates project, parses PRD content into tasks using helper, creates tasks in storage, handles dependencies, builds hierarchical response with generated tasks summary and next action suggestions.handler: async (args: any) => { try { const { workingDirectory, projectId, prdContent, generateSubtasks, defaultPriority, estimateComplexity } = args; // Validate project exists const project = await storage.getProject(projectId); if (!project) { return { content: [{ type: 'text' as const, text: `Error: Project with ID "${projectId}" not found.` }], isError: true }; } // Parse PRD content and extract tasks const parsedTasks = await parsePRDContent(prdContent, projectId, defaultPriority, estimateComplexity); // Create tasks in storage const createdTasks: Task[] = []; const taskDependencyMap = new Map<string, string[]>(); for (const taskData of parsedTasks) { const now = new Date().toISOString(); const task: Task = { id: randomUUID(), name: taskData.name, details: taskData.details, projectId, parentId: taskData.parentId, completed: false, createdAt: now, updatedAt: now, dependsOn: taskData.dependsOn || [], priority: taskData.priority || defaultPriority, complexity: taskData.complexity, status: 'pending', tags: taskData.tags || [], estimatedHours: taskData.estimatedHours }; const createdTask = await storage.createTask(task); createdTasks.push(createdTask); if (taskData.dependsOn && taskData.dependsOn.length > 0) { taskDependencyMap.set(createdTask.id, taskData.dependsOn); } } // Update dependencies with actual task IDs await updateTaskDependencies(storage, createdTasks, taskDependencyMap); // Build response text safely let responseText = `✅ PRD parsed successfully! Generated ${createdTasks.length} tasks for project "${project.name}". 📋 **Generated Tasks:**\n`; for (const task of createdTasks) { const levelIndicator = ' '.repeat(task.level || 0) + '→'; responseText += `${levelIndicator} **${task.name}** (Priority: ${task.priority}, Complexity: ${task.complexity || 'N/A'}) ${task.details.substring(0, 100)}${task.details.length > 100 ? '...' : ''} Dependencies: ${task.dependsOn?.length ? task.dependsOn.join(', ') : 'None'} Tags: ${task.tags?.join(', ') || 'None'}\n\n`; } responseText += `👉 **Your Actions: Review Tasks and Determine Next Steps** 1. **Review and Refine Generated Tasks:** Carefully examine each task generated from the PRD. If you need to adjust names, details, priorities, complexity, or dependencies, use the \`update_task\` tool. * Example: \`update_task({ id: "task_id_to_update", name: "new_task_name", details: "updated_details", priority: 7 })\` 2. **Create Nested Tasks:** For complex tasks, you can break them down further using \`create_task\` with parentId. * Example: \`create_task({ projectId: "${project.id}", parentId: "parent_task_id", name: "subtask_name", details: "subtask_details" })\` 3. **Identify Starting Task:** Once you are satisfied with the task definitions, use the \`get_next_task_recommendation\` tool to identify the best task to begin with in this project. * Example: \`get_next_task_recommendation({ projectId: "${project.id}" })\` 4. **Begin Implementation:** After getting a recommendation, you can start working on the suggested task. Remember to update its status using \`update_task\` (e.g., set to 'in-progress').`; return { content: [{ type: 'text' as const, text: responseText }] }; } catch (error: any) { return { content: [{ type: 'text' as const, text: `Error parsing PRD: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } }
- Zod schema defining the input parameters for the parse_prd tool, matching the one used in server registration.inputSchema: z.object({ workingDirectory: z.string().describe(getWorkingDirectoryDescription(config)), projectId: z.string().describe('ID of the project to add tasks to'), prdContent: z.string().describe('Content of the Product Requirements Document to parse'), generateSubtasks: z.boolean().optional().default(true).describe('Whether to generate subtasks for complex tasks'), defaultPriority: z.number().min(1).max(10).optional().default(5).describe('Default priority for generated tasks (1-10)'), estimateComplexity: z.boolean().optional().default(true).describe('Whether to estimate complexity for tasks') }),
- Helper function that implements the PRD parsing logic: splits content into sections, matches task patterns (bullets, numbered, action verbs), estimates complexity/tags/hours based on keywords, provides fallback task.async function parsePRDContent(prdContent: string, projectId: string, defaultPriority: number, estimateComplexity: boolean): Promise<CreateTaskInput[]> { const tasks: CreateTaskInput[] = []; // Simple parsing logic - look for common patterns in PRDs const sections = prdContent.split(/\n\s*\n/); for (const section of sections) { const lines = section.trim().split('\n'); if (lines.length === 0) continue; // Look for task-like patterns const taskPatterns = [ /^[-*]\s+(.+)/, // Bullet points /^\d+\.\s+(.+)/, // Numbered lists /^(implement|create|build|develop|design|add|setup|configure)\s+(.+)/i, // Action verbs /^(feature|requirement|task):\s*(.+)/i // Explicit task markers ]; for (const line of lines) { for (const pattern of taskPatterns) { const match = line.match(pattern); if (match) { const taskName = match[1] || match[2]; if (taskName && taskName.length > 5) { // Filter out very short matches // Estimate complexity based on keywords let complexity = undefined; if (estimateComplexity) { complexity = estimateTaskComplexity(taskName, section); } // Extract tags from content const tags = extractTags(taskName, section); // Estimate hours based on complexity and content const estimatedHours = estimateHours(complexity, taskName, section); const taskDetails = section.substring(0, 500) + (section.length > 500 ? '...' : ''); tasks.push({ name: taskName.trim(), details: taskDetails, projectId, priority: defaultPriority, complexity, tags, estimatedHours }); } } } } } // If no tasks found, create a general implementation task if (tasks.length === 0) { tasks.push({ name: 'Implement PRD Requirements', details: prdContent.substring(0, 1000), projectId, priority: defaultPriority, complexity: estimateComplexity ? 7 : undefined, tags: ['implementation', 'prd'], estimatedHours: 40 }); } return tasks; }
- Helper function to score task complexity (1-10) based on keyword analysis in high/medium/low complexity categories.function estimateTaskComplexity(taskName: string, context: string): number { const complexityKeywords = { high: ['architecture', 'system', 'integration', 'security', 'performance', 'scalability', 'database', 'api', 'framework'], medium: ['component', 'feature', 'interface', 'validation', 'testing', 'configuration'], low: ['button', 'text', 'style', 'color', 'layout', 'simple', 'basic'] }; const content = (taskName + ' ' + context).toLowerCase(); let score = 5; // Default medium complexity for (const keyword of complexityKeywords.high) { if (content.includes(keyword)) score += 2; } for (const keyword of complexityKeywords.medium) { if (content.includes(keyword)) score += 1; } for (const keyword of complexityKeywords.low) { if (content.includes(keyword)) score -= 1; } return Math.max(1, Math.min(10, score)); }