Skip to main content
Glama

generate-prd

Automate the creation of detailed product requirements documents (PRDs) from product descriptions. Ideal for streamlining project planning and development workflows.

Instructions

Creates comprehensive product requirements documents based on a product description and research.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
productDescriptionYesDescription of the product to create a PRD for

Implementation Reference

  • The primary handler/executor for the 'generate-prd' tool (registered as 'prd-generator'). It orchestrates background job creation, pre-generation research via Perplexity, LLM-based PRD markdown generation with strict system prompt, file saving, progress updates via SSE, and comprehensive error handling.
    export const generatePRD: ToolExecutor = async (
      params: Record<string, unknown>, // More type-safe than 'any'
      config: OpenRouterConfig,
      context?: ToolExecutionContext // Add context parameter
    ): Promise<CallToolResult> => { // Return CallToolResult
      // ---> Step 2.5(PRD).2: Inject Dependencies & Get Session ID <---
      const sessionId = context?.sessionId || 'unknown-session';
      if (sessionId === 'unknown-session') {
          logger.warn({ tool: 'generatePRD' }, 'Executing tool without a valid sessionId. SSE progress updates will not be sent.');
      }
    
      // Log the config received by the executor
      logger.debug({
        configReceived: true,
        hasLlmMapping: Boolean(config.llm_mapping),
        mappingKeys: config.llm_mapping ? Object.keys(config.llm_mapping) : []
      }, 'generatePRD executor received config');
    
      const productDescription = params.productDescription as string; // Assert type after validation
    
      // ---> Step 2.5(PRD).3: Create Job & Return Job ID <---
      const jobId = jobManager.createJob('generate-prd', params);
      logger.info({ jobId, tool: 'generatePRD', sessionId }, 'Starting background job.');
    
      // Use the shared service to format the initial response
      const initialResponse = formatBackgroundJobInitiationResponse(
        jobId,
        'generate-prd', // Internal tool name
        'PRD Generator'   // User-friendly display name
      );
    
      // ---> Step 2.5(PRD).4: Wrap Logic in Async Block <---
      setImmediate(async () => {
        const logs: string[] = []; // Keep logs specific to this job execution
        let filePath: string = ''; // Define filePath in outer scope for catch block
    
        // ---> Step 2.5(PRD).7: Update Final Result/Error Handling (Try Block Start) <---
        try {
          // ---> Step 2.5(PRD).6: Add Progress Updates (Initial) <---
          jobManager.updateJobStatus(jobId, JobStatus.RUNNING, 'Starting PRD generation process...');
          sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, 'Starting PRD generation process...');
          logs.push(`[${new Date().toISOString()}] Starting PRD generation for: ${productDescription.substring(0, 50)}...`);
    
          // Ensure directories are initialized before writing
          const prdDir = await initDirectories(context);
    
        // Generate a filename for storing the PRD
          const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
          const sanitizedName = productDescription.substring(0, 60).toLowerCase().replace(/[^a-z0-9]+/g, '-');
          const filename = `${timestamp}-${sanitizedName}-prd.md`;
          filePath = path.join(prdDir, filename); // Assign to outer scope variable
    
          // ---> Step 2.5(PRD).6: Add Progress Updates (Research Start) <---
          logger.info({ jobId, inputs: { productDescription: productDescription.substring(0, 50) } }, "PRD Generator: Starting pre-generation research...");
          jobManager.updateJobStatus(jobId, JobStatus.RUNNING, 'Performing pre-generation research...');
          sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, 'Performing pre-generation research...');
          logs.push(`[${new Date().toISOString()}] Starting pre-generation research.`);
    
          let researchContext = '';
        try {
          // Define relevant research queries
          const query1 = `Market analysis and competitive landscape for: ${productDescription}`;
          const query2 = `User needs, demographics, and expectations for: ${productDescription}`;
          const query3 = `Industry standards, best practices, and common feature sets for products like: ${productDescription}`;
    
          // Execute research queries in parallel using Perplexity
          const researchResults = await Promise.allSettled([
            performResearchQuery(query1, config), // Uses config.perplexityModel (perplexity/sonar-deep-research)
            performResearchQuery(query2, config),
            performResearchQuery(query3, config)
          ]);
    
          // Process research results
          researchContext = "## Pre-Generation Research Context (From Perplexity Sonar Deep Research):\n\n";
    
          // Add results that were fulfilled
          researchResults.forEach((result, index) => {
            const queryLabels = ["Market Analysis", "User Needs & Expectations", "Industry Standards & Best Practices"];
            if (result.status === "fulfilled") {
              researchContext += `### ${queryLabels[index]}:\n${result.value.trim()}\n\n`;
            } else {
              logger.warn({ error: result.reason }, `Research query ${index + 1} failed`);
              researchContext += `### ${queryLabels[index]}:\n*Research on this topic failed.*\n\n`;
            }
          });
    
          // ---> Step 2.5(PRD).6: Add Progress Updates (Research End) <---
          logger.info({ jobId }, "PRD Generator: Pre-generation research completed.");
          jobManager.updateJobStatus(jobId, JobStatus.RUNNING, 'Research complete. Starting main PRD generation...');
          sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, 'Research complete. Starting main PRD generation...');
          logs.push(`[${new Date().toISOString()}] Pre-generation research completed.`);
    
        } catch (researchError) {
          logger.error({ jobId, err: researchError }, "PRD Generator: Error during research aggregation");
          logs.push(`[${new Date().toISOString()}] Error during research aggregation: ${researchError instanceof Error ? researchError.message : String(researchError)}`);
          // Include error in context but continue
          researchContext = "## Pre-Generation Research Context:\n*Error occurred during research phase.*\n\n";
          sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, 'Warning: Error during research phase. Continuing generation...');
        }
    
        // Create the main generation prompt with combined research and inputs
        const mainGenerationPrompt = `Create a comprehensive PRD for the following product:\n\n${productDescription}\n\n${researchContext}`;
    
        // ---> Step 2.5(PRD).6: Add Progress Updates (LLM Call Start) <---
        logger.info({ jobId }, "PRD Generator: Starting main generation using direct LLM call...");
        jobManager.updateJobStatus(jobId, JobStatus.RUNNING, 'Generating PRD content via LLM...');
        sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, 'Generating PRD content via LLM...');
        logs.push(`[${new Date().toISOString()}] Calling LLM for main PRD generation.`);
    
        const prdMarkdown = await performFormatAwareLlmCallWithCentralizedConfig(
          mainGenerationPrompt,
          PRD_SYSTEM_PROMPT, // Pass the system prompt
          'prd_generation', // Logical task name
          'markdown', // Explicitly specify markdown format
          undefined, // No schema for markdown
          0.3 // Slightly higher temp might be okay for PRD text
        );
    
        // ---> Step 2.5(PRD).6: Add Progress Updates (LLM Call End) <---
        logger.info({ jobId }, "PRD Generator: Main generation completed.");
        jobManager.updateJobStatus(jobId, JobStatus.RUNNING, 'Processing LLM response...');
        sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, 'Processing LLM response...');
        logs.push(`[${new Date().toISOString()}] Received response from LLM.`);
    
        // Basic validation: Check if the output looks like Markdown and contains expected elements
        if (!prdMarkdown || typeof prdMarkdown !== 'string' || !prdMarkdown.trim().startsWith('# PRD:')) {
          logger.warn({ jobId, markdown: prdMarkdown?.substring(0, 100) }, 'PRD generation returned empty or potentially invalid Markdown format.');
          logs.push(`[${new Date().toISOString()}] Validation Error: LLM output invalid format.`);
          throw new ToolExecutionError('PRD generation returned empty or invalid Markdown content.');
        }
    
        // Format the PRD (already should be formatted by LLM, just add timestamp)
        const formattedResult = `${prdMarkdown}\n\n_Generated: ${new Date().toLocaleString()}_`;
    
        // ---> Step 2.5(PRD).6: Add Progress Updates (Saving File) <---
        logger.info({ jobId }, `Saving PRD to ${filePath}...`);
        jobManager.updateJobStatus(jobId, JobStatus.RUNNING, `Saving PRD to file...`);
        sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, `Saving PRD to file...`);
        logs.push(`[${new Date().toISOString()}] Saving PRD to ${filePath}.`);
    
        // Save the result
        try {
          await fs.writeFile(filePath, formattedResult, 'utf8');
          logger.info({ jobId }, `PRD generated and saved to ${filePath}`);
          logs.push(`[${new Date().toISOString()}] PRD saved successfully.`);
        } catch (fileError) {
          const errorDetails = fileError instanceof Error ? fileError.message : String(fileError);
          logger.error({ err: fileError, jobId, filePath }, `Failed to write PRD file: ${errorDetails}`);
          logs.push(`[${new Date().toISOString()}] File write error: ${errorDetails} for path: ${filePath}`);
          throw new AppError(`Failed to save PRD file to ${filePath}: ${errorDetails}`, { code: 'FILE_WRITE_ERROR', filePath }, fileError as Error);
        }
        sseNotifier.sendProgress(sessionId, jobId, JobStatus.RUNNING, `PRD saved successfully.`);
    
        // ---> Step 2.5(PRD).7: Update Final Result/Error Handling (Set Success Result) <---
        const finalResult: CallToolResult = {
          // Include file path in success message
          content: [{ type: "text", text: `PRD generated successfully and saved to: ${filePath}\n\n${formattedResult}` }],
          isError: false
        };
        jobManager.setJobResult(jobId, finalResult);
        // Optional explicit SSE: sseNotifier.sendProgress(sessionId, jobId, JobStatus.COMPLETED, 'PRD generation completed successfully.');
    
        // ---> Step 2.5(PRD).7: Update Final Result/Error Handling (Catch Block) <---
        } catch (error) {
          const errorMsg = error instanceof Error ? error.message : String(error);
          logger.error({ err: error, jobId, tool: 'generate-prd', params }, `PRD Generator Error: ${errorMsg}`);
          logs.push(`[${new Date().toISOString()}] Error: ${errorMsg}`);
    
          // Handle specific errors from direct call or research
          let appError: AppError;
          const cause = error instanceof Error ? error : undefined;
          if (error instanceof AppError) {
            appError = error;
          } else {
            appError = new ToolExecutionError(`Failed to generate PRD: ${errorMsg}`, { params, filePath }, cause);
          }
    
          const mcpError = new McpError(ErrorCode.InternalError, appError.message, appError.context);
          const errorResult: CallToolResult = {
            content: [{ type: 'text', text: `Error during background job ${jobId}: ${mcpError.message}\n\nLogs:\n${logs.join('\n')}` }],
            isError: true,
            errorDetails: mcpError
          };
    
          // Store error result in Job Manager
          jobManager.setJobResult(jobId, errorResult);
          // Send final failed status via SSE (optional if jobManager handles it)
          sseNotifier.sendProgress(sessionId, jobId, JobStatus.FAILED, `Job failed: ${mcpError.message}`);
        }
      }); // ---> END OF setImmediate WRAPPER <---
    
      return initialResponse; // Return the initial response with Job ID
    };
  • Zod input schema definition for the PRD generator tool, validating the required 'productDescription' parameter.
    const prdInputSchemaShape = {
      productDescription: z.string().min(10, { message: "Product description must be at least 10 characters." }).describe("Description of the product to create a PRD for")
    };
  • Tool definition object and registration call to the central tool registry, which enables dynamic MCP server registration in src/server.ts.
    const prdToolDefinition: ToolDefinition = {
      name: "prd-generator",
      description: "Creates comprehensive product requirements documents based on a product description and research.",
      inputSchema: prdInputSchemaShape, // Use the raw shape
      executor: generatePRD // Reference the adapted function
    };
    
    // Register the tool with the central registry
    registerTool(prdToolDefinition);
  • Exported system prompt used by the LLM for generating structured PRD markdown, including strict format requirements and research integration instructions.
    // PRD-specific system prompt (Exported for testing)
    export const PRD_SYSTEM_PROMPT = `
    # ROLE & GOAL
    You are an expert Product Manager and Technical Writer AI assistant. Your goal is to generate a comprehensive, clear, and well-structured Product Requirements Document (PRD) in Markdown format based on the provided inputs.
    
    # CORE TASK
    Generate a detailed PRD based on the user's product description and the research context provided.
    
    # INPUT HANDLING
    - The primary input is the user's 'productDescription'. Analyze it carefully to understand the core concept, features, and goals.
    - You will also receive 'Pre-Generation Research Context'.
    
    # RESEARCH CONTEXT INTEGRATION
    - **CRITICAL:** Carefully review the '## Pre-Generation Research Context (From Perplexity Sonar Deep Research)' section provided in the user prompt.
    - This section contains insights on: Market Analysis, User Needs & Expectations, and Industry Standards & Best Practices.
    - **Integrate** these insights strategically into the relevant PRD sections. For example:
        - Use 'Market Analysis' to inform the 'Goals' and 'Competitive Landscape' (if included).
        - Use 'User Needs & Expectations' and 'Personas' (if available in research) to define the 'Target Audience' and justify features.
        - Use 'Industry Standards & Best Practices' to guide 'Features & Functionality', 'Technical Considerations', and 'Non-Functional Requirements'.
    - **Synthesize**, don't just copy. Weave the research findings naturally into the PRD narrative.
    - If research context is missing or indicates failure for a topic, note this appropriately (e.g., "Market research was inconclusive, but based on the description...").
    
    # OUTPUT FORMAT & STRUCTURE (Strict Markdown)
    - Your entire response **MUST** be valid Markdown.
    - Start **directly** with the main title: '# PRD: [Inferred Product Name]'
    - Use the following sections with the specified Markdown heading levels. Include all mandatory sections; optional sections can be added if relevant information is available from the description or research.
    
      ## 1. Introduction / Overview (Mandatory)
      - Purpose of the product.
      - High-level summary.
    
      ## 2. Goals (Mandatory)
      - Business goals (e.g., increase market share, user engagement). Use research context if applicable.
      - Product goals (e.g., solve specific user problems, achieve specific functionality).
    
      ## 3. Target Audience (Mandatory)
      - Describe the primary user groups.
      - Incorporate insights on demographics, needs, and pain points from the research context. Use persona descriptions if research provided them.
    
      ## 4. Features & Functionality (Mandatory)
      - Use subheadings (###) for major features or epics.
      - For each feature, use the User Story format:
        - **User Story:** As a [user type/persona], I want to [perform action] so that [I get benefit].
        - **Description:** Further details about the story.
        - **Acceptance Criteria:**
          - GIVEN [context] WHEN [action] THEN [outcome]
          - (Provide multiple specific, testable criteria)
    
      ## 5. Design & UX Considerations (Mandatory)
      - High-level look-and-feel, usability goals. Informed by research on expectations.
    
      ## 6. Technical Considerations (Mandatory)
      - Non-functional requirements (performance, scalability, security - informed by research).
      - Potential technology constraints or suggestions based on research context.
    
      ## 7. Success Metrics (Mandatory)
      - Key Performance Indicators (KPIs) to measure success (e.g., user adoption rate, task completion time). Informed by industry standards research.
    
      ## 8. Open Issues / Questions (Mandatory)
      - List any ambiguities or areas needing further clarification.
    
      ## 9. Out-of-Scope / Future Considerations (Mandatory)
      - Features explicitly not included in this version.
      - Potential future enhancements.
    
    # QUALITY ATTRIBUTES
    - **Comprehensive:** Cover all aspects implied by the description and research.
    - **Clear & Concise:** Use unambiguous language.
    - **Structured:** Strictly adhere to the specified Markdown format and sections.
    - **Actionable:** Requirements should be clear enough for design and development teams.
    - **Accurate:** Reflect the product description and research context faithfully.
    - **Modern:** Incorporate current best practices identified in research.
    
    # CONSTRAINTS (Do NOT Do the Following)
    - **NO Conversational Filler:** Do not include greetings, apologies, self-references ("Here is the PRD...", "I have generated..."). Start directly with the '# PRD: ...' title.
    - **NO Markdown Violations:** Ensure all formatting is correct Markdown. Do not use unsupported syntax.
    - **NO External Knowledge:** Base the PRD *only* on the provided product description and research context. Do not invent unrelated features or use external data.
    - **NO Process Commentary:** Do not mention the research process or the models used (Perplexity/Gemini) within the PRD output itself.
    - **Strict Formatting:** Adhere strictly to the section structure and Markdown heading levels specified.
    `;
  • Helper function to initialize and ensure the PRD output directory exists, with fallback for backward compatibility.
    export async function initDirectories(context?: ToolExecutionContext): Promise<string> {
      try {
        const toolDir = await ensureToolOutputDirectory('prd-generator');
        logger.debug(`Ensured PRD directory exists: ${toolDir}`);
        return toolDir;
      } catch (error) {
        logger.error({ err: error }, `Failed to ensure base output directory exists for prd-generator.`);
        // Fallback to original implementation for backward compatibility
        const baseOutputDir = getBaseOutputDir(context);
        try {
          await fs.ensureDir(baseOutputDir);
          const toolDir = path.join(baseOutputDir, 'prd-generator');
          await fs.ensureDir(toolDir);
          logger.debug(`Ensured PRD directory exists (fallback): ${toolDir}`);
          return toolDir;
        } catch (fallbackError) {
          logger.error({ err: fallbackError, path: baseOutputDir }, `Fallback directory creation also failed.`);
          throw fallbackError;
        }
      }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. While 'creates' implies a write operation, it doesn't specify whether this generates new files, modifies existing ones, requires specific permissions, or has any rate limits. The description mentions 'based on research' but doesn't clarify if research is performed automatically or needs to be provided separately.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that communicates the core purpose without unnecessary words. It's appropriately sized for a single-parameter tool and front-loads the essential information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool that creates comprehensive documents with no annotations and no output schema, the description is insufficient. It doesn't explain what 'comprehensive' means, what sections the PRD includes, whether it generates markdown/PDF/other formats, or what the return value looks like. The mention of 'research' is vague and unexplained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents the single parameter 'productDescription' with its constraints. The description adds minimal value beyond what's in the schema by mentioning this is 'based on a product description', but doesn't provide additional context about format expectations or examples.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'creates' and the resource 'comprehensive product requirements documents', specifying it's based on product description and research. However, it doesn't explicitly differentiate from siblings like 'generate-user-stories' or 'generate-task-list' which might also create documentation artifacts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'generate-user-stories' or 'generate-task-list' which might be more appropriate for specific documentation needs. There's no mention of prerequisites, constraints, or typical use cases.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

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/freshtechbro/vibe-coder-mcp'

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