PRD Creator MCP Server
by Saml1211
Verified
# PRD Creator MCP Server Implementation Plan
## 1. Overview and MVP Architecture
An implementation plan with an MVP-first approach, using a free database solution.
```mermaid
graph TD
Client[MCP Client] <-->|MCP Protocol| Transport[Transport Layer]
Transport -->|STDIO| LocalTransport[STDIO Transport]
Transport <--> Core[Core Protocol Layer]
Core <--> ToolsLayer[Tools Layer]
Core <--> ResourcesLayer[Resources Layer]
ToolsLayer --> PRDGenerator[PRD Generator]
ToolsLayer --> Validator[PRD Validator]
ResourcesLayer --> Templates[PRD Templates]
ToolsLayer <--> Storage[Storage Layer]
ResourcesLayer <--> Storage
Storage <--> Database[(SQLite DB)]
subgraph "MVP Scope"
LocalTransport
Core
ToolsLayer
ResourcesLayer
PRDGenerator
Validator
Templates
Storage
Database
end
subgraph "Future Expansion"
SSETransport[SSE Transport]
ReqExtractor[Requirements Extractor]
StoryGen[User Story Generator]
CompAnalysis[Competitive Analysis]
Components[Component Library]
Guides[Best Practices]
Examples[Examples Repository]
Security[Security Layer]
Integration[Integration Layer]
end
Transport -.->|Future| SSETransport
ToolsLayer -.-> ReqExtractor
ToolsLayer -.-> StoryGen
ToolsLayer -.-> CompAnalysis
ResourcesLayer -.-> Components
ResourcesLayer -.-> Guides
ResourcesLayer -.-> Examples
Core -.-> Security
Core -.-> Integration
```
## 2. MVP Scope Definition
The MVP will focus on the core functionality needed to demonstrate the value of the PRD Creator:
### Included in MVP:
1. **Core MCP Protocol Implementation**
- STDIO transport only
- Basic request/response handling
- Error management
2. **Essential Tools**
- PRD Generator (template-based)
- PRD Validator (basic validation rules)
3. **Core Resources**
- PRD Templates (3-5 starter templates)
- URI-based resource access
4. **Lightweight Storage**
- SQLite database for persistence
- Basic caching
### Post-MVP Expansion:
1. Additional tools (Requirements Extractor, User Story Generator, etc.)
2. More resources (Component Library, Best Practices, Examples)
3. Advanced features (SSE transport, security, external integrations)
## 3. Technology Stack Selection
### Core Technologies (MVP)
- **Runtime**: Node.js (v16+)
- **Language**: TypeScript 4.5+
- **Protocol**: MCP SDK (@modelcontextprotocol/sdk)
- **Validation**: Zod
- **Database**: SQLite (free, serverless, easy setup)
- **Logging**: Winston
- **Testing**: Jest
### Database Rationale
SQLite is recommended for the MVP because:
- Zero-configuration setup
- No separate server process required
- Free and open source
- File-based storage (simple backup/restore)
- Sufficient performance for initial template and resource storage
- Easy migration path to PostgreSQL if needed later
## 4. Implementation Plan
### Phase 1: Project Setup and Core Protocol (1-2 weeks)
#### 1.1 Project Initialization
```bash
# Create project directory
mkdir prd-creator-mcp
cd prd-creator-mcp
# Initialize Node.js project with TypeScript
npm init -y
npm install typescript @types/node ts-node tsconfig-paths --save-dev
npx tsc --init
# Install core dependencies
npm install @modelcontextprotocol/sdk zod winston sqlite3 better-sqlite3
# Development dependencies
npm install jest ts-jest @types/jest eslint prettier --save-dev
```
#### 1.2 Project Structure
```
prd-creator-mcp/
├── src/
│ ├── config/ # Configuration management
│ ├── core/ # Core MCP implementation
│ ├── storage/ # Database and caching
│ ├── tools/ # Tool implementations
│ ├── resources/ # Resource implementations
│ ├── utils/ # Utility functions
│ ├── templates/ # Initial PRD templates
│ └── index.ts # Main entry point
├── tests/ # Test files
├── tsconfig.json # TypeScript configuration
├── package.json # Project metadata
└── README.md # Documentation
```
#### 1.3 Core MCP Server Implementation
```typescript
// src/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio';
import { configureLogging } from './config/logging';
import { registerTools } from './tools';
import { registerResources } from './resources';
import { initializeStorage } from './storage';
async function main() {
// Configure logging
const logger = configureLogging();
// Initialize storage
await initializeStorage();
// Create MCP server
const server = new McpServer({
name: 'PRD Creator',
version: '0.1.0',
});
// Register tools and resources
registerTools(server);
registerResources(server);
// Handle errors
server.onError = (error) => {
logger.error('Server error:', error);
};
// Connect using STDIO transport
const transport = new StdioServerTransport();
logger.info('Starting PRD Creator MCP Server...');
await server.connect(transport);
logger.info('PRD Creator MCP Server running with STDIO transport');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
```
### Phase 2: Storage Layer Implementation (1 week)
#### 2.1 SQLite Database Setup
```typescript
// src/storage/db.ts
import BetterSqlite3 from 'better-sqlite3';
import { join } from 'path';
import { logger } from '../config/logging';
let db: BetterSqlite3.Database;
export async function initializeDatabase() {
const dbPath = join(process.cwd(), 'data', 'prd-creator.db');
logger.info(`Initializing SQLite database at ${dbPath}`);
// Ensure data directory exists
const fs = await import('fs/promises');
await fs.mkdir(join(process.cwd(), 'data'), { recursive: true });
// Create/open database
db = new BetterSqlite3(dbPath);
// Create tables if they don't exist
db.exec(`
CREATE TABLE IF NOT EXISTS templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
content TEXT NOT NULL,
tags TEXT,
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS template_versions (
id TEXT PRIMARY KEY,
template_id TEXT NOT NULL,
version INTEGER NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (template_id) REFERENCES templates(id)
);
-- Add indexes
CREATE INDEX IF NOT EXISTS idx_templates_name ON templates(name);
CREATE INDEX IF NOT EXISTS idx_template_versions_template_id ON template_versions(template_id);
`);
return db;
}
export function getDatabase(): BetterSqlite3.Database {
if (!db) {
throw new Error('Database not initialized. Call initializeDatabase() first.');
}
return db;
}
```
#### 2.2 Template Storage Implementation
```typescript
// src/storage/templates.ts
import { getDatabase } from './db';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '../config/logging';
export interface Template {
id: string;
name: string;
description?: string;
content: string;
tags?: string[];
version: number;
createdAt: Date;
updatedAt: Date;
}
export async function getTemplate(nameOrId: string): Promise<Template> {
const db = getDatabase();
// Try to find by ID first, then by name
let template = db.prepare(`
SELECT * FROM templates
WHERE id = ? OR name = ?
LIMIT 1
`).get(nameOrId, nameOrId);
if (!template) {
throw new Error(`Template not found: ${nameOrId}`);
}
// Parse tags if they exist
const tags = template.tags ? JSON.parse(template.tags) : [];
return {
id: template.id,
name: template.name,
description: template.description,
content: template.content,
tags,
version: template.version,
createdAt: new Date(template.created_at),
updatedAt: new Date(template.updated_at)
};
}
export async function saveTemplate(template: Omit<Template, 'id' | 'createdAt' | 'updatedAt' | 'version'>): Promise<Template> {
const db = getDatabase();
const id = uuidv4();
const now = new Date().toISOString();
const version = 1;
// Stringify tags if they exist
const tags = template.tags ? JSON.stringify(template.tags) : null;
db.prepare(`
INSERT INTO templates (id, name, description, content, tags, version, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run(id, template.name, template.description, template.content, tags, version, now, now);
logger.info(`Created new template: ${template.name} (${id})`);
return {
...template,
id,
version,
createdAt: new Date(now),
updatedAt: new Date(now)
};
}
export async function updateTemplate(id: string, updates: Partial<Omit<Template, 'id' | 'createdAt' | 'updatedAt'>>): Promise<Template> {
const db = getDatabase();
// Get current template
const current = await getTemplate(id);
// Start a transaction
const transaction = db.transaction(() => {
// Update the template
const tags = updates.tags ? JSON.stringify(updates.tags) : current.tags ? JSON.stringify(current.tags) : null;
const now = new Date().toISOString();
const newVersion = current.version + 1;
// Store previous version
db.prepare(`
INSERT INTO template_versions (id, template_id, version, content, created_at)
VALUES (?, ?, ?, ?, ?)
`).run(uuidv4(), current.id, current.version, current.content, now);
// Update the template
db.prepare(`
UPDATE templates
SET name = ?, description = ?, content = ?, tags = ?, version = ?, updated_at = ?
WHERE id = ?
`).run(
updates.name || current.name,
updates.description || current.description,
updates.content || current.content,
tags,
newVersion,
now,
id
);
return {
...current,
...updates,
version: newVersion,
updatedAt: new Date(now)
};
});
const updated = transaction();
logger.info(`Updated template: ${updated.name} (${id}) to version ${updated.version}`);
return updated;
}
export async function listTemplates(): Promise<Omit<Template, 'content'>[]> {
const db = getDatabase();
const templates = db.prepare(`
SELECT id, name, description, tags, version, created_at, updated_at
FROM templates
ORDER BY name
`).all();
return templates.map(t => ({
id: t.id,
name: t.name,
description: t.description,
tags: t.tags ? JSON.parse(t.tags) : [],
version: t.version,
createdAt: new Date(t.created_at),
updatedAt: new Date(t.updated_at)
}));
}
// Initialize with default templates
export async function initializeDefaultTemplates() {
logger.info('Initializing default templates');
// Check if we already have templates
const db = getDatabase();
const count = db.prepare('SELECT COUNT(*) as count FROM templates').get().count;
if (count > 0) {
logger.info(`Found ${count} existing templates, skipping initialization`);
return;
}
// Add default templates
const fs = await import('fs/promises');
const path = await import('path');
const templatesDir = path.join(process.cwd(), 'src', 'templates');
const files = await fs.readdir(templatesDir);
for (const file of files) {
if (file.endsWith('.md')) {
const content = await fs.readFile(path.join(templatesDir, file), 'utf-8');
const name = file.replace('.md', '');
await saveTemplate({
name,
description: `Default template: ${name}`,
content,
tags: ['default']
});
logger.info(`Added default template: ${name}`);
}
}
}
```
### Phase 3: Tool Implementation (2 weeks)
#### 3.1 PRD Generator Tool
```typescript
// src/tools/prd-generator.ts
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server';
import { logger } from '../config/logging';
import { getTemplate } from '../storage/templates';
// Input schema
export const generatePrdSchema = z.object({
productDescription: z.string().min(1, "Product description is required"),
targetAudience: z.string().min(1, "Target audience is required"),
coreFeatures: z.array(z.string()).min(1, "At least one core feature is required"),
constraints: z.array(z.string()).optional(),
templateName: z.string().optional(),
});
// Utility function for PRD generation
async function generatePRD(
productDescription: string,
targetAudience: string,
coreFeatures: string[],
constraints?: string[],
templateName: string = 'standard'
): Promise<string> {
logger.info(`Generating PRD using template: ${templateName}`);
// Get the template
const template = await getTemplate(templateName);
// Simple template variable replacement
let content = template.content;
content = content.replace(/\{\{PRODUCT_DESCRIPTION\}\}/g, productDescription);
content = content.replace(/\{\{TARGET_AUDIENCE\}\}/g, targetAudience);
// Replace features list
const featuresContent = coreFeatures.map(feature => `- ${feature}`).join('\n');
content = content.replace(/\{\{CORE_FEATURES\}\}/g, featuresContent);
// Replace constraints if provided
if (constraints && constraints.length > 0) {
const constraintsContent = constraints.map(constraint => `- ${constraint}`).join('\n');
content = content.replace(/\{\{CONSTRAINTS\}\}/g, constraintsContent);
} else {
content = content.replace(/\{\{CONSTRAINTS\}\}/g, 'No specific constraints identified.');
}
// Replace date
content = content.replace(/\{\{DATE\}\}/g, new Date().toLocaleDateString());
return content;
}
// Register the tool with the server
export function registerPrdGeneratorTool(server: McpServer) {
server.tool(
'generate_prd',
generatePrdSchema,
async (params) => {
try {
const prd = await generatePRD(
params.productDescription,
params.targetAudience,
params.coreFeatures,
params.constraints,
params.templateName
);
return {
content: [{ type: 'text', text: prd }],
};
} catch (error) {
logger.error(`Error generating PRD: ${error.message}`, { error });
return {
content: [{ type: 'text', text: `Error generating PRD: ${error.message}` }],
isError: true,
};
}
}
);
logger.info('Registered PRD Generator tool');
}
```
#### 3.2 PRD Validator Tool
```typescript
// src/tools/prd-validator.ts
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server';
import { logger } from '../config/logging';
// Input schema
export const validatePrdSchema = z.object({
prdContent: z.string().min(1, "PRD content is required"),
validationRules: z.array(z.string()).optional(),
});
// Validation rule types
interface ValidationRule {
id: string;
name: string;
description: string;
validate: (content: string) => ValidationResult;
}
interface ValidationResult {
passed: boolean;
message: string;
details?: string;
}
// Default validation rules
const defaultRules: ValidationRule[] = [
{
id: 'has-introduction',
name: 'Has Introduction',
description: 'PRD must have an introduction section',
validate: (content) => {
const hasIntro = /^#+\s*introduction/mi.test(content);
return {
passed: hasIntro,
message: hasIntro ? 'Introduction section found' : 'Missing introduction section',
};
}
},
{
id: 'has-target-users',
name: 'Has Target Users',
description: 'PRD must define target users or audience',
validate: (content) => {
const hasTargetUsers = /^#+\s*(target\s*users|audience|users)/mi.test(content);
return {
passed: hasTargetUsers,
message: hasTargetUsers ? 'Target users section found' : 'Missing target users or audience section',
};
}
},
{
id: 'has-features',
name: 'Has Features',
description: 'PRD must describe features or requirements',
validate: (content) => {
const hasFeatures = /^#+\s*(features|requirements)/mi.test(content);
return {
passed: hasFeatures,
message: hasFeatures ? 'Features or requirements section found' : 'Missing features or requirements section',
};
}
},
{
id: 'has-acceptance-criteria',
name: 'Has Acceptance Criteria',
description: 'Features should have acceptance criteria',
validate: (content) => {
const hasAcceptanceCriteria = /acceptance\s*criteria/mi.test(content);
return {
passed: hasAcceptanceCriteria,
message: hasAcceptanceCriteria ? 'Acceptance criteria found' : 'No acceptance criteria found in document',
};
}
},
{
id: 'minimum-length',
name: 'Minimum Length',
description: 'PRD should have sufficient detail (at least 1000 characters)',
validate: (content) => {
const passed = content.length >= 1000;
return {
passed,
message: passed ? 'PRD has sufficient length' : 'PRD is too short (less than 1000 characters)',
details: `Current length: ${content.length} characters`,
};
}
}
];
// Get rules by ID
function getRules(ruleIds?: string[]): ValidationRule[] {
if (!ruleIds || ruleIds.length === 0) {
return defaultRules;
}
return defaultRules.filter(rule => ruleIds.includes(rule.id));
}
// Validate PRD against rules
async function validatePRD(content: string, ruleIds?: string[]): Promise<{
results: ValidationResult[];
summary: {
total: number;
passed: number;
failed: number;
score: number;
}
}> {
logger.info('Validating PRD content');
const rules = getRules(ruleIds);
const results = rules.map(rule => {
const result = rule.validate(content);
return {
rule: rule.name,
...result
};
});
const passed = results.filter(r => r.passed).length;
const total = results.length;
return {
results,
summary: {
total,
passed,
failed: total - passed,
score: (passed / total) * 100
}
};
}
// Register the tool with the server
export function registerPrdValidatorTool(server: McpServer) {
server.tool(
'validate_prd',
validatePrdSchema,
async (params) => {
try {
const validation = await validatePRD(
params.prdContent,
params.validationRules
);
return {
content: [{ type: 'text', text: JSON.stringify(validation, null, 2) }],
};
} catch (error) {
logger.error(`Error validating PRD: ${error.message}`, { error });
return {
content: [{ type: 'text', text: `Error validating PRD: ${error.message}` }],
isError: true,
};
}
}
);
logger.info('Registered PRD Validator tool');
}
```
### Phase 4: Resource Implementation (1 week)
```typescript
// src/resources/templates.ts
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server';
import { logger } from '../config/logging';
import { getTemplate } from '../storage/templates';
// Simple cache implementation
const templateCache = new Map<string, { content: string, timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
// Register the PRD Templates resource
export function registerTemplateResources(server: McpServer) {
// Define the resource
server.resource(
'prd-templates',
new ResourceTemplate('prd://templates/{templateName}'),
async (uri) => {
// Extract the template name from the URI
const templateName = uri.pathname.split('/').pop();
if (!templateName) {
return {
contents: [],
isError: true,
error: 'Template name not provided in URI',
};
}
try {
// Check cache first
const now = Date.now();
const cached = templateCache.get(templateName);
if (cached && (now - cached.timestamp < CACHE_TTL)) {
logger.info(`Serving cached template: ${templateName}`);
return {
contents: [{ uri: uri.href, text: cached.content }],
};
}
// Get the template from storage
const template = await getTemplate(templateName);
// Cache the template
templateCache.set(templateName, {
content: template.content,
timestamp: now
});
logger.info(`Retrieved template: ${templateName}`);
// Return the template
return {
contents: [{ uri: uri.href, text: template.content }],
};
} catch (error) {
logger.error(`Template not found: ${templateName}`, { error });
return {
contents: [],
isError: true,
error: `Template not found: ${templateName}`,
};
}
}
);
logger.info('Registered PRD Templates resource');
}
```
### Phase 5: Default Templates and Initialization (1 week)
Create a few basic PRD templates that will be included with the MVP:
#### 5.1 Standard PRD Template
```markdown
# {{PRODUCT_NAME}} - Product Requirements Document
## Introduction
### Product Overview
{{PRODUCT_DESCRIPTION}}
### Target Audience
{{TARGET_AUDIENCE}}
## Core Features
{{CORE_FEATURES}}
## Constraints and Limitations
{{CONSTRAINTS}}
## User Stories
*To be added by the product team*
## Acceptance Criteria
*To be added for each feature*
## Timeline
*To be determined*
---
Generated on {{DATE}}
```
#### 5.2 Initialization and Registration
```typescript
// src/tools/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server';
import { registerPrdGeneratorTool } from './prd-generator';
import { registerPrdValidatorTool } from './prd-validator';
export function registerTools(server: McpServer) {
registerPrdGeneratorTool(server);
registerPrdValidatorTool(server);
}
// src/resources/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server';
import { registerTemplateResources } from './templates';
export function registerResources(server: McpServer) {
registerTemplateResources(server);
}
// src/storage/index.ts
import { initializeDatabase } from './db';
import { initializeDefaultTemplates } from './templates';
export async function initializeStorage() {
await initializeDatabase();
await initializeDefaultTemplates();
}
```
## 5. Testing and Documentation
### 5.1 Unit Testing Setup
```typescript
// tests/tools/prd-generator.test.ts
import { generatePRD } from '../../src/tools/prd-generator';
import { getTemplate } from '../../src/storage/templates';
// Mock the template storage
jest.mock('../../src/storage/templates', () => ({
getTemplate: jest.fn(),
}));
describe('PRD Generator', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should generate PRD with template replacement', async () => {
// Mock the template
(getTemplate as jest.Mock).mockResolvedValue({
content: '# {{PRODUCT_NAME}}\n\n{{PRODUCT_DESCRIPTION}}\n\n## Features\n\n{{CORE_FEATURES}}\n\n## Constraints\n\n{{CONSTRAINTS}}',
});
const result = await generatePRD(
'A new product',
'Software developers',
['Feature 1', 'Feature 2'],
['Constraint 1']
);
expect(result).toContain('A new product');
expect(result).toContain('- Feature 1');
expect(result).toContain('- Feature 2');
expect(result).toContain('- Constraint 1');
});
});
```
### 5.2 Documentation
Create a detailed README.md file with:
1. Project overview
2. Installation instructions
3. Usage examples
4. Tool and resource documentation
5. Database schema
6. Expansion guidelines
## 6. Future Expansion Roadmap
After completing the MVP, the PRD Creator MCP Server can be expanded with:
1. **Additional Tools**
- Requirements Extractor
- User Story Generator
- Competitive Analysis
2. **Additional Resources**
- Component Library
- Best Practices Guide
- Examples Repository
3. **Advanced Features**
- SSE Transport for networked access
- User authentication and access control
- External system integration (JIRA, GitHub, etc.)
- Database migration to PostgreSQL for multi-user environments
4. **Deployment Options**
- Docker containerization
- Cloud deployment configurations
- CI/CD pipeline
## 7. Implementation Timeline
The MVP can be implemented in approximately 6-8 weeks:
```mermaid
gantt
title PRD Creator MCP Server - MVP Implementation
dateFormat YYYY-MM-DD
section Phase 1
Project Setup :a1, 2023-06-01, 3d
Core MCP Implementation :a2, after a1, 7d
section Phase 2
Database Setup :b1, after a2, 5d
Template Storage :b2, after b1, 5d
section Phase 3
PRD Generator Tool :c1, after b2, 7d
PRD Validator Tool :c2, after c1, 7d
section Phase 4
Template Resources :d1, after c2, 5d
section Phase 5
Default Templates :e1, after d1, 3d
Integration Testing :e2, after e1, 5d
Documentation :e3, after e2, 3d
```
## 8. Conclusion
This implementation plan outlines a practical approach to building the PRD Creator MCP Server with a focus on an MVP first. The plan:
1. Uses free, embedded SQLite for the database to avoid costs and complexity
2. Focuses on core functionality first (generator and validator)
3. Provides a clear path for future expansion
4. Includes enough technical detail for an AI to implement
5. Follows best practices for Node.js/TypeScript development