Skip to main content
Glama

Agentic Tools MCP Server

parse-prd.ts•11.2 kB
import { z } from 'zod'; import { randomUUID } from 'crypto'; import { Task, CreateTaskInput } from '../../models/task.js'; import { Project } from '../../models/project.js'; import { Storage } from '../../storage/storage.js'; /** * Parse a Product Requirements Document (PRD) and generate structured tasks * This tool analyzes PRD content and creates a hierarchical task breakdown with intelligent analysis */ export function createParsePRDTool(storage: Storage, getWorkingDirectoryDescription: (config: any) => string, config: any) { return { name: 'parse_prd', description: 'Parse a Product Requirements Document (PRD) and automatically generate structured tasks with dependencies, priorities, and complexity estimates. This tool analyzes PRD content and creates a comprehensive task breakdown with intelligent analysis.', 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') }), 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 }; } } }; } /** * Parse PRD content and extract task information * This is a simplified parser - in a real implementation, you might use NLP or LLM APIs */ 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; } /** * Estimate task complexity based on content analysis */ 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)); } /** * Extract relevant tags from task content */ function extractTags(taskName: string, context: string): string[] { const content = (taskName + ' ' + context).toLowerCase(); const tags: string[] = []; const tagPatterns = { 'frontend': ['ui', 'interface', 'component', 'react', 'vue', 'angular', 'css', 'html'], 'backend': ['api', 'server', 'database', 'endpoint', 'service', 'microservice'], 'testing': ['test', 'testing', 'unit test', 'integration test', 'e2e'], 'security': ['auth', 'authentication', 'authorization', 'security', 'encryption'], 'performance': ['performance', 'optimization', 'caching', 'speed'], 'documentation': ['docs', 'documentation', 'readme', 'guide'], 'setup': ['setup', 'configuration', 'install', 'deployment', 'environment'] }; for (const [tag, keywords] of Object.entries(tagPatterns)) { if (keywords.some(keyword => content.includes(keyword))) { tags.push(tag); } } return tags; } /** * Estimate hours based on complexity and content */ function estimateHours(complexity: number | undefined, taskName: string, context: string): number { if (!complexity) return 8; // Default 1 day const baseHours = complexity * 2; // 2-20 hours based on complexity // Adjust based on content length and keywords const content = (taskName + ' ' + context).toLowerCase(); let multiplier = 1; if (content.includes('complex') || content.includes('advanced')) multiplier += 0.5; if (content.includes('simple') || content.includes('basic')) multiplier -= 0.3; if (content.includes('research') || content.includes('investigation')) multiplier += 0.4; return Math.max(1, Math.round(baseHours * multiplier)); } /** * Update task dependencies with actual task IDs */ async function updateTaskDependencies(storage: Storage, createdTasks: Task[], dependencyMap: Map<string, string[]>): Promise<void> { // This is a simplified implementation // In a real scenario, you'd need more sophisticated dependency resolution // For now, we'll just clear dependencies since we don't have a way to map them properly for (const task of createdTasks) { if (dependencyMap.has(task.id)) { // Clear dependencies for now - would need more sophisticated mapping await storage.updateTask(task.id, { dependsOn: [] }); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Pimzino/agentic-tools-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server