This is page 1 of 4. Use http://codebase.md/crazyrabbitltc/mcp-vibecoder?page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── bun.lockb
├── debug.js
├── docs
│ └── mcp-sdk-improvements.md
├── documents
│ ├── test-feature
│ │ └── prd.md
│ └── test-feature-custom.md
├── instructions
│ ├── checklist.md
│ ├── Impl.md
│ ├── mcp_sdk_improvements.md
│ ├── mcp_server_improvements.md
│ ├── plan.md
│ ├── PRD.md
│ └── solidity_tic_tac_toe_project.md
├── MCP-docs.txt
├── MCP-Typescript-readme.txt
├── package-lock.json
├── package.json
├── README.md
├── repomix-output.txt
├── src
│ ├── clarification.ts
│ ├── document-storage.ts
│ ├── documentation.ts
│ ├── errors.ts
│ ├── index-updated.ts
│ ├── index.ts
│ ├── mcp-server.ts
│ ├── phases.ts
│ ├── registry.ts
│ ├── resource-handlers.ts
│ ├── schemas.ts
│ ├── storage.ts
│ ├── templates
│ │ ├── impl-template.md
│ │ └── prd-template.md
│ ├── test-mcp-improved.js
│ ├── tool-handlers.ts
│ ├── types.ts
│ ├── utils.ts
│ └── validators.ts
├── test-document-storage.js
├── test-document-tools.js
├── test-mcp.js
├── tsconfig.build.json
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
node_modules/
build/
*.log
.env*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Vibe-Coder MCP Server
A Model Context Protocol server that implements a structured development workflow for LLM-based coding.
## Overview
This MCP server helps LLMs build features in an organized, clean, and safe manner by providing:
- A structured feature clarification process with guided questions
- PRD and implementation plan generation
- Phased development with task tracking
- Progress tracking and status reporting
- Document storage and retrieval capabilities
## Features
### Resources
- Feature details, PRDs, and implementation plans
- Progress reports and status tracking
- Phase and task details
### Tools
- `start_feature_clarification` - Begin the feature clarification process
- `provide_clarification` - Answer clarification questions about a feature
- `generate_prd` - Generate a Product Requirements Document and implementation plan
- `create_phase` - Create a development phase for a feature
- `add_task` - Add tasks to a development phase
- `update_phase_status` - Update the status of a phase
- `update_task_status` - Update the completion status of a task
- `get_next_phase_action` - Get guidance on what to do next
- `get_document_path` - Get the path of a generated document
- `save_document` - Save a document to a specific location
### Prompts
- `feature-planning` - A prompt template for planning feature development
## Document Storage
The server includes a hybrid document storage system that:
1. Automatically saves generated documents (PRDs, implementation plans) to files
2. Maintains an in-memory copy for quick access
3. Allows clients to retrieve document paths and save to custom locations
### Default Storage Location
Documents are stored in the `documents/{featureId}/` directory by default, with filenames based on document type:
- `documents/{featureId}/prd.md` - Product Requirements Document
- `documents/{featureId}/implementation-plan.md` - Implementation Plan
### Custom Storage
You can use the `save_document` tool to save documents to custom locations:
```json
{
"featureId": "feature-123",
"documentType": "prd",
"filePath": "/custom/path/feature-123-prd.md"
}
```
### Path Retrieval
To get the path of a document, use the `get_document_path` tool:
```json
{
"featureId": "feature-123",
"documentType": "prd"
}
```
This returns both the path and whether the document has been saved to disk.
## Development
Install dependencies:
```bash
npm install
```
Build the server:
```bash
npm run build
```
For development with auto-rebuild:
```bash
npm run watch
```
## Installation
To use with compatible MCP clients:
On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"vibe-coder-mcp": {
"command": "/path/to/vibe-coder-mcp/build/mcp-server.js"
}
}
}
```
### Debugging
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
```bash
npm run inspector
```
The Inspector will provide a URL to access debugging tools in your browser.
## Implementation Notes
This server is implemented using the high-level `McpServer` class from the Model Context Protocol TypeScript SDK, which simplifies the process of creating MCP servers by providing a clean API for defining resources, tools, and prompts.
```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// Create an MCP server
const server = new McpServer({
name: "Vibe-Coder",
version: "0.3.0"
});
// Add a resource
server.resource(
"features-list",
"features://list",
async (uri) => ({ /* ... */ })
);
// Add a tool
server.tool(
"start_feature_clarification",
{ /* parameters schema */ },
async (params) => ({ /* ... */ })
);
// Add a prompt
server.prompt(
"feature-planning",
{ /* parameters schema */ },
(params) => ({ /* ... */ })
);
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
```
## Workflow
The Vibe-Coder MCP server is designed to guide the development process through the following steps:
1. **Feature Clarification**: Start by gathering requirements and understanding the feature's purpose, target users, and constraints
2. **Documentation**: Generate a PRD and implementation plan based on the clarified requirements
3. **Phased Development**: Break down the implementation into logical phases with clear tasks
4. **Progress Tracking**: Monitor the completion of tasks and phases to guide development
5. **Completion**: Verify that all requirements have been implemented and the feature is ready for use
```
--------------------------------------------------------------------------------
/documents/test-feature-custom.md:
--------------------------------------------------------------------------------
```markdown
# Test Feature - PRD
This is a test PRD document.
```
--------------------------------------------------------------------------------
/documents/test-feature/prd.md:
--------------------------------------------------------------------------------
```markdown
# Test Feature - PRD
This is a test PRD document.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/src/templates/impl-template.md:
--------------------------------------------------------------------------------
```markdown
# {{featureName}} Implementation Plan
## Overview
{{featureDescription}}
## Implementation Steps
{{#each phases}}
### Phase {{phaseNumber}}: {{phaseName}}
#### Objectives
{{objectives}}
#### Tasks
{{#each tasks}}
- [ ] {{description}}
{{/each}}
#### Code Style & Practices
{{codeStyle}}
{{/each}}
## File Structure
```
{{fileStructure}}
```
## Implementation Timeline
{{#each phases}}
{{phaseNumber}}. **{{phaseName}}**: {{timeEstimate}}
{{/each}}
## Next Steps
1. {{nextStep1}}
2. {{nextStep2}}
3. {{nextStep3}}
4. {{nextStep4}}
5. {{nextStep5}}
```
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/mcp-server.ts",
"src/types.ts",
"src/storage.ts",
"src/utils.ts",
"src/clarification.ts",
"src/documentation.ts",
"src/document-storage.ts"
],
"exclude": [
"node_modules",
"src/registry.ts",
"src/resource-handlers.ts",
"src/tool-handlers.ts",
"src/index.ts",
"src/index-updated.ts"
]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "vibe-coder-mcp",
"version": "0.3.0",
"description": "A custom MCP for vibecoding",
"private": true,
"type": "module",
"bin": {
"vibe-coder-mcp": "./build/mcp-server.js"
},
"files": [
"build"
],
"scripts": {
"build": "tsc --project tsconfig.build.json && node -e \"require('fs').chmodSync('build/mcp-server.js', '755')\"",
"watch": "tsc --project tsconfig.build.json --watch",
"inspector": "npx @modelcontextprotocol/inspector build/mcp-server.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.7.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
}
}
```
--------------------------------------------------------------------------------
/debug.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
// This file is just for inspecting the features state
// Since we can't access the runtime state directly,
// we'll examine how the clarification functions work
import { getNextClarificationQuestion, DEFAULT_CLARIFICATION_QUESTIONS } from './build/clarification.js';
console.log('Default questions:', DEFAULT_CLARIFICATION_QUESTIONS);
// Test the function with a mock feature
const mockFeature = {
id: 'test',
clarificationResponses: []
};
console.log('Initial question:', getNextClarificationQuestion(mockFeature));
// Add a response and check next question
mockFeature.clarificationResponses.push({
question: DEFAULT_CLARIFICATION_QUESTIONS[0],
answer: 'Test answer',
timestamp: new Date()
});
console.log('After 1 response, next question:', getNextClarificationQuestion(mockFeature));
// Add another response
mockFeature.clarificationResponses.push({
question: DEFAULT_CLARIFICATION_QUESTIONS[1],
answer: 'Test answer 2',
timestamp: new Date()
});
console.log('After 2 responses, next question:', getNextClarificationQuestion(mockFeature));
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Core data types for the Vibe-Coder MCP Server
*/
/**
* Represents a feature being developed
*/
export type Feature = {
id: string;
name: string;
description: string;
clarificationResponses: ClarificationResponse[];
prd?: string;
prdDoc?: string;
implementationPlan?: string;
implDoc?: string;
phases: Phase[];
createdAt: Date;
updatedAt: Date;
};
/**
* Represents a question and answer pair from the clarification process
*/
export type ClarificationResponse = {
question: string;
answer: string;
timestamp: Date;
};
/**
* Represents a development phase within a feature
*/
export type Phase = {
id: string;
name: string;
description: string;
tasks: Task[];
status: PhaseStatus;
createdAt: Date;
updatedAt: Date;
};
/**
* Possible statuses for a development phase
*/
export type PhaseStatus = "pending" | "in_progress" | "completed" | "reviewed";
/**
* Represents a task within a development phase
*/
export type Task = {
id: string;
description: string;
completed: boolean;
createdAt: Date;
updatedAt: Date;
};
/**
* Storage interface for features
*/
export interface FeatureStorage {
[id: string]: Feature;
}
```
--------------------------------------------------------------------------------
/src/templates/prd-template.md:
--------------------------------------------------------------------------------
```markdown
# {{featureName}} PRD
## 1. Introduction
{{featureDescription}}
## 2. Feature Objectives
{{objectives}}
## 3. Scope and Requirements
### 3.1 Feature Request Clarification
{{requirements}}
### 3.2 Technical Specifications
{{technicalSpecs}}
## 4. High-Level Implementation Overview
### 4.1 Development Workflow and Branching Strategy
- **Feature Branch:**
- Begin implementation on a dedicated branch named `feature/{{featureNameSlug}}`.
- **Phase Branches:**
- For each phase, create a branch off the feature branch named `phase/{{featureNameSlug}}/{{phaseNumber}}-{{phaseName}}`.
### 4.2 Implementation Phases
{{implementationPhases}}
## 5. Feedback and Iteration Process
- **User Feedback Integration:**
- Regularly incorporate user feedback to refine feature objectives and requirements.
- **Iterative Updates:**
- Update both the PRD and the implementation plan as new details emerge during the development process.
- **Review and Approval:**
- Ensure that each phase is reviewed and approved before moving to subsequent phases.
## 6. File Naming Conventions
- **PRD File:** `{{stepNumber}}_{{featureName}}_PRD.md`
- **Implementation Plan File:** `{{stepNumber}}_{{featureName}}_Implementation.md`
```
--------------------------------------------------------------------------------
/src/storage.ts:
--------------------------------------------------------------------------------
```typescript
/**
* In-memory storage for features and related data.
*/
import { Feature, FeatureStorage } from './types.js';
/**
* In-memory storage for features
*/
export const features: FeatureStorage = {};
/**
* Add a new feature to storage
* @param feature The feature to store
* @returns The stored feature
*/
export function storeFeature(feature: Feature): Feature {
features[feature.id] = feature;
return feature;
}
/**
* Retrieve a feature by ID
* @param id The feature ID
* @returns The feature or undefined if not found
*/
export function getFeature(id: string): Feature | undefined {
return features[id];
}
/**
* Update an existing feature
* @param id The feature ID
* @param updatedFields Fields to update on the feature
* @returns The updated feature or undefined if not found
*/
export function updateFeature(
id: string,
updatedFields: Partial<Omit<Feature, 'id' | 'createdAt'>>
): Feature | undefined {
const feature = features[id];
if (!feature) {
return undefined;
}
// Update the feature with new fields
const updatedFeature = {
...feature,
...updatedFields,
updatedAt: new Date()
};
features[id] = updatedFeature;
return updatedFeature;
}
/**
* List all features
* @returns Array of all features
*/
export function listFeatures(): Feature[] {
return Object.values(features);
}
/**
* Delete a feature by ID
* @param id The feature ID
* @returns True if deleted, false if not found
*/
export function deleteFeature(id: string): boolean {
if (features[id]) {
delete features[id];
return true;
}
return false;
}
```
--------------------------------------------------------------------------------
/instructions/checklist.md:
--------------------------------------------------------------------------------
```markdown
# Vibe-Coder MCP Server Implementation Checklist
## Step 1: Server Configuration and Core Structure
- [x] Update server metadata and name
- [x] Define core data types (Feature, ClarificationResponse, Phase, Task)
- [x] Create in-memory storage for features and projects
- [x] Set up directory structure and file organization
- [x] Implement basic utilities (generateId, etc.)
## Step 2: Feature Clarification Implementation
- [x] Create tool for initiating feature clarification
- [x] Implement tool for receiving clarification responses
- [x] Create resource to retrieve clarification status
- [x] Add support for structured questioning workflow
## Step 3: PRD and Implementation Plan Generation
- [x] Create tool for generating PRD document
- [x] Implement tool for creating implementation plan
- [x] Add helper functions for extracting requirements from clarifications
- [x] Implement markdown formatting for documentation generation
- [x] Create template structure for PRDs and implementation plans
## Step 4: Phase Management Implementation
- [x] Implement tool for creating phases
- [x] Add tool for updating phase status
- [x] Create tool for managing tasks within phases
- [x] Implement phase workflow progression logic
## Step 5: Resource Implementation
- [x] Create resources for listing all features
- [x] Add resources for accessing feature details
- [x] Implement resources for retrieving PRDs
- [x] Create resources for retrieving implementation plans
- [x] Add support for phase and task status viewing
## Step 6: Tool Implementation
- [x] Define all tool schemas and validations
- [x] Implement error handling for tools
- [x] Ensure proper argument validation
- [x] Add support for tool discoverability
- [x] Test tool invocations with example data
## Step 7: Prompt Implementation
- [ ] Create clarify_feature prompt
- [ ] Implement generate_prd_template prompt
- [ ] Add phase_implementation_guide prompt
- [ ] Ensure prompts are well-structured for LLM use
## Testing and Documentation
- [ ] Test each tool individually
- [ ] Test complete workflows end-to-end
- [ ] Create example usage documentation
- [ ] Add README with setup and usage instructions
```
--------------------------------------------------------------------------------
/instructions/mcp_sdk_improvements.md:
--------------------------------------------------------------------------------
```markdown
# Implementation Plan for MCP SDK Improvements
## 1. Replace Switch Statement with Tool Handler Map
### Tasks:
- [x] Create a tool handler type definition in `types.ts`
- [x] Create a tools registry object that maps tool names to handler functions
- [x] Refactor the `CallToolRequestSchema` handler to use this registry
- [x] Add validation and error handling for unknown tools
- [x] Update tool registration to automatically populate the registry
- [ ] Test all tools to ensure functionality remains the same
## 2. Implement Robust URI Parsing
### Tasks:
- [x] Create a `ResourceRegistry` class that handles URI templates and matching
- [x] Update resource definitions to use proper URI templates
- [x] Implement pattern matching for incoming resource URIs
- [x] Refactor `ReadResourceRequestSchema` handler to use the registry
- [x] Add validation and error handling for malformed URIs
- [ ] Test with various URI patterns to ensure correct matching
## 3. Improve Error Handling Consistency
### Tasks:
- [x] Define standard error response types following JSON-RPC 2.0 spec
- [x] Create utility functions for generating consistent error responses
- [x] Update all error returns to use these utility functions
- [x] Ensure error codes are appropriate and consistent
- [x] Add more detailed error messages with context
- [x] Improve validation error reporting
- [ ] Test error scenarios to verify consistent responses
## 4. Use More Specific Types and Reduce Type Assertions
### Tasks:
- [x] Create Zod schemas for core data structures (Feature, Phase, Task)
- [x] Implement branded types for identifiers (FeatureId, PhaseId, TaskId)
- [x] Replace string types with more specific types where appropriate
- [ ] Eliminate `@ts-ignore` comments by fixing underlying type issues
- [x] Add runtime type validation at critical boundaries
- [x] Update function signatures to use the new types
- [ ] Test type safety with various inputs
## Implementation Order and Dependencies
1. **First Phase**: Type improvements (foundation for other changes) ✅
- Implement specific types and Zod schemas ✅
- Remove type assertions where possible ✅
2. **Second Phase**: Error handling improvements ✅
- Create error utilities ✅
- Update error responses to use common format ✅
3. **Third Phase**: Resource URI handling ✅
- Implement resource registry ✅
- Update URI parsing and matching ✅
4. **Fourth Phase**: Tool handler refactoring ✅
- Create tools registry ✅
- Refactor call tool request handling ✅
## Next Steps
1. Testing strategy:
- Create unit tests for each component
- Test edge cases and error scenarios
- Compare outputs before and after changes to ensure consistency
2. Create a PR with the changes
- Update the documentation
- Add examples of using the new APIs
```
--------------------------------------------------------------------------------
/test-document-storage.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
/**
* @file test-document-storage.js
*
* Simple test script for the document storage functionality.
* Run with: node test-document-storage.js
*/
import { documentStorage, DocumentType } from './build/document-storage.js';
import * as fs from 'fs/promises';
import * as path from 'path';
// Test feature ID
const featureId = 'test-feature';
/**
* Check if a file exists
* @param {string} filePath Path to check
* @returns {Promise<boolean>} True if file exists
*/
async function fileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
/**
* Run document storage tests
*/
async function runTests() {
console.log('=== Document Storage Test ===\n');
try {
// Create documents directory if it doesn't exist
const testDirPath = path.join(process.cwd(), 'documents', featureId);
try {
await fs.mkdir(testDirPath, { recursive: true });
} catch (err) {
// Ignore if directory already exists
}
// Test 1: Store a PRD document
console.log('Test 1: Storing PRD document');
const prdContent = `# Test Feature - PRD\n\nThis is a test PRD document.`;
const prdDocument = await documentStorage.storeDocument(featureId, DocumentType.PRD, prdContent);
console.log(`- Document stored in memory: ${Boolean(prdDocument)}`);
console.log(`- Document metadata:`, prdDocument.metadata);
console.log(`- Document saved to disk: ${prdDocument.metadata.isSaved}`);
// Test 2: Retrieve the document
console.log('\nTest 2: Retrieving document');
const retrievedDoc = documentStorage.getDocument(featureId, DocumentType.PRD);
console.log(`- Document retrieved: ${Boolean(retrievedDoc)}`);
console.log(`- Document content preview: ${retrievedDoc?.content.substring(0, 30)}...`);
// Test 3: Save to custom path
console.log('\nTest 3: Saving to custom path');
const customPath = path.join(process.cwd(), 'documents', `${featureId}-custom.md`);
try {
const savedPath = await documentStorage.saveDocumentToCustomPath(
featureId,
DocumentType.PRD,
customPath
);
console.log(`- Document saved to custom path: ${savedPath}`);
// Check if the file was created
const fileExistsResult = await fileExists(customPath);
console.log(`- Custom file exists: ${fileExistsResult}`);
} catch (error) {
console.error(`- Error saving to custom path: ${error.message}`);
}
// Test 4: Path validation
console.log('\nTest 4: Path validation');
try {
// Try to save outside the documents directory (should fail)
const invalidPath = path.join(process.cwd(), '..', 'invalid-path.md');
await documentStorage.saveDocumentToCustomPath(
featureId,
DocumentType.PRD,
invalidPath
);
console.log('- Path validation FAILED: Allowed path outside documents directory');
} catch (error) {
console.log(`- Path validation successful: ${error.message}`);
}
console.log('\n=== Tests completed successfully ===');
} catch (error) {
console.error('\nTest failed with error:', error);
}
}
// Run the tests
runTests().catch(error => {
console.error('Unhandled error:', error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/schemas.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file schemas.ts
* @version 1.0.0
*
* Provides Zod schemas and branded types for the Vibe-Coder MCP Server.
* These schemas enable runtime validation and improve type safety across the codebase.
*/
import { z } from 'zod';
import { PhaseStatus } from './types.js';
// --------- Branded Types for IDs ---------
/**
* Branded type for Feature IDs
*/
export type FeatureId = string & { readonly _brand: unique symbol };
/**
* Branded type for Phase IDs
*/
export type PhaseId = string & { readonly _brand: unique symbol };
/**
* Branded type for Task IDs
*/
export type TaskId = string & { readonly _brand: unique symbol };
/**
* Type guards for branded types
*/
export const isFeatureId = (id: string): id is FeatureId => id.startsWith('feature-');
export const isPhaseId = (id: string): id is PhaseId => id.startsWith('phase-');
export const isTaskId = (id: string): id is TaskId => id.startsWith('task-');
/**
* Brand creator functions
*/
export const createFeatureId = (id: string): FeatureId => {
if (!isFeatureId(id)) throw new Error(`Invalid feature ID format: ${id}`);
return id as FeatureId;
};
export const createPhaseId = (id: string): PhaseId => {
if (!isPhaseId(id)) throw new Error(`Invalid phase ID format: ${id}`);
return id as PhaseId;
};
export const createTaskId = (id: string): TaskId => {
if (!isTaskId(id)) throw new Error(`Invalid task ID format: ${id}`);
return id as TaskId;
};
// --------- Core Zod Schemas ---------
/**
* Schema for ClarificationResponse
*/
export const ClarificationResponseSchema = z.object({
question: z.string().min(1, "Question cannot be empty"),
answer: z.string().min(1, "Answer cannot be empty"),
timestamp: z.date(),
});
/**
* Schema for Task
*/
export const TaskSchema = z.object({
id: z.string().refine(isTaskId, "Invalid task ID format"),
description: z.string().min(3, "Task description must be at least 3 characters"),
completed: z.boolean(),
createdAt: z.date(),
updatedAt: z.date(),
});
/**
* Schema for PhaseStatus
*/
export const PhaseStatusSchema = z.enum(["pending", "in_progress", "completed", "reviewed"]);
/**
* Schema for Phase
*/
export const PhaseSchema = z.object({
id: z.string().refine(isPhaseId, "Invalid phase ID format"),
name: z.string().min(2, "Phase name must be at least 2 characters").max(100, "Phase name must be at most 100 characters"),
description: z.string(),
tasks: z.array(TaskSchema),
status: PhaseStatusSchema,
createdAt: z.date(),
updatedAt: z.date(),
});
/**
* Schema for Feature
*/
export const FeatureSchema = z.object({
id: z.string().refine(isFeatureId, "Invalid feature ID format"),
name: z.string().min(2, "Feature name must be at least 2 characters").max(100, "Feature name must be at most 100 characters"),
description: z.string(),
clarificationResponses: z.array(ClarificationResponseSchema),
prd: z.string().optional(),
prdDoc: z.string().optional(),
implementationPlan: z.string().optional(),
implDoc: z.string().optional(),
phases: z.array(PhaseSchema),
createdAt: z.date(),
updatedAt: z.date(),
});
// --------- Type Inference ---------
/**
* Zod-inferred types to ensure schema and type compatibility
*/
export type ZodFeature = z.infer<typeof FeatureSchema>;
export type ZodPhase = z.infer<typeof PhaseSchema>;
export type ZodTask = z.infer<typeof TaskSchema>;
export type ZodClarificationResponse = z.infer<typeof ClarificationResponseSchema>;
/**
* Validation functions for inputs
*/
export const validateFeature = (feature: unknown): ZodFeature => {
return FeatureSchema.parse(feature);
};
export const validatePhase = (phase: unknown): ZodPhase => {
return PhaseSchema.parse(phase);
};
export const validateTask = (task: unknown): ZodTask => {
return TaskSchema.parse(task);
};
export const validateClarificationResponse = (response: unknown): ZodClarificationResponse => {
return ClarificationResponseSchema.parse(response);
};
```
--------------------------------------------------------------------------------
/src/errors.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file errors.ts
* @version 1.0.0
*
* Provides standardized error handling utilities for the Vibe-Coder MCP Server.
* These utilities ensure consistent error responses following the JSON-RPC 2.0 specification.
*/
import { z } from 'zod';
// --------- Error Codes ---------
/**
* Standard JSON-RPC 2.0 error codes
* https://www.jsonrpc.org/specification#error_object
*/
export enum ErrorCode {
// JSON-RPC 2.0 Standard Error Codes
PARSE_ERROR = -32700,
INVALID_REQUEST = -32600,
METHOD_NOT_FOUND = -32601,
INVALID_PARAMS = -32602,
INTERNAL_ERROR = -32603,
// Custom Error Codes (must be between -32000 and -32099)
VALIDATION_ERROR = -32000,
RESOURCE_NOT_FOUND = -32001,
TOOL_NOT_FOUND = -32002,
PROMPT_NOT_FOUND = -32003,
UNAUTHORIZED = -32004,
FEATURE_NOT_FOUND = -32010,
PHASE_NOT_FOUND = -32011,
TASK_NOT_FOUND = -32012,
INVALID_PHASE_TRANSITION = -32013,
CLARIFICATION_INCOMPLETE = -32014,
}
// --------- Error Responses ---------
/**
* Standard JSON-RPC 2.0 error response structure
*/
export type ErrorResponse = {
error: {
code: ErrorCode;
message: string;
data?: any;
}
};
/**
* Tool error response
*/
export type ToolErrorResponse = {
content: [{
type: "text";
text: string;
}];
isError?: boolean;
};
/**
* Create a standard error response for JSON-RPC 2.0
*/
export const createErrorResponse = (
code: ErrorCode,
message: string,
data?: any
): ErrorResponse => {
return {
error: {
code,
message,
data
}
};
};
/**
* Create a tool error response
*/
export const createToolErrorResponse = (
message: string,
details?: any
): ToolErrorResponse => {
return {
content: [{
type: "text",
text: `Error: ${message}`
}],
isError: true
};
};
/**
* Capture and format Zod validation errors
*/
export const handleZodError = (error: z.ZodError): ErrorResponse => {
const formattedErrors = error.errors.map(err => ({
path: err.path.join('.'),
message: err.message
}));
return createErrorResponse(
ErrorCode.VALIDATION_ERROR,
"Validation error",
formattedErrors
);
};
/**
* Handle feature not found errors
*/
export const featureNotFoundError = (featureId: string): ToolErrorResponse => {
return createToolErrorResponse(`Feature with ID ${featureId} not found`);
};
/**
* Handle phase not found errors
*/
export const phaseNotFoundError = (
phaseId: string,
featureName: string
): ToolErrorResponse => {
return createToolErrorResponse(
`Phase with ID ${phaseId} not found in feature ${featureName}`
);
};
/**
* Handle task not found errors
*/
export const taskNotFoundError = (
taskId: string,
phaseName: string
): ToolErrorResponse => {
return createToolErrorResponse(
`Task with ID ${taskId} not found in phase ${phaseName}`
);
};
/**
* Handle invalid phase transition errors
*/
export const invalidPhaseTransitionError = (
currentStatus: string,
newStatus: string
): ToolErrorResponse => {
return createToolErrorResponse(
`Cannot transition phase from "${currentStatus}" to "${newStatus}"`
);
};
/**
* Handle clarification incomplete errors
*/
export const clarificationIncompleteError = (
status: any
): ToolErrorResponse => {
return createToolErrorResponse(
`Cannot proceed until clarification is complete`,
{ clarificationStatus: status }
);
};
/**
* Try-catch wrapper for Zod validation
*/
export const tryValidate = <T>(
schema: z.ZodType<T>,
data: unknown
): { success: true; data: T } | { success: false; error: ErrorResponse } => {
try {
return { success: true, data: schema.parse(data) };
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, error: handleZodError(error) };
}
return {
success: false,
error: createErrorResponse(
ErrorCode.INTERNAL_ERROR,
error instanceof Error ? error.message : "Unknown error"
)
};
}
};
```
--------------------------------------------------------------------------------
/test-mcp.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
import { spawn } from 'child_process';
import { createInterface } from 'readline';
console.log('Starting Vibe-Coder MCP server...');
// Start the MCP server
const serverProcess = spawn('node', ['./build/index.js']);
// Create readline interface to read server output
const rl = createInterface({
input: serverProcess.stdout,
output: process.stdout,
terminal: false
});
// Set up error handling
serverProcess.stderr.on('data', (data) => {
console.error(`Server stderr: ${data.toString()}`);
});
// Wait for server to start
setTimeout(() => {
console.log('Sending initialize request...');
// First, send the initialize request
const initializeMsg = {
jsonrpc: "2.0",
id: "init1",
method: "initialize",
params: {
client: {
name: "Test Client",
version: "1.0.0"
},
capabilities: {
resources: {},
tools: {},
prompts: {}
}
}
};
serverProcess.stdin.write(JSON.stringify(initializeMsg) + '\n');
// Wait for response and send clarification
let featureId = null;
let questionIndex = 0;
rl.on('line', (line) => {
try {
console.log('Raw server output:', line);
const response = JSON.parse(line);
console.log('Parsed response:', JSON.stringify(response, null, 2));
// After initialization, list available tools
if (response.id === "init1") {
console.log('Initialization successful, listing tools...');
const listToolsMsg = {
jsonrpc: "2.0",
id: "list1",
method: "listTools",
params: {}
};
serverProcess.stdin.write(JSON.stringify(listToolsMsg) + '\n');
}
// After listing tools, call the start_feature_clarification tool
if (response.id === "list1") {
console.log('Tools listed, starting feature clarification...');
const callToolMsg = {
jsonrpc: "2.0",
id: "call1",
method: "callTool",
params: {
name: "start_feature_clarification",
arguments: {
featureName: "Test Automated Feature",
initialDescription: "This is a test automated feature for testing the clarification flow"
}
}
};
serverProcess.stdin.write(JSON.stringify(callToolMsg) + '\n');
}
// Check if this is a response to start_feature_clarification
if (response.id === "call1" && response.result?.content) {
// Extract feature ID from response text
const text = response.result.content[0].text;
const match = text.match(/Feature ID: ([a-z0-9]+)/);
if (match && match[1]) {
featureId = match[1];
console.log(`Extracted feature ID: ${featureId}`);
// Send first clarification
sendClarification(featureId, questionIndex, "This is an answer to question 0");
}
}
// Check if this is a response to any of the clarification messages
if (response.id && response.id.startsWith("clarify") && response.result?.content) {
const text = response.result.content[0].text;
console.log('Clarification response text:', text);
// Check if we need to send the next question
if (text.startsWith("Response recorded.")) {
questionIndex++;
if (questionIndex < 7) { // We have 7 default questions
setTimeout(() => {
sendClarification(featureId, questionIndex, `This is an answer to question ${questionIndex}`);
}, 500);
} else {
console.log('All questions answered, test complete');
serverProcess.kill();
process.exit(0);
}
}
}
} catch (e) {
console.error('Error parsing JSON response:', e);
}
});
function sendClarification(featureId, index, answer) {
const clarifyMsg = {
jsonrpc: "2.0",
id: `clarify${index}`,
method: "callTool",
params: {
name: "provide_clarification",
arguments: {
featureId: featureId,
question: `Question #${index}`,
answer: answer
}
}
};
console.log(`Sending clarification ${index}...`);
serverProcess.stdin.write(JSON.stringify(clarifyMsg) + '\n');
}
}, 1000);
// Handle process exit
process.on('exit', () => {
serverProcess.kill();
});
```
--------------------------------------------------------------------------------
/src/clarification.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Feature clarification module for the Vibe-Coder MCP Server.
* This module handles the iterative questioning to clarify feature requests.
*/
import { Feature, ClarificationResponse } from './types.js';
import { updateFeature, getFeature } from './storage.js';
import { now } from './utils.js';
/**
* Default questions to ask during feature clarification
*/
export const DEFAULT_CLARIFICATION_QUESTIONS = [
"What specific problem does this feature solve?",
"Who are the target users for this feature?",
"What are the key requirements for this feature?",
"What are the technical constraints or considerations?",
"How will we measure the success of this feature?",
"Are there any dependencies on other features or systems?",
"What are the potential risks or challenges in implementing this feature?"
];
/**
* Result when all clarification questions are complete
*/
export interface ClarificationComplete {
done: true;
message: string;
}
/**
* Get the next clarification question for a feature
* @param feature The feature to get the next question for
* @returns The next question to ask or a completion object if all questions have been answered
* @throws Error if the feature is missing the clarificationResponses array
*/
export function getNextClarificationQuestion(feature: Feature): string | ClarificationComplete {
if (!feature.clarificationResponses) {
// Throw a proper error instead of just logging
throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
}
// Check if we've asked all the default questions
if (feature.clarificationResponses.length >= DEFAULT_CLARIFICATION_QUESTIONS.length) {
return {
done: true,
message: "All clarification questions have been answered. You can now generate a PRD for this feature."
};
}
// Get the next question based on the number of responses
return DEFAULT_CLARIFICATION_QUESTIONS[feature.clarificationResponses.length];
}
/**
* Add a clarification response to a feature
* @param featureId The ID of the feature to add the response to
* @param question The question that was asked
* @param answer The user's answer
* @returns The updated feature
* @throws Error if the feature is not found
*/
export function addClarificationResponse(
featureId: string,
question: string,
answer: string
): Feature {
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature with ID ${featureId} not found`);
}
// Create a new response
const newResponse: ClarificationResponse = {
question,
answer,
timestamp: now()
};
// Update the feature with the new response
const updatedFeature = updateFeature(featureId, {
clarificationResponses: [...feature.clarificationResponses, newResponse]
});
if (!updatedFeature) {
throw new Error(`Failed to update feature ${featureId} with new clarification response`);
}
return updatedFeature;
}
/**
* Format clarification responses as text
* @param responses The responses to format
* @returns Formatted text
*/
export function formatClarificationResponses(responses: ClarificationResponse[]): string {
if (!responses || responses.length === 0) {
return "No clarification responses yet.";
}
return responses.map(cr => `Q: ${cr.question}\nA: ${cr.answer}`).join('\n\n');
}
/**
* Check if a feature has completed the clarification process
* @param feature The feature to check
* @returns True if clarification is complete, false otherwise
* @throws Error if the feature is missing the clarificationResponses array
*/
export function isClarificationComplete(feature: Feature): boolean {
if (!feature.clarificationResponses) {
throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
}
return feature.clarificationResponses.length >= DEFAULT_CLARIFICATION_QUESTIONS.length;
}
/**
* Get a summary of the clarification status
* @param feature The feature to get the status for
* @returns A text summary of the clarification status
* @throws Error if the feature is missing the clarificationResponses array
*/
export function getClarificationStatus(feature: Feature): string {
if (!feature.clarificationResponses) {
throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
}
const total = DEFAULT_CLARIFICATION_QUESTIONS.length;
const completed = feature.clarificationResponses.length;
return `Clarification progress: ${completed}/${total} questions answered.`;
}
```
--------------------------------------------------------------------------------
/instructions/mcp_server_improvements.md:
--------------------------------------------------------------------------------
```markdown
# Implementation Plan: MCP Server Improvements
## Phase 1: Critical Fixes (High Priority)
### Task 1: Fix Resource Registry Import
- [x] Update `index-updated.ts` to import `resourceRegistry` at the top of the file
- [x] Remove the `require` call from the `ReadResourceRequestSchema` handler
- [x] Test the handler to ensure it works properly with the imported registry
### Task 2: Improve Error Handling
- [x] Modify `documentation.ts` to throw proper errors when template loading fails
- [x] Update `clarification.ts` to throw specific errors and return completion objects
- [x] Add try/catch blocks where necessary to handle these new errors properly
- [ ] Test the error handling with invalid inputs
## Phase 2: Architecture Improvements (Medium Priority)
### Task 1: Enhance Tool Registry with Metadata
- [x] Modify the `ToolRegistry` class in `registry.ts` to store and manage tool metadata
- [x] Add `listTools()` method to return all registered tools with their metadata
- [x] Update the `register` method to accept description and input schema
- [x] Modify `index-updated.ts` to use `toolRegistry.listTools()` in the handler
### Task 2: Implement Self-Registration Pattern
- [ ] Create a new file `tool-registry.ts` to define the tool registration process
- [ ] Modify each tool handler file to self-register on import
- [ ] Remove the centralized `registerToolHandlers` function
- [ ] Repeat the same pattern for resource handlers
- [ ] Test that all tools and resources are properly registered
## Phase 3: Enhance Testing Framework (Medium Priority)
### Task 1: Set Up Jest Testing Framework
- [ ] Install Jest and related dependencies
- [ ] Configure Jest for TypeScript testing
- [ ] Create a basic test setup file with server startup/shutdown
### Task 2: Migrate Existing Tests to Jest
- [ ] Convert the `test-mcp-improved.js` to use Jest's test structure
- [ ] Replace timeouts with proper async/await and promises
- [ ] Add assertions to verify responses
### Task 3: Add Comprehensive Tests
- [ ] Add tests for error cases and edge conditions
- [ ] Test URI matching with various patterns
- [ ] Test tool execution with valid and invalid inputs
- [ ] Test resource handling with various URIs
## Phase 4: Additional Improvements (Lower Priority)
### Task 1: Implement Better Templating
- [ ] Research and select an appropriate templating engine
- [ ] Update `documentation.ts` to use the selected templating engine
- [ ] Create improved templates with proper variable handling
### Task 2: Ensure Consistent Code Style
- [ ] Apply consistent naming conventions throughout the codebase
- [ ] Add JSDoc comments to all functions and classes
- [ ] Set up a code formatter like Prettier
## Implementation Steps
### For Phase 1:
1. Fix Resource Registry Import: ✅
```typescript
// At the top of index-updated.ts
import { toolRegistry, resourceRegistry } from './registry.js';
// In the ReadResourceRequestSchema handler
const match = resourceRegistry.findMatch(uri);
```
2. Improve Error Handling: ✅
```typescript
// In documentation.ts
function loadTemplate(templateName: string): string {
try {
return fs.readFileSync(templatePath, 'utf-8');
} catch (error) {
throw new Error(`Failed to load template ${templateName}: ${error}`);
}
}
// In clarification.ts
export function getNextClarificationQuestion(feature: Feature): string | { done: true } {
if (!feature.clarificationResponses) {
throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
}
if (feature.clarificationResponses.length >= DEFAULT_CLARIFICATION_QUESTIONS.length) {
return { done: true };
}
return DEFAULT_CLARIFICATION_QUESTIONS[feature.clarificationResponses.length];
}
```
### For Phase 2:
1. Enhance Tool Registry: ✅
```typescript
// In registry.ts
export class ToolRegistry {
private handlers: Map<string, ToolHandler> = new Map();
private toolMetadata: Map<string, { description: string; inputSchema: any }> = new Map();
register<T>(
name: string,
handler: ToolHandler<T>,
description: string,
inputSchema: any
): void {
this.handlers.set(name, handler);
this.toolMetadata.set(name, { description, inputSchema });
}
// ... existing methods ...
listTools(): { name: string; description: string; inputSchema: any }[] {
return Array.from(this.handlers.keys()).map((name) => {
const metadata = this.toolMetadata.get(name);
return {
name,
description: metadata?.description || "No description provided.",
inputSchema: metadata?.inputSchema || {},
};
});
}
}
// In index-updated.ts
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: toolRegistry.listTools(),
};
});
```
## Timeline and Dependencies
1. Phase 1 (1-2 days): ✅
- Resource registry fix: 0.5 day ✅
- Error handling improvements: 1-1.5 days ✅
- No dependencies
2. Phase 2 (2-3 days): 🔄
- Tool registry enhancements: 1-1.5 days ✅
- Self-registration pattern: 1-1.5 days 🔄
- Depends on Phase 1 completion ✅
```
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file utils.ts
* @version 1.1.0
*
* Utility functions for the Vibe-Coder MCP Server
*/
import { Feature, Phase, Task, PhaseStatus } from './types.js';
import { FeatureId, PhaseId, TaskId, createFeatureId, createPhaseId, createTaskId } from './schemas.js';
/**
* Generate a unique ID for features with proper prefix
* @returns A feature ID string
*/
export function generateFeatureId(): FeatureId {
const randomPart = Math.random().toString(36).substring(2, 10);
return createFeatureId(`feature-${randomPart}`);
}
/**
* Generate a unique ID for phases with proper prefix
* @returns A phase ID string
*/
export function generatePhaseId(): PhaseId {
const randomPart = Math.random().toString(36).substring(2, 10);
return createPhaseId(`phase-${randomPart}`);
}
/**
* Generate a unique ID for tasks with proper prefix
* @returns A task ID string
*/
export function generateTaskId(): TaskId {
const randomPart = Math.random().toString(36).substring(2, 10);
return createTaskId(`task-${randomPart}`);
}
/**
* Legacy ID generator for backward compatibility
* @deprecated Use the typed ID generators instead
* @returns A random string ID
*/
export function generateId(): string {
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}
/**
* Format a date to ISO string without milliseconds
* @param date The date to format
* @returns Formatted date string
*/
export function formatDate(date: Date): string {
return date.toISOString().split('.')[0] + 'Z';
}
/**
* Create a new timestamp
* @returns Current date
*/
export function now(): Date {
return new Date();
}
/**
* Create a default feature object with the given name and description
* @param name The feature name
* @param description The feature description
* @returns A new feature object
*/
export function createFeatureObject(name: string, description: string = ""): Feature {
const timestamp = now();
return {
id: generateFeatureId(),
name,
description,
clarificationResponses: [],
phases: [],
createdAt: timestamp,
updatedAt: timestamp
};
}
/**
* Create a default phase object with the given name and description
* @param name The phase name
* @param description The phase description
* @returns A new phase object
*/
export function createPhaseObject(name: string, description: string): Phase {
const timestamp = now();
return {
id: generatePhaseId(),
name,
description,
tasks: [],
status: "pending" as PhaseStatus,
createdAt: timestamp,
updatedAt: timestamp
};
}
/**
* Create a default task object with the given description
* @param description The task description
* @returns A new task object
*/
export function createTaskObject(description: string): Task {
const timestamp = now();
return {
id: generateTaskId(),
description,
completed: false,
createdAt: timestamp,
updatedAt: timestamp
};
}
/**
* Validate if a status is a valid phase status
* @param status The status to validate
* @returns True if valid, false otherwise
*/
export function isValidPhaseStatus(status: string): boolean {
return ['pending', 'in_progress', 'completed', 'reviewed'].includes(status);
}
/**
* Generate a detailed progress summary for a feature
* @param feature The feature to generate a summary for
* @returns A formatted string with the feature's progress details
*/
export function generateFeatureProgressSummary(feature: Feature): string {
const totalPhases = feature.phases.length;
const completedPhases = feature.phases.filter(p => p.status === 'completed' || p.status === 'reviewed').length;
const inProgressPhases = feature.phases.filter(p => p.status === 'in_progress').length;
const totalTasks = feature.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
const completedTasks = feature.phases.reduce(
(acc, phase) => acc + phase.tasks.filter(t => t.completed).length, 0
);
const phaseProgress = totalPhases > 0
? Math.round((completedPhases / totalPhases) * 100)
: 0;
const taskProgress = totalTasks > 0
? Math.round((completedTasks / totalTasks) * 100)
: 0;
let summary = `
# Feature Progress: ${feature.name}
## Overview
- Feature ID: ${feature.id}
- Created: ${feature.createdAt.toISOString()}
- Last Updated: ${feature.updatedAt.toISOString()}
- Description: ${feature.description}
## Progress Summary
- Phases: ${completedPhases}/${totalPhases} completed (${phaseProgress}%)
- Tasks: ${completedTasks}/${totalTasks} completed (${taskProgress}%)
- Phases in Progress: ${inProgressPhases}
## Phase Details
`;
if (totalPhases === 0) {
summary += "\nNo phases defined for this feature yet.";
} else {
feature.phases.forEach(phase => {
const phaseTasks = phase.tasks.length;
const phaseCompletedTasks = phase.tasks.filter(t => t.completed).length;
const phaseTaskProgress = phaseTasks > 0
? Math.round((phaseCompletedTasks / phaseTasks) * 100)
: 0;
summary += `
### ${phase.name} (${phase.status})
- ID: ${phase.id}
- Progress: ${phaseCompletedTasks}/${phaseTasks} tasks (${phaseTaskProgress}%)
- Description: ${phase.description}
Tasks:
${phase.tasks.map(task => `- [${task.completed ? 'x' : ' '}] ${task.description}`).join('\n')}
`;
});
}
return summary;
}
```
--------------------------------------------------------------------------------
/src/validators.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file validators.ts
* @version 1.0.0
*
* Provides validation utilities for the Vibe-Coder MCP Server tools.
* These validators ensure that tool inputs are valid and provide consistent
* error handling across all tools.
*/
import { Feature, Phase, Task, PhaseStatus } from './types.js';
import { getFeature } from './storage.js';
import { isValidPhaseStatus } from './utils.js';
/**
* Standard validation result type
*/
export type ValidationResult = {
valid: boolean;
message: string;
data?: any; // Optional validated data
};
/**
* Validate feature ID and return the feature if valid
* @param featureId The feature ID to validate
* @returns Validation result with feature in data if valid
*/
export function validateFeatureId(featureId: string): ValidationResult {
// Check if feature ID is provided
if (!featureId || featureId.trim() === '') {
return {
valid: false,
message: 'Feature ID is required'
};
}
// Check if feature exists
const feature = getFeature(featureId);
if (!feature) {
return {
valid: false,
message: `Feature with ID ${featureId} not found`
};
}
return {
valid: true,
message: 'Feature is valid',
data: feature
};
}
/**
* Validate phase ID in the context of a feature
* @param feature The feature containing the phase
* @param phaseId The phase ID to validate
* @returns Validation result with phase in data if valid
*/
export function validatePhaseId(feature: Feature, phaseId: string): ValidationResult {
// Check if phase ID is provided
if (!phaseId || phaseId.trim() === '') {
return {
valid: false,
message: 'Phase ID is required'
};
}
// Check if phase exists in feature
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
return {
valid: false,
message: `Phase with ID ${phaseId} not found in feature ${feature.name}`
};
}
return {
valid: true,
message: 'Phase is valid',
data: phase
};
}
/**
* Validate task ID in the context of a phase
* @param phase The phase containing the task
* @param taskId The task ID to validate
* @returns Validation result with task in data if valid
*/
export function validateTaskId(phase: Phase, taskId: string): ValidationResult {
// Check if task ID is provided
if (!taskId || taskId.trim() === '') {
return {
valid: false,
message: 'Task ID is required'
};
}
// Check if task exists in phase
const task = phase.tasks.find(t => t.id === taskId);
if (!task) {
return {
valid: false,
message: `Task with ID ${taskId} not found in phase ${phase.name}`
};
}
return {
valid: true,
message: 'Task is valid',
data: task
};
}
/**
* Validate phase status
* @param status The status to validate
* @returns Validation result
*/
export function validatePhaseStatusValue(status: string): ValidationResult {
if (!status || status.trim() === '') {
return {
valid: false,
message: 'Phase status is required'
};
}
if (!isValidPhaseStatus(status)) {
return {
valid: false,
message: `Invalid phase status: ${status}. Valid values are: pending, in_progress, completed, reviewed`
};
}
return {
valid: true,
message: 'Phase status is valid',
data: status as PhaseStatus
};
}
/**
* Validate required text field
* @param value The text value
* @param fieldName The name of the field for error messages
* @param minLength Minimum allowed length
* @param maxLength Maximum allowed length
* @returns Validation result
*/
export function validateRequiredText(
value: string,
fieldName: string,
minLength: number = 1,
maxLength: number = 1000
): ValidationResult {
if (!value || value.trim() === '') {
return {
valid: false,
message: `${fieldName} is required`
};
}
if (value.length < minLength) {
return {
valid: false,
message: `${fieldName} must be at least ${minLength} characters`
};
}
if (value.length > maxLength) {
return {
valid: false,
message: `${fieldName} must be no more than ${maxLength} characters`
};
}
return {
valid: true,
message: `${fieldName} is valid`,
data: value.trim()
};
}
/**
* Validate feature, phase, and task IDs together
* @param featureId Feature ID
* @param phaseId Phase ID
* @param taskId Task ID (optional)
* @returns Validation result with feature, phase, and task in data if valid
*/
export function validateFeaturePhaseTask(
featureId: string,
phaseId: string,
taskId?: string
): ValidationResult {
// Validate feature
const featureResult = validateFeatureId(featureId);
if (!featureResult.valid) {
return featureResult;
}
// Validate phase
const phaseResult = validatePhaseId(featureResult.data, phaseId);
if (!phaseResult.valid) {
return phaseResult;
}
// If taskId is provided, validate it
if (taskId) {
const taskResult = validateTaskId(phaseResult.data, taskId);
if (!taskResult.valid) {
return taskResult;
}
return {
valid: true,
message: 'Feature, phase, and task are valid',
data: {
feature: featureResult.data,
phase: phaseResult.data,
task: taskResult.data
}
};
}
return {
valid: true,
message: 'Feature and phase are valid',
data: {
feature: featureResult.data,
phase: phaseResult.data
}
};
}
```
--------------------------------------------------------------------------------
/instructions/PRD.md:
--------------------------------------------------------------------------------
```markdown
# Vibe-Coder MCP Server PRD
## 1. Introduction
The Vibe-Coder MCP Server is designed to help casual, LLM-based coders build new features in an organized, clean, and safe manner. By leveraging the Model Context Protocol (MCP), this server guides users through a structured process—from clarifying feature requests to implementing them in discrete, verifiable phases. This document defines the product requirements and lays the groundwork for the detailed implementation plan.
## 2. Feature Objectives
- **Assist LLM-based Coders:** Enable casual developers (vibe coders) to request and build features efficiently using an LLM-driven workflow.
- **Structured Development:** Organize the feature creation process into clearly defined stages, ensuring each phase is documented, reviewed, and implemented according to best practices.
- **Clean and Safe Code:** Enforce code quality standards, safe implementation practices, and thorough documentation throughout the development cycle.
- **Iterative Clarification:** Engage users in rounds of questions to continuously refine and narrow down the feature requirements.
## 3. Scope and Requirements
### 3.1 Feature Request Clarification
- **Iterative Engagement:**
- The system will prompt users with follow-up questions to clarify the exact goals and nuances of the requested feature.
- User responses will be recorded and used to refine the feature’s scope.
### 3.2 PRD Documentation
- **Concise PRD Drafting:**
- Develop a narrow and specific PRD that captures:
- **Feature Objectives:** A clear description of what the feature should achieve.
- **Detailed Requirements:** Specific, actionable requirements and constraints.
- **Documentation References:** Links or references to relevant code formats, style guides, and best practices.
- **File Organization:**
- Save the PRD as a Markdown file using the naming convention:
`StepNumber_FeatureName_PRD.md`
*(Example: “01_VibeCoder_PRD.md”)*
### 3.3 Implementation Specification
- **Phased Breakdown:**
- Divide the feature’s implementation into discrete phases, where each phase addresses a single focused task.
- **Checklists and Standards:**
- For each phase, include a checklist detailing:
- Task requirements
- Code style and practices that must be adhered to
- **Separate Documentation:**
- Save the implementation plan in its own Markdown file, following the naming convention:
`StepNumber_FeatureName_Implementation.md`
## 4. High-Level Implementation Overview
### 4.1 Development Workflow and Branching Strategy
- **Feature Branch:**
- Begin each new feature on a dedicated branch.
- **Phase Branches:**
- For every discrete phase within a feature, create a new branch off the feature branch.
- **Commit and Code Review Process:**
- After completing each phase:
- Create detailed git commits capturing all changes.
- Request a targeted code review that includes the PRD, the phase-specific implementation plan, and the relevant code changes.
- Merge the phase branch back into the feature branch (retain phase branches for history).
### 4.2 Finalization Process
- **Project Summary:**
- Upon feature completion, update the project README with a summary of the new functionality.
- **Feature-Specific README:**
- Create a dedicated README that details:
- The new feature’s functionality
- How to use the feature
- The adherence to the documented practices and checklists
- **Final Code Review:**
- Conduct a comprehensive code review covering all documentation and code, ensuring only the relevant portions are evaluated.
## 5. Structuring the MCP for LLM Compliance
To ensure the LLM follows the outlined instructions accurately, the following practices are essential:
- **Clear Hierarchical Sections:**
- Use distinct headings and subheadings for each major section (e.g., Introduction, Objectives, Scope, etc.).
- **Step-by-Step Breakdown:**
- Organize tasks in numbered steps with clear, actionable directives (e.g., “Draft the PRD,” “Create phase-specific branches”).
- **Consistent Naming Conventions:**
- Specify precise file and branch naming formats to eliminate ambiguity.
- **Imperative Language:**
- Use direct commands (e.g., “Create,” “Define,” “Save,” “Merge”) to ensure clarity.
- **Checklists and Examples:**
- Provide concrete examples or templates for PRD sections, implementation plans, and commit messages to serve as models.
- **Feedback Loops:**
- Include instructions to ask clarifying questions if any step is ambiguous.
- **Documentation Focus:**
- Emphasize thorough documentation of every phase with clear checklists and version control histories.
## 6. Feedback and Iteration Process
- **User Feedback Integration:**
- Regularly incorporate user feedback to refine feature objectives and requirements.
- **Iterative Updates:**
- Update both the PRD and the implementation plan as new details emerge during the development process.
- **Review and Approval:**
- Ensure that each phase is reviewed and approved before moving to subsequent phases.
## 7. Appendix
### 7.1 File Naming Conventions
- **PRD File:** `StepNumber_FeatureName_PRD.md`
*(Example: “01_VibeCoder_PRD.md”)*
- **Implementation Plan File:** `StepNumber_FeatureName_Implementation.md`
### 7.2 Example Checklists and Templates
- **Feature Request Checklist:**
- [ ] Define feature objective
- [ ] Clarify user requirements through iterative questions
- [ ] Record user responses for scope refinement
- **Phase Implementation Checklist:**
- [ ] Define phase-specific tasks
- [ ] Outline required code standards and practices
- [ ] Document progress with commit messages
- [ ] Request targeted code review
- [ ] Merge phase branch into feature branch after review
```
--------------------------------------------------------------------------------
/test-document-tools.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env bun
// Import the necessary modules from our codebase
import { createFeatureObject } from './build/utils.js';
import { storeFeature } from './build/storage.js';
import { addClarificationResponse } from './build/clarification.js';
import { generatePRD, generateImplementationPlan } from './build/documentation.js';
import { documentStorage, DocumentType } from './build/document-storage.js';
// Function to create a test feature with some clarification responses
async function createTestFeature() {
console.log('Creating test feature...');
// Create a new feature
const feature = createFeatureObject(
'Test Document Tools',
'A feature to test document storage tools'
);
// Store the feature first
storeFeature(feature);
// Add some clarification responses
addClarificationResponse(feature.id,
'What specific problem does this feature solve?',
'Testing document tools after fixing enum serialization issues'
);
addClarificationResponse(feature.id,
'Who are the target users for this feature?',
'Developers who need to use document storage tools'
);
addClarificationResponse(feature.id,
'What are the key requirements for this feature?',
'Both sets of document tools must work correctly'
);
addClarificationResponse(feature.id,
'What are the technical constraints or considerations?',
'Must work with various MCP clients'
);
addClarificationResponse(feature.id,
'How will we measure the success of this feature?',
'All document tools work correctly'
);
addClarificationResponse(feature.id,
'Are there any dependencies on other features or systems?',
'Document storage system'
);
addClarificationResponse(feature.id,
'What are the potential risks or challenges in implementing this feature?',
'Enum serialization issues with MCP'
);
// Generate documents
const prd = await generatePRD(feature);
console.log('Generated PRD');
const implementationPlan = await generateImplementationPlan(feature);
console.log('Generated Implementation Plan');
return feature;
}
// Function to test the document_path tool logic
async function testDocumentPath(feature) {
console.log('\nTesting document_path tool logic...');
try {
// Map string to DocumentType enum
const documentType = 'prd';
let docType;
if (documentType === 'prd') {
docType = DocumentType.PRD;
} else if (documentType === 'implementation-plan') {
docType = DocumentType.IMPLEMENTATION_PLAN;
} else {
throw new Error(`Invalid document type: ${documentType}`);
}
// Check if the document exists
if (!documentStorage.hasDocument(feature.id, docType)) {
throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`);
}
// Get the default file path for the document
const filePath = documentStorage.getDefaultFilePath(feature.id, docType);
// Get the document to check if it's been saved
const document = documentStorage.getDocument(feature.id, docType);
console.log(`Document path: ${filePath}`);
console.log(`Saved to disk: ${document?.metadata.isSaved ? 'Yes' : 'No'}`);
return {
filePath,
isSaved: document?.metadata.isSaved
};
} catch (error) {
console.error(`Error in document_path: ${error.message}`);
return null;
}
}
// Function to test the document_save tool logic
async function testDocumentSave(feature, customPath = null) {
console.log('\nTesting document_save tool logic...');
try {
// Map string to DocumentType enum
const documentType = 'prd';
let docType;
if (documentType === 'prd') {
docType = DocumentType.PRD;
} else if (documentType === 'implementation-plan') {
docType = DocumentType.IMPLEMENTATION_PLAN;
} else {
throw new Error(`Invalid document type: ${documentType}`);
}
// Check if the document exists
if (!documentStorage.hasDocument(feature.id, docType)) {
throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`);
}
let savedPath;
// If a custom path was provided, use it; otherwise, save to the default path
if (customPath) {
savedPath = await documentStorage.saveDocumentToCustomPath(feature.id, docType, customPath);
} else {
savedPath = await documentStorage.saveDocumentToFile(feature.id, docType);
}
console.log(`Document saved successfully to: ${savedPath}`);
return savedPath;
} catch (error) {
console.error(`Error in document_save: ${error.message}`);
return null;
}
}
// Main function to run all tests
async function main() {
try {
console.log('=== Document Tools Test ===');
// Create a test feature and generate documents
const feature = await createTestFeature();
console.log(`Feature created with ID: ${feature.id}`);
// Test document_path tool logic
const pathResult = await testDocumentPath(feature);
// Test document_save tool logic
const saveResult = await testDocumentSave(feature);
// Test with a custom path
const customPath = `./documents/test-document-tools-${Date.now()}.md`;
const customSaveResult = await testDocumentSave(feature, customPath);
// Print summary
console.log('\n=== Test Summary ===');
console.log(`Feature ID: ${feature.id}`);
console.log(`Document path: ${pathResult?.filePath || 'Error'}`);
console.log(`Document saved to default path: ${saveResult || 'Error'}`);
console.log(`Document saved to custom path: ${customSaveResult || 'Error'}`);
console.log('\nTest completed successfully!');
} catch (error) {
console.error('Error in test:', error);
}
}
// Run the main function
main().catch(console.error);
```
--------------------------------------------------------------------------------
/src/registry.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file registry.ts
* @version 1.1.0
*
* Provides registries for tools and resources in the Vibe-Coder MCP Server.
* These registries manage URI templates, matching, and tool handlers.
*/
import { ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
import { ErrorCode, createErrorResponse, createToolErrorResponse } from './errors.js';
import { Feature } from './types.js';
// --------- Tool Registry ---------
/**
* Type for tool handler function
*/
export type ToolHandler<T = any> = (params: T) => Promise<any>;
/**
* Tool metadata type
*/
export interface ToolMetadata {
description: string;
inputSchema: any;
examples?: any[];
}
/**
* Tool registry to map tool names to their handler functions and metadata
*/
export class ToolRegistry {
private handlers: Map<string, ToolHandler> = new Map();
private toolMetadata: Map<string, ToolMetadata> = new Map();
/**
* Register a tool handler with metadata
* @param name Tool name
* @param handler Tool handler function
* @param description Tool description
* @param inputSchema Tool input schema
* @param examples Optional examples for the tool
*/
register<T>(
name: string,
handler: ToolHandler<T>,
description: string,
inputSchema: any,
examples?: any[]
): void {
this.handlers.set(name, handler);
this.toolMetadata.set(name, {
description,
inputSchema,
examples
});
}
/**
* Get a tool handler by name
* @param name Tool name
* @returns The tool handler function or undefined if not found
*/
getHandler(name: string): ToolHandler | undefined {
return this.handlers.get(name);
}
/**
* Get tool metadata by name
* @param name Tool name
* @returns The tool metadata or undefined if not found
*/
getMetadata(name: string): ToolMetadata | undefined {
return this.toolMetadata.get(name);
}
/**
* Check if a tool exists
* @param name Tool name
* @returns True if the tool exists
*/
hasHandler(name: string): boolean {
return this.handlers.has(name);
}
/**
* Execute a tool by name with parameters
* @param name Tool name
* @param params Tool parameters
* @returns The tool execution result
*/
async execute(name: string, params: any): Promise<any> {
const handler = this.getHandler(name);
if (!handler) {
return createToolErrorResponse(`Unknown tool "${name}"`);
}
try {
return await handler(params);
} catch (error) {
console.error(`Error executing tool ${name}:`, error);
return createToolErrorResponse(
error instanceof Error ? error.message : "Internal server error"
);
}
}
/**
* List all registered tools with their metadata
* @returns Array of tool information objects
*/
listTools(): { name: string; description: string; inputSchema: any; examples?: any[] }[] {
return Array.from(this.handlers.keys()).map((name) => {
const metadata = this.toolMetadata.get(name);
return {
name,
description: metadata?.description || "No description provided",
inputSchema: metadata?.inputSchema || {},
examples: metadata?.examples
};
});
}
}
// --------- Resource Registry ---------
/**
* Resource handler type
*/
export type ResourceHandler = (uri: URL, params: Record<string, string>) => Promise<any>;
/**
* Resource registry entry
*/
type ResourceRegistryEntry = {
template: ResourceTemplate;
handler: ResourceHandler;
};
/**
* Resource registry for managing URI templates and handlers
*/
export class ResourceRegistry {
private resources: ResourceRegistryEntry[] = [];
/**
* Register a resource with a template and handler
*/
register(template: ResourceTemplate, handler: ResourceHandler): void {
this.resources.push({ template, handler });
}
/**
* Find a matching resource and extract parameters
*/
findMatch(uri: string): { handler: ResourceHandler; params: Record<string, string> } | undefined {
const url = new URL(uri);
for (const { template, handler } of this.resources) {
const match = this.matchTemplate(url, template);
if (match) {
return { handler, params: match };
}
}
return undefined;
}
/**
* Match a URL against a template and extract parameters
*/
private matchTemplate(url: URL, template: ResourceTemplate): Record<string, string> | undefined {
// Convert template to regex pattern
const pattern = this.templateToPattern(template.pattern);
const regex = new RegExp(pattern.pattern);
// Match against the full URL
const match = regex.exec(url.href);
if (!match) {
return undefined;
}
// Extract named parameters
const params: Record<string, string> = {};
pattern.paramNames.forEach((name, index) => {
params[name] = match[index + 1] || '';
});
return params;
}
/**
* Convert a URI template to a regex pattern
* Handles {param} syntax in templates
*/
private templateToPattern(template: string): { pattern: string; paramNames: string[] } {
const paramNames: string[] = [];
let pattern = template.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Replace {param} with capture groups
pattern = pattern.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, name) => {
paramNames.push(name);
return '([^/]+)';
});
// Replace * with wildcard
pattern = pattern.replace(/\\\*/g, '.*');
// Anchor to start of string
pattern = `^${pattern}$`;
return { pattern, paramNames };
}
/**
* List all registered resources
* @returns Array of resource templates
*/
listResources(): { template: string }[] {
return this.resources.map(({ template }) => ({
template: template.pattern
}));
}
}
// Create global instances
export const toolRegistry = new ToolRegistry();
export const resourceRegistry = new ResourceRegistry();
```
--------------------------------------------------------------------------------
/docs/mcp-sdk-improvements.md:
--------------------------------------------------------------------------------
```markdown
# MCP SDK Implementation Improvements
## Overview
This document outlines the improvements made to the Vibe-Coder MCP server implementation to better align with the MCP TypeScript SDK best practices and improve overall code quality, maintainability, and type safety.
## Key Improvements
### 1. Tool Handler Registry
We replaced the large switch statement in the main file with a tool handler registry that maps tool names to their handler functions:
```typescript
// Before (in index.ts)
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
switch (request.params.name) {
case "start_feature_clarification": {
// Implementation...
}
case "provide_clarification": {
// Implementation...
}
// Many more cases...
}
} catch (error) {
// Error handling...
}
});
// After
// In registry.ts
export class ToolRegistry {
private handlers: Map<string, ToolHandler> = new Map();
register<T>(name: string, handler: ToolHandler<T>): void {
this.handlers.set(name, handler);
}
async execute(name: string, params: any): Promise<any> {
const handler = this.getHandler(name);
if (!handler) {
return createToolErrorResponse(`Unknown tool "${name}"`);
}
return await handler(params);
}
}
// In index-updated.ts
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const toolName = request.params.name;
const toolArguments = request.params.arguments || {};
return await toolRegistry.execute(toolName, toolArguments);
} catch (error) {
// Improved error handling...
}
});
```
### 2. Robust Resource URI Handling
We implemented a robust resource URI handling mechanism using a `ResourceRegistry` class:
```typescript
// In registry.ts
export class ResourceRegistry {
private resources: ResourceRegistryEntry[] = [];
register(template: ResourceTemplate, handler: ResourceHandler): void {
this.resources.push({ template, handler });
}
findMatch(uri: string): { handler: ResourceHandler; params: Record<string, string> } | undefined {
const url = new URL(uri);
for (const { template, handler } of this.resources) {
const match = this.matchTemplate(url, template);
if (match) {
return { handler, params: match };
}
}
return undefined;
}
}
// In index-updated.ts
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
try {
const uri = request.params.uri;
const match = resourceRegistry.findMatch(uri);
if (match) {
return await match.handler(new URL(uri), match.params);
}
return createErrorResponse(ErrorCode.RESOURCE_NOT_FOUND, `Resource not found: ${uri}`);
} catch (error) {
// Improved error handling...
}
});
```
### 3. Improved Error Handling
We standardized error handling across the codebase with consistent JSON-RPC 2.0 error responses:
```typescript
// In errors.ts
export enum ErrorCode {
// JSON-RPC 2.0 Standard Error Codes
PARSE_ERROR = -32700,
INVALID_REQUEST = -32600,
METHOD_NOT_FOUND = -32601,
INVALID_PARAMS = -32602,
INTERNAL_ERROR = -32603,
// Custom Error Codes
VALIDATION_ERROR = -32000,
RESOURCE_NOT_FOUND = -32001,
// More custom error codes...
}
export const createErrorResponse = (
code: ErrorCode,
message: string,
data?: any
): ErrorResponse => {
return {
error: {
code,
message,
data
}
};
};
export const createToolErrorResponse = (
message: string,
details?: any
): ToolErrorResponse => {
return {
content: [{
type: "text",
text: `Error: ${message}`
}],
isError: true
};
};
```
### 4. Stronger Type Safety
We improved type safety throughout the codebase with Zod schemas and branded types:
```typescript
// In schemas.ts
// Branded types for IDs
export type FeatureId = string & { readonly _brand: unique symbol };
export type PhaseId = string & { readonly _brand: unique symbol };
export type TaskId = string & { readonly _brand: unique symbol };
// Zod schemas for validation
export const FeatureSchema = z.object({
id: z.string().refine(isFeatureId, "Invalid feature ID format"),
name: z.string().min(2).max(100),
// More fields...
});
// Type guards
export const isFeatureId = (id: string): id is FeatureId => id.startsWith('feature-');
// ID creators
export const createFeatureId = (id: string): FeatureId => {
if (!isFeatureId(id)) throw new Error(`Invalid feature ID format: ${id}`);
return id as FeatureId;
};
// In utils.ts
export function generateFeatureId(): FeatureId {
const randomPart = Math.random().toString(36).substring(2, 10);
return createFeatureId(`feature-${randomPart}`);
}
```
### 5. Modular Code Structure
We reorganized the codebase into more modular components with clear separation of concerns:
- `registry.ts` - Tool and resource registries
- `tool-handlers.ts` - Tool handler implementations
- `resource-handlers.ts` - Resource handler implementations
- `errors.ts` - Error handling utilities
- `schemas.ts` - Type definitions and validation schemas
## Benefits
1. **Improved Maintainability**: The modular code structure makes it easier to understand, maintain, and extend the codebase.
2. **Better Type Safety**: Branded types and Zod schemas provide stronger type checking and runtime validation.
3. **Consistent Error Handling**: Standardized error responses across the codebase with proper JSON-RPC 2.0 error codes.
4. **More Robust URI Handling**: Proper URI template matching for resources following MCP best practices.
5. **Cleaner Code**: Separation of concerns between different components and elimination of large switch statements.
## Testing
The improvements have been tested with a comprehensive test script (`test-mcp-improved.js`) that verifies the functionality of:
- Tool registration and execution
- Resource URI matching and handling
- Error reporting
- End-to-end workflows (feature clarification, PRD generation, etc.)
## Conclusion
These improvements bring the Vibe-Coder MCP server implementation more in line with MCP TypeScript SDK best practices while also improving overall code quality, maintainability, and type safety.
```
--------------------------------------------------------------------------------
/instructions/Impl.md:
--------------------------------------------------------------------------------
```markdown
# Vibe-Coder MCP Server Implementation Plan
This document outlines the step-by-step implementation strategy for building the Vibe-Coder MCP Server using the Model Context Protocol specification for TypeScript. Each phase represents a discrete, focused task with clear checklists, coding standards, and review processes.
---
## Phase 1: Environment Setup and Repository Initialization
### Objectives
- Set up the development environment and repository.
- Install required dependencies (including the MCP TypeScript SDK).
- Establish a working base by validating a simple MCP server.
### Tasks
- [ ] **Repository Initialization:**
- Create a new repository or branch dedicated to the Vibe-Coder feature.
- Follow the naming convention: `feature/vibe-coder`.
- [ ] **Environment Setup:**
- Ensure Node.js and npm are installed.
- Install the MCP TypeScript SDK:
```bash
npm install @modelcontextprotocol/sdk
```
- [ ] **Basic MCP Server Setup:**
- Create a minimal MCP server (e.g., an Echo server) based on the MCP TypeScript examples.
- Validate that the server correctly processes simple requests (using `StdioServerTransport` or HTTP with SSE).
- [ ] **Establish Branching Guidelines:**
- Document and enforce the branch naming convention (e.g., each phase creates a branch off the main feature branch).
### Code Style & Practices
- Use TypeScript best practices.
- Follow the MCP SDK examples for a clean code structure.
- Maintain clear commit messages describing changes made.
---
## Phase 2: Implement Feature Request Clarification Module
### Objectives
- Build a module to interactively engage users to clarify feature requests.
- Record and structure user responses to refine the feature scope.
### Tasks
- [ ] **Design Clarification Workflow:**
- Define a flow that asks iterative questions and records responses.
- Use clear prompts to ensure the user’s requirements are well defined.
- [ ] **Implement Clarification Module:**
- Create a dedicated TypeScript module (e.g., `clarification.ts`) that:
- Sends questions to the user.
- Stores responses in a structured format (e.g., JSON).
- [ ] **Integrate with MCP Tools/Prompts:**
- Utilize MCP prompts to ask clarifying questions if appropriate.
- [ ] **Testing:**
- Create unit tests that simulate user interactions and verify the response recording.
### Checklist
- [ ] Define a list of initial questions.
- [ ] Implement response recording with validation.
- [ ] Integrate and test prompt handling via MCP.
---
## Phase 3: PRD and Documentation Generation Module
### Objectives
- Automate the generation and saving of PRD documentation.
- Ensure that the PRD is saved in the correct folder with the proper naming convention.
### Tasks
- [ ] **Template Creation:**
- Develop a Markdown template for the PRD (reuse sections from the PRD document).
- [ ] **File Generation Module:**
- Create a module (e.g., `docGenerator.ts`) that:
- Populates the template with feature details.
- Saves the file to `vibe-instructions/` with a name like `01_VibeCoder_PRD.md`.
- [ ] **Validation:**
- Implement checks to ensure the generated file meets naming and format requirements.
### Checklist
- [ ] Define PRD template sections.
- [ ] Write file I/O functions to save Markdown files.
- [ ] Test file generation with dummy data.
---
## Phase 4: MCP Server Integration and Feature Implementation
### Objectives
- Integrate the clarified feature request and documentation modules into the MCP server.
- Build the core functionality of the Vibe-Coder using the MCP TypeScript SDK.
### Tasks
- [ ] **Server Initialization:**
- Initialize an MCP server instance using the MCP TypeScript SDK.
- Example:
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "Vibe-Coder",
version: "1.0.0"
});
```
- [ ] **Integrate Clarification Module:**
- Connect the clarification module to the MCP server’s tool or prompt mechanisms.
- [ ] **Feature Request Handling:**
- Define MCP tools or resources to process and store feature requests and clarifications.
- [ ] **Documentation Integration:**
- Link the generated PRD and Implementation Plan documents to the MCP server’s workflow (e.g., as resources that can be fetched by clients).
- [ ] **Testing:**
- Test server interactions using both local transports (stdio) and, if applicable, HTTP with SSE.
### Checklist
- [ ] Initialize MCP server using the TypeScript SDK.
- [ ] Integrate the clarification module with MCP prompts.
- [ ] Validate tool/resource functionality with sample requests.
- [ ] Ensure documentation files are retrievable by the server.
---
## Phase 5: Phased Implementation Workflow and Branching
### Objectives
- Establish and enforce the phased development workflow.
- Ensure each phase’s changes are documented, committed, and reviewed before merging.
### Tasks
- [ ] **Branch Creation:**
- Create a new branch for each phase from the main feature branch (e.g., `phase/clarification`, `phase/documentation`, `phase/integration`).
- [ ] **Commit Process:**
- Write detailed commit messages that reference the checklist and completed tasks.
- [ ] **Code Reviews:**
- Request targeted code reviews for each phase.
- Share the PRD, phase-specific Implementation Plan, and corresponding code changes.
- [ ] **Merge and History:**
- Merge phase branches into the feature branch once approved.
- Retain phase branches for historical reference.
### Checklist
- [ ] Create and document branch naming conventions.
- [ ] Write commit message guidelines.
- [ ] Schedule code reviews for each completed phase.
- [ ] Merge phase branches after successful review.
---
## Phase 6: Finalization, Project Summary, and Feature README
### Objectives
- Complete the feature by updating project documentation.
- Summarize the new functionality and provide usage instructions.
### Tasks
- [ ] **Project README Update:**
- Update the main project README with a summary of the Vibe-Coder feature.
- [ ] **Feature-Specific README:**
- Create a dedicated README that details:
- The new feature’s functionality.
- How to use and integrate the Vibe-Coder MCP Server.
- References to the PRD and Implementation Plan.
- [ ] **Final Code Review:**
- Request a comprehensive review covering the entire feature.
- Ensure all documentation, tests, and code changes meet the defined standards.
### Checklist
- [ ] Write a project summary for the new feature.
- [ ] Generate a feature-specific README with detailed instructions.
- [ ] Conduct a final, comprehensive code review.
- [ ] Merge final changes into the main branch.
---
## Summary
This Implementation Plan breaks down the development of the Vibe-Coder MCP Server into six discrete phases—from environment setup to final documentation and code review. Each phase is accompanied by detailed tasks and checklists, ensuring adherence to the MCP specification for TypeScript and maintaining a clear, organized workflow.
By following this plan, the development team will be able to iteratively build, test, and review the feature, ensuring that it is robust, well-documented, and compliant with established coding standards and best practices.
```
--------------------------------------------------------------------------------
/src/test-mcp-improved.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
/**
* @file test-mcp-improved.js
*
* Test script for the improved MCP server implementation.
* Tests the tool registry, resource registry, and error handling.
*/
const { spawn } = require('child_process');
const path = require('path');
// Path to the MCP server
const SERVER_PATH = path.join(__dirname, 'index-updated.ts');
// Store responses and state
const responses = [];
let featureId = null;
// Start the MCP server process
const server = spawn('ts-node', [SERVER_PATH], {
stdio: ['pipe', 'pipe', process.stderr]
});
// Handle server's stdout
server.stdout.on('data', (data) => {
try {
const lines = data.toString().trim().split('\n');
for (const line of lines) {
if (!line.trim()) continue;
// Parse the JSON response
const response = JSON.parse(line);
responses.push(response);
// Process the response based on its type
processResponse(response);
}
} catch (error) {
console.error('Error processing server response:', error);
}
});
// Process a server response
function processResponse(response) {
if (response.id === 'init') {
sendInitialized();
} else if (response.id === 'start-feature') {
// Extract feature ID from the response
const content = response.result?.content[0]?.text;
if (content) {
const match = content.match(/Feature ID: ([a-z0-9-]+)/);
if (match) {
featureId = match[1];
console.log(`\nCreated feature with ID: ${featureId}`);
// Ask the first clarification question
sendProvideClarification(featureId, 'What problem does this feature solve?', 'The feature solves the problem of managing test data across different environments.');
}
}
} else if (response.id === 'clarify-1') {
sendProvideClarification(featureId, 'Who are the target users?', 'Developers and QA engineers who need to create, manage, and share test data.');
} else if (response.id === 'clarify-2') {
sendProvideClarification(featureId, 'What are the key requirements?', 'Data import/export, version control integration, and user-friendly interface.');
} else if (response.id === 'clarify-3') {
sendProvideClarification(featureId, 'What are the success criteria?', 'Users can create and manage test data across environments with minimal effort.');
} else if (response.id === 'clarify-4') {
sendProvideClarification(featureId, 'Any technical constraints?', 'Must work with existing database systems and be containerizable.');
} else if (response.id === 'clarify-5') {
console.log('\nAll clarification questions answered.');
// Generate PRD
sendGeneratePRD(featureId);
} else if (response.id === 'generate-prd') {
// Generate implementation plan
sendGenerateImplementationPlan(featureId);
} else if (response.id === 'generate-implementation-plan') {
// Create a phase
sendCreatePhase(featureId);
} else if (response.id === 'create-phase') {
// Extract phase ID from the response
const content = response.result?.content[0]?.text;
if (content) {
const match = content.match(/ID: ([a-z0-9-]+)/);
if (match) {
const phaseId = match[1];
console.log(`\nCreated phase with ID: ${phaseId}`);
// Update phase status
sendUpdatePhaseStatus(featureId, phaseId, 'in_progress');
}
}
} else if (response.id === 'update-phase-status') {
// Get feature details
sendReadResource(`feature://${featureId}`);
} else if (response.id === 'read-feature') {
// Test resource handler
sendReadResource(`feature://${featureId}/progress`);
} else if (response.id === 'read-progress') {
// Test list resources
sendListResources();
} else if (response.id === 'list-resources') {
// Test list tools
sendListTools();
} else if (response.id === 'list-tools') {
// All tests completed
console.log('\nAll tests completed successfully.');
// Clean up
server.stdin.end();
process.exit(0);
}
}
// Send initialization message
function sendInitialize() {
const message = {
jsonrpc: '2.0',
id: 'init',
method: 'initialize',
params: {
processId: process.pid,
clientInfo: {
name: 'MCP Test Client',
version: '1.0.0'
},
capabilities: {
resources: {},
tools: {},
prompts: {}
}
}
};
sendMessage(message);
}
// Send initialized notification
function sendInitialized() {
const message = {
jsonrpc: '2.0',
method: 'initialized',
params: {}
};
sendMessage(message);
// Start the tests after initialization
console.log('\nStarting tests...');
sendStartFeature();
}
// Send a message to start feature clarification
function sendStartFeature() {
const message = {
jsonrpc: '2.0',
id: 'start-feature',
method: 'callTool',
params: {
name: 'start_feature_clarification',
arguments: {
featureName: 'Test Data Manager',
initialDescription: 'A tool for managing test data across various environments.'
}
}
};
sendMessage(message);
}
// Send a message to provide clarification
function sendProvideClarification(featureId, question, answer) {
// Use a different ID for each clarification to track them
const clarificationId = `clarify-${responses.filter(r => r.id && r.id.startsWith('clarify-')).length + 1}`;
const message = {
jsonrpc: '2.0',
id: clarificationId,
method: 'callTool',
params: {
name: 'provide_clarification',
arguments: {
featureId,
question,
answer
}
}
};
sendMessage(message);
}
// Send a message to generate a PRD
function sendGeneratePRD(featureId) {
const message = {
jsonrpc: '2.0',
id: 'generate-prd',
method: 'callTool',
params: {
name: 'generate_prd',
arguments: {
featureId
}
}
};
sendMessage(message);
}
// Send a message to generate an implementation plan
function sendGenerateImplementationPlan(featureId) {
const message = {
jsonrpc: '2.0',
id: 'generate-implementation-plan',
method: 'callTool',
params: {
name: 'generate_implementation_plan',
arguments: {
featureId
}
}
};
sendMessage(message);
}
// Send a message to create a phase
function sendCreatePhase(featureId) {
const message = {
jsonrpc: '2.0',
id: 'create-phase',
method: 'callTool',
params: {
name: 'create_phase',
arguments: {
featureId,
name: 'Design',
description: 'Design the architecture and user interface for the Test Data Manager.'
}
}
};
sendMessage(message);
}
// Send a message to update phase status
function sendUpdatePhaseStatus(featureId, phaseId, status) {
const message = {
jsonrpc: '2.0',
id: 'update-phase-status',
method: 'callTool',
params: {
name: 'update_phase_status',
arguments: {
featureId,
phaseId,
status
}
}
};
sendMessage(message);
}
// Send a message to read a resource
function sendReadResource(uri) {
const message = {
jsonrpc: '2.0',
id: uri.includes('progress') ? 'read-progress' : 'read-feature',
method: 'readResource',
params: {
uri
}
};
sendMessage(message);
}
// Send a message to list resources
function sendListResources() {
const message = {
jsonrpc: '2.0',
id: 'list-resources',
method: 'listResources',
params: {}
};
sendMessage(message);
}
// Send a message to list tools
function sendListTools() {
const message = {
jsonrpc: '2.0',
id: 'list-tools',
method: 'listTools',
params: {}
};
sendMessage(message);
}
// Send a message to the server
function sendMessage(message) {
const json = JSON.stringify(message);
server.stdin.write(json + '\n');
}
// Start the test
sendInitialize();
// Handle process exit
process.on('exit', () => {
server.kill();
});
```
--------------------------------------------------------------------------------
/src/document-storage.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file document-storage.ts
* @version 1.0.0
* @status STABLE - DO NOT MODIFY WITHOUT TESTS
* @lastModified 2023-03-23
*
* Document storage module for Vibe-Coder MCP Server.
* Provides both in-memory and file-based storage for documents
* generated during the feature development process.
*
* IMPORTANT:
* - Path validation is critical for security
* - All file operations must be properly error-handled
* - Document metadata must be maintained consistently
*
* Functionality:
* - Store documents in memory and file system
* - Retrieve documents from storage
* - Path resolution and validation
* - Error handling for file operations
*/
import * as fs from 'fs/promises';
import * as path from 'path';
import { Feature } from './types.js';
/**
* Document types that can be stored
*/
export enum DocumentType {
PRD = 'prd',
IMPLEMENTATION_PLAN = 'implementation-plan'
}
/**
* Document storage options
*/
export interface DocumentStorageOptions {
/** Root directory for document storage */
rootDir: string;
/** Whether to automatically save documents to files */
autoSave: boolean;
/** Whether to create directories if they don't exist */
createDirs: boolean;
}
/**
* Document metadata
*/
export interface DocumentMetadata {
/** Document type */
type: DocumentType;
/** Feature ID this document belongs to */
featureId: string;
/** Timestamp when the document was generated */
generatedAt: Date;
/** Path to the file, if saved */
filePath?: string;
/** Whether the document has been saved to a file */
isSaved: boolean;
}
/**
* Document with its content and metadata
*/
export interface Document {
/** Document content */
content: string;
/** Document metadata */
metadata: DocumentMetadata;
}
/**
* Default document storage options
*/
const DEFAULT_OPTIONS: DocumentStorageOptions = {
rootDir: path.join(process.cwd(), 'documents'),
autoSave: true,
createDirs: true
};
/**
* Document storage class
*/
export class DocumentStorage {
private options: DocumentStorageOptions;
private documents: Map<string, Document> = new Map();
/**
* Create a new document storage instance
* @param options Storage options
*/
constructor(options: Partial<DocumentStorageOptions> = {}) {
this.options = { ...DEFAULT_OPTIONS, ...options };
}
/**
* Generate a document key
* @param featureId Feature ID
* @param type Document type
* @returns Document key
*/
private getDocumentKey(featureId: string, type: DocumentType): string {
return `${featureId}:${type}`;
}
/**
* Generate a file path for a document
* @param featureId Feature ID
* @param type Document type
* @returns File path
*/
private getDocumentPath(featureId: string, type: DocumentType): string {
const featureDir = path.join(this.options.rootDir, featureId);
const filename = `${type}.md`;
return path.join(featureDir, filename);
}
/**
* Validate a file path to prevent directory traversal attacks
* @param filePath File path to validate
* @returns Normalized file path if valid, throws an error otherwise
*/
private validatePath(filePath: string): string {
// Normalize the path to resolve .. and . segments
const normalizedPath = path.normalize(filePath);
// Convert both paths to absolute paths for comparison
const absolutePath = path.isAbsolute(normalizedPath)
? normalizedPath
: path.join(process.cwd(), normalizedPath);
const rootDir = path.isAbsolute(this.options.rootDir)
? this.options.rootDir
: path.join(process.cwd(), this.options.rootDir);
// Check if the path is outside the root directory
if (!absolutePath.startsWith(rootDir)) {
throw new Error(`Invalid file path: Path must be within ${rootDir}`);
}
return normalizedPath;
}
/**
* Validate a custom file path provided by a client
* @param filePath File path to validate
* @returns Normalized file path if valid, throws an error otherwise
*/
public validateCustomPath(filePath: string): string {
// First validate that it's within the root directory
const normalizedPath = this.validatePath(filePath);
// Additional validation for custom paths
if (path.extname(normalizedPath) !== '.md') {
throw new Error('Invalid file path: File must have .md extension');
}
return normalizedPath;
}
/**
* Ensure directory exists, creating it if necessary and allowed
* @param dirPath Directory path
*/
private async ensureDir(dirPath: string): Promise<void> {
try {
await fs.access(dirPath);
} catch (error) {
if (this.options.createDirs) {
await fs.mkdir(dirPath, { recursive: true });
} else {
throw new Error(`Directory does not exist: ${dirPath}`);
}
}
}
/**
* Store a document
* @param featureId Feature ID
* @param type Document type
* @param content Document content
* @param autoSave Whether to automatically save the document to a file
* @returns Stored document
*/
public async storeDocument(
featureId: string,
type: DocumentType,
content: string,
autoSave: boolean = this.options.autoSave
): Promise<Document> {
const key = this.getDocumentKey(featureId, type);
const filePath = this.getDocumentPath(featureId, type);
const metadata: DocumentMetadata = {
type,
featureId,
generatedAt: new Date(),
filePath,
isSaved: false
};
const document: Document = {
content,
metadata
};
this.documents.set(key, document);
if (autoSave) {
try {
await this.saveDocumentToFile(featureId, type);
} catch (error) {
console.error(`Failed to auto-save document: ${error}`);
// Note that we still store the document in memory even if file save fails
}
}
return document;
}
/**
* Get a document
* @param featureId Feature ID
* @param type Document type
* @returns Document or undefined if not found
*/
public getDocument(featureId: string, type: DocumentType): Document | undefined {
const key = this.getDocumentKey(featureId, type);
return this.documents.get(key);
}
/**
* Check if a document exists
* @param featureId Feature ID
* @param type Document type
* @returns True if the document exists
*/
public hasDocument(featureId: string, type: DocumentType): boolean {
const key = this.getDocumentKey(featureId, type);
return this.documents.has(key);
}
/**
* Save a document to a file using its default path
* @param featureId Feature ID
* @param type Document type
* @returns Path to the saved file
*/
public async saveDocumentToFile(featureId: string, type: DocumentType): Promise<string> {
const document = this.getDocument(featureId, type);
if (!document) {
throw new Error(`Document not found: ${featureId}:${type}`);
}
const filePath = document.metadata.filePath!;
const dirPath = path.dirname(filePath);
await this.ensureDir(dirPath);
await fs.writeFile(filePath, document.content, 'utf-8');
// Update metadata
document.metadata.isSaved = true;
return filePath;
}
/**
* Save a document to a custom file path
* @param featureId Feature ID
* @param type Document type
* @param customPath Custom file path
* @returns Path to the saved file
*/
public async saveDocumentToCustomPath(
featureId: string,
type: DocumentType,
customPath: string
): Promise<string> {
const document = this.getDocument(featureId, type);
if (!document) {
throw new Error(`Document not found: ${featureId}:${type}`);
}
const validatedPath = this.validateCustomPath(customPath);
const dirPath = path.dirname(validatedPath);
await this.ensureDir(dirPath);
await fs.writeFile(validatedPath, document.content, 'utf-8');
// Don't update metadata.filePath as we want to keep the default path
// Just mark it as saved
document.metadata.isSaved = true;
return validatedPath;
}
/**
* Get the default file path for a document
* @param featureId Feature ID
* @param type Document type
* @returns Default file path
*/
public getDefaultFilePath(featureId: string, type: DocumentType): string {
return this.getDocumentPath(featureId, type);
}
}
// Create a singleton instance
export const documentStorage = new DocumentStorage();
```
--------------------------------------------------------------------------------
/src/index-updated.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
/**
* @file Vibe-Coder MCP Server
* @version 0.2.0
*
* This MCP server implements a structured development workflow that helps
* LLM-based coders build features in an organized, clean, and safe manner.
*
* Core functionalities:
* - Feature request clarification through iterative questioning
* - PRD and implementation plan generation
* - Phased development with tasks and status tracking
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/types.js";
// Import core modules
import { Feature, FeatureStorage, PhaseStatus, Phase, Task, ClarificationResponse } from './types.js';
import { features, storeFeature, getFeature, updateFeature, listFeatures } from './storage.js';
import {
DEFAULT_CLARIFICATION_QUESTIONS as CLARIFICATION_QUESTIONS,
getNextClarificationQuestion,
addClarificationResponse,
formatClarificationResponses,
isClarificationComplete,
getClarificationStatus
} from './clarification.js';
import { generatePRD, generateImplementationPlan } from './documentation.js';
import { createPhase, getPhase, updatePhaseStatus, getNextPhaseStatus, validatePhaseTransition, addTask, updateTaskStatus } from './phases.js';
import { generateId, createFeatureObject, createPhaseObject, createTaskObject, generateFeatureProgressSummary, isValidPhaseStatus } from './utils.js';
import { validateFeatureId, validatePhaseId, validateTaskId, validateFeaturePhaseTask, validateRequiredText, validatePhaseStatusValue } from './validators.js';
import { toolRegistry, resourceRegistry } from './registry.js';
import { registerToolHandlers } from './tool-handlers.js';
import { registerResourceHandlers } from './resource-handlers.js';
import { ErrorCode, createErrorResponse, createToolErrorResponse } from './errors.js';
/**
* Create an MCP server with capabilities for resources, tools, and prompts
*/
const server = new Server(
{
name: "Vibe-Coder MCP Server",
version: "0.2.0",
},
{
capabilities: {
resources: {}, // Expose resources for features, PRDs, and implementation plans
tools: {}, // Provide tools for feature clarification and development
prompts: {}, // Supply prompts for guiding the development process
},
}
);
/**
* Initialize the server by registering all tool and resource handlers
*/
function initializeServer() {
// Register all tool handlers
registerToolHandlers();
// Register all resource handlers
registerResourceHandlers();
console.error("Vibe-Coder MCP Server initialized successfully");
}
/**
* Handler for listing available resources.
*/
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "features://list",
mimeType: "text/plain",
name: "Features List",
description: "Lists all features being developed"
},
{
uri: "features://status",
mimeType: "text/markdown",
name: "Project Status",
description: "Provides a summary of all features and their development status"
},
...listFeatures().flatMap(feature => [
{
uri: `feature://${feature.id}`,
mimeType: "text/plain",
name: feature.name,
description: `Details about feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/progress`,
mimeType: "text/markdown",
name: `${feature.name} Progress Report`,
description: `Detailed progress report for feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/prd`,
mimeType: "text/markdown",
name: `${feature.name} PRD`,
description: `PRD document for feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/implementation`,
mimeType: "text/markdown",
name: `${feature.name} Implementation Plan`,
description: `Implementation plan for feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/phases`,
mimeType: "text/plain",
name: `${feature.name} Phases`,
description: `Lists all phases for feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/tasks`,
mimeType: "text/plain",
name: `${feature.name} All Tasks`,
description: `Lists all tasks across all phases for feature: ${feature.name}`
},
...feature.phases.flatMap(phase => [
{
uri: `feature://${feature.id}/phase/${phase.id}`,
mimeType: "text/plain",
name: `${feature.name} - ${phase.name}`,
description: `Details about phase: ${phase.name} for feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/phase/${phase.id}/tasks`,
mimeType: "text/plain",
name: `${feature.name} - ${phase.name} Tasks`,
description: `Lists all tasks for phase: ${phase.name}`
},
...phase.tasks.map(task => ({
uri: `feature://${feature.id}/phase/${phase.id}/task/${task.id}`,
mimeType: "text/plain",
name: `Task: ${task.description.substring(0, 30)}${task.description.length > 30 ? '...' : ''}`,
description: `Details about task: ${task.description.substring(0, 50)}${task.description.length > 50 ? '...' : ''}`
}))
])
])
]
};
});
/**
* Handler for reading feature resources.
*/
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
try {
const uri = request.params.uri;
const match = resourceRegistry.findMatch(uri);
if (match) {
return await match.handler(new URL(uri), match.params);
}
return createErrorResponse(
ErrorCode.RESOURCE_NOT_FOUND,
`Resource not found: ${uri}`
);
} catch (error: any) {
console.error(`Error reading resource ${request.params.uri}:`, error);
return createErrorResponse(
ErrorCode.INTERNAL_ERROR,
error.message || 'Unknown error'
);
}
});
/**
* Handler that lists available tools.
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: toolRegistry.listTools()
};
});
/**
* Handler for implementing MCP tools.
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const toolName = request.params.name;
const toolArguments = request.params.arguments || {};
// Execute the tool using the tool registry
return await toolRegistry.execute(toolName, toolArguments);
} catch (error: any) {
console.error('Error executing tool:', error);
return createToolErrorResponse(`An unexpected error occurred: ${error.message || 'Unknown error'}`);
}
});
/**
* Handler that lists available prompts.
*/
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "clarify_feature",
description: "Guide to clarify a feature request through questioning"
}
]
};
});
/**
* Handler for the clarify_feature prompt.
*/
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
if (request.params.name === "clarify_feature") {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: "Help me clarify this feature request by asking questions about:"
}
},
{
role: "user",
content: {
type: "text",
text: "1. The specific problem it solves\n2. The target users\n3. Key requirements\n4. Success criteria\n5. Technical constraints\n\nAsk one question at a time, analyze the response, then proceed to the next most relevant question."
}
}
]
};
}
return createErrorResponse(
ErrorCode.PROMPT_NOT_FOUND,
`Unknown prompt: ${request.params.name}`
);
});
/**
* Start the server using stdio transport.
*/
async function main() {
console.error("Starting Vibe-Coder MCP Server...");
// Initialize the server
initializeServer();
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/phases.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Phase management module for the Vibe-Coder MCP Server.
* This module handles the creation and management of development phases and tasks.
*/
import { Feature, Phase, Task, PhaseStatus } from './types.js';
import { updateFeature, getFeature } from './storage.js';
import { generateId, createPhaseObject, createTaskObject, isValidPhaseStatus, now } from './utils.js';
/**
* Define valid phase status transitions
* A map of current status to valid next statuses
*/
const VALID_PHASE_TRANSITIONS: Record<PhaseStatus, PhaseStatus[]> = {
'pending': ['in_progress'],
'in_progress': ['completed'],
'completed': ['reviewed'],
'reviewed': []
};
/**
* Create a new phase for a feature
* @param featureId The ID of the feature to create the phase for
* @param name The name of the phase
* @param description The description of the phase
* @returns The created phase or undefined if the feature is not found
*/
export function createPhase(
featureId: string,
name: string,
description: string
): Phase | undefined {
const feature = getFeature(featureId);
if (!feature) return undefined;
const newPhase = createPhaseObject(name, description);
updateFeature(featureId, {
phases: [...feature.phases, newPhase]
});
return newPhase;
}
/**
* Get a phase by ID
* @param featureId The ID of the feature
* @param phaseId The ID of the phase to get
* @returns The phase or undefined if not found
*/
export function getPhase(
featureId: string,
phaseId: string
): Phase | undefined {
const feature = getFeature(featureId);
if (!feature) return undefined;
return feature.phases.find(p => p.id === phaseId);
}
/**
* Add a new phase to a feature
* @param featureId The ID of the feature to add the phase to
* @param name The name of the phase
* @param description The description of the phase
* @returns The updated feature or undefined if not found
*/
export function addPhase(
featureId: string,
name: string,
description: string
): Feature | undefined {
const feature = getFeature(featureId);
if (!feature) return undefined;
const newPhase = createPhaseObject(name, description);
return updateFeature(featureId, {
phases: [...feature.phases, newPhase]
});
}
/**
* Add a task to a phase
* @param featureId The ID of the feature
* @param phaseId The ID of the phase to add the task to
* @param description The description of the task
* @returns The updated feature or undefined if not found
*/
export function addTask(
featureId: string,
phaseId: string,
description: string
): Feature | undefined {
const feature = getFeature(featureId);
if (!feature) return undefined;
const phaseIndex = feature.phases.findIndex(p => p.id === phaseId);
if (phaseIndex === -1) return undefined;
const newTask = createTaskObject(description);
const updatedPhases = [...feature.phases];
updatedPhases[phaseIndex] = {
...updatedPhases[phaseIndex],
tasks: [...updatedPhases[phaseIndex].tasks, newTask],
updatedAt: now()
};
return updateFeature(featureId, { phases: updatedPhases });
}
/**
* Update the status of a phase
* @param featureId The ID of the feature
* @param phaseId The ID of the phase to update
* @param status The new status
* @returns The updated feature or undefined if not found
*/
export function updatePhaseStatus(
featureId: string,
phaseId: string,
status: PhaseStatus
): Feature | undefined {
const feature = getFeature(featureId);
if (!feature) return undefined;
const phaseIndex = feature.phases.findIndex(p => p.id === phaseId);
if (phaseIndex === -1) return undefined;
// Validate the status transition
const phase = feature.phases[phaseIndex];
const validationResult = validatePhaseTransition(phase.status, status);
if (!validationResult.valid) {
console.error(validationResult.message);
return undefined;
}
const updatedPhases = [...feature.phases];
updatedPhases[phaseIndex] = {
...updatedPhases[phaseIndex],
status,
updatedAt: now()
};
return updateFeature(featureId, { phases: updatedPhases });
}
/**
* Update a task's completion status
* @param featureId The ID of the feature
* @param phaseId The ID of the phase
* @param taskId The ID of the task to update
* @param completed Whether the task is completed
* @returns The updated feature or undefined if not found
*/
export function updateTaskStatus(
featureId: string,
phaseId: string,
taskId: string,
completed: boolean
): Feature | undefined {
const feature = getFeature(featureId);
if (!feature) return undefined;
const phaseIndex = feature.phases.findIndex(p => p.id === phaseId);
if (phaseIndex === -1) return undefined;
const taskIndex = feature.phases[phaseIndex].tasks.findIndex(t => t.id === taskId);
if (taskIndex === -1) return undefined;
const updatedPhases = [...feature.phases];
const updatedTasks = [...updatedPhases[phaseIndex].tasks];
updatedTasks[taskIndex] = {
...updatedTasks[taskIndex],
completed,
updatedAt: now()
};
updatedPhases[phaseIndex] = {
...updatedPhases[phaseIndex],
tasks: updatedTasks,
updatedAt: now()
};
return updateFeature(featureId, { phases: updatedPhases });
}
/**
* Validate a phase status transition
* @param currentStatus The current status
* @param newStatus The proposed new status
* @returns An object with valid flag and message
*/
export function validatePhaseTransition(
currentStatus: PhaseStatus,
newStatus: PhaseStatus
): { valid: boolean; message: string } {
// Allow same status
if (currentStatus === newStatus) {
return { valid: true, message: "Status unchanged" };
}
// Check if the status is valid
if (!isValidPhaseStatus(newStatus)) {
return {
valid: false,
message: `Invalid status: ${newStatus}. Valid statuses are: pending, in_progress, completed, reviewed`
};
}
// Check if the transition is valid
const validNextStatuses = VALID_PHASE_TRANSITIONS[currentStatus];
if (!validNextStatuses.includes(newStatus)) {
return {
valid: false,
message: `Invalid transition from ${currentStatus} to ${newStatus}. Valid next statuses are: ${validNextStatuses.join(', ')}`
};
}
return { valid: true, message: "Valid transition" };
}
/**
* Get the next logical status for a phase
* @param currentStatus The current status
* @returns The recommended next status, or null if no valid transition
*/
export function getNextPhaseStatus(currentStatus: PhaseStatus): PhaseStatus | null {
const validNextStatuses = VALID_PHASE_TRANSITIONS[currentStatus];
if (validNextStatuses.length === 0) {
return null;
}
return validNextStatuses[0];
}
/**
* Get a summary of the phases for a feature
* @param featureId The ID of the feature
* @returns A text summary of the phases
*/
export function getPhaseSummary(featureId: string): string {
const feature = getFeature(featureId);
if (!feature) return "Feature not found";
if (feature.phases.length === 0) {
return "No phases defined for this feature yet.";
}
const phasesSummary = feature.phases.map(phase => {
const completedTasks = phase.tasks.filter(t => t.completed).length;
const totalTasks = phase.tasks.length;
return `${phase.name} (${phase.status}): ${completedTasks}/${totalTasks} tasks completed`;
}).join('\n');
return `Phases for ${feature.name}:\n${phasesSummary}`;
}
/**
* Initialize default phases for a feature based on its implementation plan
* @param featureId The ID of the feature
* @returns The updated feature or undefined if not found
*/
export function initializeDefaultPhases(featureId: string): Feature | undefined {
const feature = getFeature(featureId);
if (!feature) return undefined;
// If the feature already has phases, don't override them
if (feature.phases && feature.phases.length > 0) {
return feature;
}
// Create default phases
const defaultPhases = [
createPhaseObject(
"Requirements Analysis and Design",
"Analyze requirements, design the architecture, and create a detailed implementation plan."
),
createPhaseObject(
"Core Implementation",
"Implement the core functionality based on the design."
),
createPhaseObject(
"Testing and Integration",
"Test all components, integrate with existing systems, and refine the implementation."
),
createPhaseObject(
"Documentation and Finalization",
"Finalize documentation, clean up code, and prepare for deployment."
)
];
// Add default tasks for each phase
const phase1 = defaultPhases[0];
addTask(featureId, phase1.id, "Review and analyze clarification responses");
addTask(featureId, phase1.id, "Identify key components and their interactions");
addTask(featureId, phase1.id, "Design the system architecture");
addTask(featureId, phase1.id, "Create UML diagrams if necessary");
addTask(featureId, phase1.id, "Identify potential edge cases and risks");
const phase2 = defaultPhases[1];
addTask(featureId, phase2.id, "Set up project structure and dependencies");
addTask(featureId, phase2.id, "Implement data models and interfaces");
addTask(featureId, phase2.id, "Build core business logic");
addTask(featureId, phase2.id, "Create unit tests for core functionality");
addTask(featureId, phase2.id, "Ensure code follows best practices");
const phase3 = defaultPhases[2];
addTask(featureId, phase3.id, "Write unit tests for all components");
addTask(featureId, phase3.id, "Perform integration testing");
addTask(featureId, phase3.id, "Fix bugs and edge cases");
addTask(featureId, phase3.id, "Optimize performance");
addTask(featureId, phase3.id, "Document any known limitations");
const phase4 = defaultPhases[3];
addTask(featureId, phase4.id, "Complete inline code documentation");
addTask(featureId, phase4.id, "Create user documentation");
addTask(featureId, phase4.id, "Clean up and refactor code");
addTask(featureId, phase4.id, "Prepare deployment strategy");
addTask(featureId, phase4.id, "Create final pull request");
return updateFeature(featureId, { phases: defaultPhases });
}
```
--------------------------------------------------------------------------------
/MCP-Typescript-readme.txt:
--------------------------------------------------------------------------------
```
MCP TypeScript SDK NPM Version MIT licensed
Table of Contents
Overview
Installation
Quickstart
What is MCP?
Core Concepts
Server
Resources
Tools
Prompts
Running Your Server
stdio
HTTP with SSE
Testing and Debugging
Examples
Echo Server
SQLite Explorer
Advanced Usage
Low-Level Server
Writing MCP Clients
Server Capabilities
Overview
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to:
Build MCP clients that can connect to any MCP server
Create MCP servers that expose resources, prompts and tools
Use standard transports like stdio and SSE
Handle all MCP protocol messages and lifecycle events
Installation
npm install @modelcontextprotocol/sdk
Quick Start
Let's create a simple MCP server that exposes a calculator tool and some data:
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "Demo",
version: "1.0.0"
});
// Add an addition tool
server.tool("add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// Add a dynamic greeting resource
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
What is MCP?
The Model Context Protocol (MCP) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
Expose data through Resources (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
Provide functionality through Tools (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
Define interaction patterns through Prompts (reusable templates for LLM interactions)
And more!
Core Concepts
Server
The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
const server = new McpServer({
name: "My App",
version: "1.0.0"
});
Resources
Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
// Static resource
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{
uri: uri.href,
text: "App configuration here"
}]
})
);
// Dynamic resource with parameters
server.resource(
"user-profile",
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
async (uri, { userId }) => ({
contents: [{
uri: uri.href,
text: `Profile data for user ${userId}`
}]
})
);
Tools
Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
// Simple tool with parameters
server.tool(
"calculate-bmi",
{
weightKg: z.number(),
heightM: z.number()
},
async ({ weightKg, heightM }) => ({
content: [{
type: "text",
text: String(weightKg / (heightM * heightM))
}]
})
);
// Async tool with external API call
server.tool(
"fetch-weather",
{ city: z.string() },
async ({ city }) => {
const response = await fetch(`https://api.weather.com/${city}`);
const data = await response.text();
return {
content: [{ type: "text", text: data }]
};
}
);
Prompts
Prompts are reusable templates that help LLMs interact with your server effectively:
server.prompt(
"review-code",
{ code: z.string() },
({ code }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please review this code:\n\n${code}`
}
}]
})
);
Running Your Server
MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:
stdio
For command-line tools and direct integrations:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "example-server",
version: "1.0.0"
});
// ... set up server resources, tools, and prompts ...
const transport = new StdioServerTransport();
await server.connect(transport);
HTTP with SSE
For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to:
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const server = new McpServer({
name: "example-server",
version: "1.0.0"
});
// ... set up server resources, tools, and prompts ...
const app = express();
app.get("/sse", async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
app.post("/messages", async (req, res) => {
// Note: to support multiple simultaneous connections, these messages will
// need to be routed to a specific matching transport. (This logic isn't
// implemented here, for simplicity.)
await transport.handlePostMessage(req, res);
});
app.listen(3001);
Testing and Debugging
To test your server, you can use the MCP Inspector. See its README for more information.
Examples
Echo Server
A simple server demonstrating resources, tools, and prompts:
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({
name: "Echo",
version: "1.0.0"
});
server.resource(
"echo",
new ResourceTemplate("echo://{message}", { list: undefined }),
async (uri, { message }) => ({
contents: [{
uri: uri.href,
text: `Resource echo: ${message}`
}]
})
);
server.tool(
"echo",
{ message: z.string() },
async ({ message }) => ({
content: [{ type: "text", text: `Tool echo: ${message}` }]
})
);
server.prompt(
"echo",
{ message: z.string() },
({ message }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please process this message: ${message}`
}
}]
})
);
SQLite Explorer
A more complex example showing database integration:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";
const server = new McpServer({
name: "SQLite Explorer",
version: "1.0.0"
});
// Helper to create DB connection
const getDb = () => {
const db = new sqlite3.Database("database.db");
return {
all: promisify<string, any[]>(db.all.bind(db)),
close: promisify(db.close.bind(db))
};
};
server.resource(
"schema",
"schema://main",
async (uri) => {
const db = getDb();
try {
const tables = await db.all(
"SELECT sql FROM sqlite_master WHERE type='table'"
);
return {
contents: [{
uri: uri.href,
text: tables.map((t: {sql: string}) => t.sql).join("\n")
}]
};
} finally {
await db.close();
}
}
);
server.tool(
"query",
{ sql: z.string() },
async ({ sql }) => {
const db = getDb();
try {
const results = await db.all(sql);
return {
content: [{
type: "text",
text: JSON.stringify(results, null, 2)
}]
};
} catch (err: unknown) {
const error = err as Error;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
} finally {
await db.close();
}
}
);
Advanced Usage
Low-Level Server
For more control, you can use the low-level Server class directly:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
ListPromptsRequestSchema,
GetPromptRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{
name: "example-server",
version: "1.0.0"
},
{
capabilities: {
prompts: {}
}
}
);
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [{
name: "example-prompt",
description: "An example prompt template",
arguments: [{
name: "arg1",
description: "Example argument",
required: true
}]
}]
};
});
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
if (request.params.name !== "example-prompt") {
throw new Error("Unknown prompt");
}
return {
description: "Example prompt",
messages: [{
role: "user",
content: {
type: "text",
text: "Example prompt text"
}
}]
};
});
const transport = new StdioServerTransport();
await server.connect(transport);
Writing MCP Clients
The SDK provides a high-level client interface:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
},
{
capabilities: {
prompts: {},
resources: {},
tools: {}
}
}
);
await client.connect(transport);
// List prompts
const prompts = await client.listPrompts();
// Get a prompt
const prompt = await client.getPrompt("example-prompt", {
arg1: "value"
});
// List resources
const resources = await client.listResources();
// Read a resource
const resource = await client.readResource("file:///example.txt");
// Call a tool
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});
```
--------------------------------------------------------------------------------
/src/documentation.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file documentation.ts
* @version 1.1.0
* @status STABLE - DO NOT MODIFY WITHOUT TESTS
* @lastModified 2023-03-23
*
* Documentation module for the Vibe-Coder MCP Server.
* This module handles PRD and implementation plan generation
* based on clarification responses.
*
* IMPORTANT:
* - Document generation uses structured templates
* - Output is stored both in-memory and in the file system
*
* Functionality:
* - PRD generation
* - Implementation plan generation
* - Document storage via DocumentStorage module
*/
import { Feature, ClarificationResponse } from './types.js';
import { formatDate } from './utils.js';
import { documentStorage, DocumentType } from './document-storage.js';
/**
* Extract objectives from clarification responses
* @param responses Clarification responses
* @returns Array of objectives
*/
export function extractObjectivesFromClarifications(responses: ClarificationResponse[]): string[] {
// Find the response to the problem question
const problemResponse = responses.find(r =>
r.question.toLowerCase().includes('what specific problem') ||
r.question.toLowerCase().includes('what problem')
);
if (!problemResponse) return [];
// Extract objectives from the problem statement
const answer = problemResponse.answer;
const objectives: string[] = [];
// If the response has numbered points or bullet points
if (answer.includes('\n- ') || answer.includes('\n* ') || /\n\d+\./.test(answer)) {
const lines = answer.split('\n').map(line => line.trim());
for (const line of lines) {
// Look for bullet points or numbered lists
if (line.startsWith('- ') || line.startsWith('* ') || /^\d+\./.test(line)) {
// Clean up the line (remove the bullet or number)
const cleanLine = line.replace(/^- |^\* |^\d+\.\s*/, '');
objectives.push(cleanLine);
}
}
} else {
// No clear bullet points, try to extract sentences
const sentences = answer.split(/\.(?!\d)/g).map(s => s.trim()).filter(s => s.length > 10);
objectives.push(...sentences);
}
return objectives;
}
/**
* Extract requirements from clarification responses
* @param responses Clarification responses
* @returns Array of requirements
*/
export function extractRequirementsFromClarifications(responses: ClarificationResponse[]): string[] {
// Find the response to the requirements question
const requirementsResponse = responses.find(r =>
r.question.toLowerCase().includes('key requirements')
);
if (!requirementsResponse) return [];
// Extract requirements from the response
const answer = requirementsResponse.answer;
const requirements: string[] = [];
// If the response has numbered points or bullet points
if (answer.includes('\n- ') || answer.includes('\n* ') || /\n\d+\./.test(answer)) {
const lines = answer.split('\n').map(line => line.trim());
for (const line of lines) {
// Look for bullet points or numbered lists
if (line.startsWith('- ') || line.startsWith('* ') || /^\d+\.\s*/.test(line)) {
// Clean up the line (remove the bullet or number)
let cleanLine = line.replace(/^- |^\* |^\d+\.\s*/, '');
// Check if the line itself contains a nested list
if (cleanLine.includes(':')) {
const [req, details] = cleanLine.split(':');
requirements.push(req.trim());
// If there are nested items, add them as separate requirements
if (details && (details.includes(', ') || details.includes(' and '))) {
const nestedItems = details
.split(/,\s*|\s+and\s+/)
.map(item => item.trim())
.filter(Boolean);
for (const item of nestedItems) {
requirements.push(`${req.trim()} - ${item}`);
}
}
} else {
requirements.push(cleanLine);
}
}
}
} else {
// No clear bullet points, try to extract sentences
const sentences = answer.split(/\.(?!\d)/g).map(s => s.trim()).filter(s => s.length > 10);
requirements.push(...sentences);
}
return requirements;
}
/**
* Extract technical specifications from clarification responses
* @param responses Clarification responses
* @returns Array of technical specifications
*/
export function extractTechnicalSpecsFromClarifications(responses: ClarificationResponse[]): string[] {
// Find the response to the technical constraints question
const technicalResponse = responses.find(r =>
r.question.toLowerCase().includes('technical constraints') ||
r.question.toLowerCase().includes('technical considerations')
);
if (!technicalResponse) return [];
// Extract technical specifications from the response
const answer = technicalResponse.answer;
const specs: string[] = [];
// If the response has numbered points or bullet points
if (answer.includes('\n- ') || answer.includes('\n* ') || /\n\d+\./.test(answer)) {
const lines = answer.split('\n').map(line => line.trim());
for (const line of lines) {
// Look for bullet points or numbered lists
if (line.startsWith('- ') || line.startsWith('* ') || /^\d+\.\s*/.test(line)) {
// Clean up the line (remove the bullet or number)
const cleanLine = line.replace(/^- |^\* |^\d+\.\s*/, '');
specs.push(cleanLine);
}
}
} else {
// No clear bullet points, try to extract sentences
const sentences = answer.split(/\.(?!\d)/g).map(s => s.trim()).filter(s => s.length > 10);
specs.push(...sentences);
}
return specs;
}
/**
* Generate a Product Requirements Document (PRD) for a feature
* @param feature The feature to generate a PRD for
* @returns The PRD document as a markdown string
*/
export function generatePRD(feature: Feature): string {
const { name, description, clarificationResponses } = feature;
// Find specific clarification responses
const problemResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('what specific problem') ||
r.question.toLowerCase().includes('what problem')
);
const usersResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('target users')
);
const requirementsResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('key requirements')
);
const technicalResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('technical constraints') ||
r.question.toLowerCase().includes('technical considerations')
);
const metricsResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('measure') &&
r.question.toLowerCase().includes('success')
);
const dependenciesResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('dependencies')
);
const risksResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('risks') ||
r.question.toLowerCase().includes('challenges')
);
// Extract structured information
const objectives = extractObjectivesFromClarifications(clarificationResponses);
const requirements = extractRequirementsFromClarifications(clarificationResponses);
const technicalSpecs = extractTechnicalSpecsFromClarifications(clarificationResponses);
// Format the PRD
const prd = `# ${name} - Product Requirements Document
## 1. Introduction
### 1.1 Purpose
${problemResponse ? problemResponse.answer : description}
### 1.2 Scope
This document outlines the requirements and specifications for the ${name} feature.
### 1.3 Background
${description}
## 2. Product Overview
### 2.1 Product Description
${description}
### 2.2 Target Users
${usersResponse ? usersResponse.answer : 'To be determined during development.'}
## 3. Functional Requirements
${requirements.map((req, index) => `### 3.${index + 1} ${req}`).join('\n\n')}
## 4. Non-Functional Requirements
### 4.1 Technical Constraints
${technicalResponse ? technicalResponse.answer : 'No specific technical constraints identified.'}
### 4.2 Performance Requirements
${technicalSpecs.filter(spec =>
spec.toLowerCase().includes('performance') ||
spec.toLowerCase().includes('speed') ||
spec.toLowerCase().includes('time') ||
spec.toLowerCase().includes('fast')
).map(spec => `- ${spec}`).join('\n') || 'No specific performance requirements identified.'}
## 5. Success Metrics
### 5.1 Key Performance Indicators
${metricsResponse ? metricsResponse.answer : 'Success metrics to be determined.'}
## 6. Dependencies
### 6.1 System Dependencies
${dependenciesResponse ? dependenciesResponse.answer : 'No specific dependencies identified.'}
## 7. Risks and Challenges
### 7.1 Identified Risks
${risksResponse ? risksResponse.answer : 'Risks to be assessed during development.'}
## 8. Milestones and Implementation Plan
Refer to the Implementation Plan document for detailed phases and tasks.
---
Generated on: ${formatDate(new Date())}
`;
// Store the generated PRD in the document storage system
documentStorage.storeDocument(feature.id, DocumentType.PRD, prd)
.catch(error => console.error(`Failed to store PRD document: ${error}`));
return prd;
}
/**
* Generate an implementation plan for a feature
* @param feature The feature to generate an implementation plan for
* @returns The implementation plan as a markdown string
*/
export function generateImplementationPlan(feature: Feature): string {
const { name, description, clarificationResponses } = feature;
// Find specific clarification responses
const requirementsResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('key requirements')
);
const technicalResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('technical constraints') ||
r.question.toLowerCase().includes('technical considerations')
);
const dependenciesResponse = clarificationResponses.find(r =>
r.question.toLowerCase().includes('dependencies')
);
// Extract structured information
const requirements = extractRequirementsFromClarifications(clarificationResponses);
const technicalSpecs = extractTechnicalSpecsFromClarifications(clarificationResponses);
// Format the implementation plan
const implementationPlan = `# ${name} - Implementation Plan
## 1. Overview
${description}
## 2. Requirements Summary
${requirementsResponse ? requirementsResponse.answer : 'Requirements to be determined.'}
## 3. Technical Approach
${technicalResponse ? technicalResponse.answer : 'Technical approach to be determined during development.'}
## 4. Implementation Phases
### Phase 1: Setup and Initial Development
**Description**: Set up the development environment and implement core functionality.
**Tasks**:
${requirements.slice(0, Math.ceil(requirements.length / 3)).map(req => `- Implement ${req}`).join('\n')}
### Phase 2: Core Feature Implementation
**Description**: Implement the main feature components and functionality.
**Tasks**:
${requirements.slice(Math.ceil(requirements.length / 3), Math.ceil(requirements.length * 2 / 3)).map(req => `- Implement ${req}`).join('\n')}
### Phase 3: Testing and Refinement
**Description**: Test the feature thoroughly and refine based on feedback.
**Tasks**:
${requirements.slice(Math.ceil(requirements.length * 2 / 3)).map(req => `- Test and refine ${req}`).join('\n')}
- Write automated tests for all functionality
- Conduct code review
- Performance optimization
## 5. Dependencies and Prerequisites
${dependenciesResponse ? dependenciesResponse.answer : 'No specific dependencies identified.'}
## 6. Timeline Estimate
- Phase 1: 1-2 weeks
- Phase 2: 2-3 weeks
- Phase 3: 1-2 weeks
Total estimated time: 4-7 weeks, depending on complexity and available resources.
## 7. Resources Required
- Developer time: 1-2 developers
- Testing resources
- Technical documentation
- Any specific tools or libraries mentioned in dependencies
---
Generated on: ${formatDate(new Date())}
`;
// Store the generated implementation plan in the document storage system
documentStorage.storeDocument(feature.id, DocumentType.IMPLEMENTATION_PLAN, implementationPlan)
.catch(error => console.error(`Failed to store implementation plan document: ${error}`));
return implementationPlan;
}
```
--------------------------------------------------------------------------------
/src/resource-handlers.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @file resource-handlers.ts
* @version 1.0.0
*
* Provides handlers for resources exposed by the Vibe-Coder MCP Server.
* These handlers are registered with the resource registry for consistent handling.
*/
import { ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
import { resourceRegistry, ResourceHandler } from './registry.js';
import { Feature } from './types.js';
import { listFeatures, getFeature } from './storage.js';
import { formatClarificationResponses } from './clarification.js';
import { generatePRD, generateImplementationPlan } from './documentation.js';
import { generateFeatureProgressSummary } from './utils.js';
import { ErrorCode, createErrorResponse } from './errors.js';
/**
* Define resource templates for all resources
*/
const RESOURCE_TEMPLATES = {
featuresList: new ResourceTemplate('features://list', { list: undefined }),
featuresStatus: new ResourceTemplate('features://status', { list: undefined }),
featureDetail: new ResourceTemplate('feature://{featureId}', { list: undefined }),
featureProgress: new ResourceTemplate('feature://{featureId}/progress', { list: undefined }),
featurePrd: new ResourceTemplate('feature://{featureId}/prd', { list: undefined }),
featureImplementation: new ResourceTemplate('feature://{featureId}/implementation', { list: undefined }),
featurePhases: new ResourceTemplate('feature://{featureId}/phases', { list: undefined }),
featureTasks: new ResourceTemplate('feature://{featureId}/tasks', { list: undefined }),
phaseDetail: new ResourceTemplate('feature://{featureId}/phase/{phaseId}', { list: undefined }),
phaseTasks: new ResourceTemplate('feature://{featureId}/phase/{phaseId}/tasks', { list: undefined }),
taskDetail: new ResourceTemplate('feature://{featureId}/phase/{phaseId}/task/{taskId}', { list: undefined }),
};
/**
* Handler for the features list resource
*/
const featuresListHandler: ResourceHandler = async (uri, params) => {
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: listFeatures().map(f => `${f.id}: ${f.name}`).join("\n")
}]
};
};
/**
* Handler for the features status resource
*/
const featuresStatusHandler: ResourceHandler = async (uri, params) => {
const features = listFeatures();
if (features.length === 0) {
return {
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: "# Project Status\n\nNo features have been created yet."
}]
};
}
const featuresStatus = features.map(feature => {
const totalPhases = feature.phases.length;
const completedPhases = feature.phases.filter(p => p.status === 'completed' || p.status === 'reviewed').length;
const totalTasks = feature.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
const completedTasks = feature.phases.reduce(
(acc, phase) => acc + phase.tasks.filter(t => t.completed).length, 0
);
return `## ${feature.name}
- ID: ${feature.id}
- Status: ${completedPhases === totalPhases && totalPhases > 0 ? 'Completed' : 'In Progress'}
- Phases: ${completedPhases}/${totalPhases} completed
- Tasks: ${completedTasks}/${totalTasks} completed
- [View Details](feature://${feature.id}/progress)
`;
}).join('\n');
return {
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: `# Project Status\n\n${featuresStatus}`
}]
};
};
/**
* Handler for the feature detail resource
*/
const featureDetailHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const timestamp = feature.updatedAt.toISOString();
const clarifications = formatClarificationResponses(feature.clarificationResponses);
const phasesText = feature.phases.map(p =>
`- ${p.name} (${p.status}): ${p.tasks.filter(t => t.completed).length}/${p.tasks.length} tasks completed`
).join('\n');
const featureDetails = `
Feature: ${feature.name}
ID: ${feature.id}
Description: ${feature.description}
Last Updated: ${timestamp}
Clarification Responses:
${clarifications}
Phases (${feature.phases.length}):
${phasesText}
`;
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: featureDetails
}]
};
};
/**
* Handler for the feature progress resource
*/
const featureProgressHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const progressReport = generateFeatureProgressSummary(feature);
return {
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: progressReport
}]
};
};
/**
* Handler for the feature PRD resource
*/
const featurePrdHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const prdContent = feature.prdDoc || generatePRD(feature);
return {
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: prdContent
}]
};
};
/**
* Handler for the feature implementation plan resource
*/
const featureImplementationHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const implContent = feature.implDoc || generateImplementationPlan(feature);
return {
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: implContent
}]
};
};
/**
* Handler for the feature phases resource
*/
const featurePhasesHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
if (feature.phases.length === 0) {
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: `No phases defined for feature: ${feature.name}`
}]
};
}
const phasesContent = feature.phases.map(phase => {
const completedTasks = phase.tasks.filter(t => t.completed).length;
const totalTasks = phase.tasks.length;
return `Phase: ${phase.name} (ID: ${phase.id})
Status: ${phase.status}
Description: ${phase.description}
Progress: ${completedTasks}/${totalTasks} tasks completed
Last Updated: ${phase.updatedAt.toISOString()}
`;
}).join('\n---\n\n');
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: `# Phases for Feature: ${feature.name}\n\n${phasesContent}`
}]
};
};
/**
* Handler for the feature tasks resource
*/
const featureTasksHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const allTasks = feature.phases.flatMap(phase =>
phase.tasks.map(task => ({
...task,
phaseName: phase.name,
phaseId: phase.id,
phaseStatus: phase.status
}))
);
if (allTasks.length === 0) {
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: `No tasks defined for feature: ${feature.name}`
}]
};
}
const pendingTasks = allTasks.filter(t => !t.completed);
const completedTasks = allTasks.filter(t => t.completed);
const pendingTasksText = pendingTasks.length > 0
? pendingTasks.map(task => `- [ ] ${task.description} (ID: ${task.id}, Phase: ${task.phaseName})`).join('\n')
: "No pending tasks.";
const completedTasksText = completedTasks.length > 0
? completedTasks.map(task => `- [x] ${task.description} (ID: ${task.id}, Phase: ${task.phaseName})`).join('\n')
: "No completed tasks.";
const tasksContent = `# All Tasks for Feature: ${feature.name}
## Pending Tasks (${pendingTasks.length})
${pendingTasksText}
## Completed Tasks (${completedTasks.length})
${completedTasksText}
`;
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: tasksContent
}]
};
};
/**
* Handler for the phase detail resource
*/
const phaseDetailHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const phaseId = params.phaseId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
return createErrorResponse(ErrorCode.PHASE_NOT_FOUND, `Phase ${phaseId} not found in feature ${feature.name}`);
}
const completedTasks = phase.tasks.filter(t => t.completed).length;
const totalTasks = phase.tasks.length;
const taskList = phase.tasks.map(task =>
`- [${task.completed ? 'x' : ' '}] ${task.description} (ID: ${task.id})`
).join('\n');
const phaseDetails = `
Phase: ${phase.name}
ID: ${phase.id}
Status: ${phase.status}
Description: ${phase.description}
Created: ${phase.createdAt.toISOString()}
Last Updated: ${phase.updatedAt.toISOString()}
Progress: ${completedTasks}/${totalTasks} tasks completed
Tasks:
${taskList}
`;
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: phaseDetails
}]
};
};
/**
* Handler for the phase tasks resource
*/
const phaseTasksHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const phaseId = params.phaseId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
return createErrorResponse(ErrorCode.PHASE_NOT_FOUND, `Phase ${phaseId} not found in feature ${feature.name}`);
}
if (phase.tasks.length === 0) {
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: `No tasks defined for phase: ${phase.name}`
}]
};
}
const tasksContent = phase.tasks.map(task => `
Task: ${task.description}
ID: ${task.id}
Status: ${task.completed ? 'Completed' : 'Pending'}
Created: ${task.createdAt.toISOString()}
Last Updated: ${task.updatedAt.toISOString()}
`).join('\n---\n');
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: `# Tasks for Phase: ${phase.name}\n\n${tasksContent}`
}]
};
};
/**
* Handler for the task detail resource
*/
const taskDetailHandler: ResourceHandler = async (uri, params) => {
const featureId = params.featureId;
const phaseId = params.phaseId;
const taskId = params.taskId;
const feature = getFeature(featureId);
if (!feature) {
return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`);
}
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
return createErrorResponse(ErrorCode.PHASE_NOT_FOUND, `Phase ${phaseId} not found in feature ${feature.name}`);
}
const task = phase.tasks.find(t => t.id === taskId);
if (!task) {
return createErrorResponse(ErrorCode.TASK_NOT_FOUND, `Task ${taskId} not found in phase ${phase.name}`);
}
const taskDetails = `
Task: ${task.description}
ID: ${task.id}
Status: ${task.completed ? 'Completed' : 'Pending'}
Created: ${task.createdAt.toISOString()}
Last Updated: ${task.updatedAt.toISOString()}
`;
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: taskDetails
}]
};
};
/**
* Register all resource handlers
*/
export function registerResourceHandlers() {
resourceRegistry.register(RESOURCE_TEMPLATES.featuresList, featuresListHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.featuresStatus, featuresStatusHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.featureDetail, featureDetailHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.featureProgress, featureProgressHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.featurePrd, featurePrdHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.featureImplementation, featureImplementationHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.featurePhases, featurePhasesHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.featureTasks, featureTasksHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.phaseDetail, phaseDetailHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.phaseTasks, phaseTasksHandler);
resourceRegistry.register(RESOURCE_TEMPLATES.taskDetail, taskDetailHandler);
}
// Export individual handlers for testing or direct usage
export {
featuresListHandler,
featuresStatusHandler,
featureDetailHandler,
featureProgressHandler,
featurePrdHandler,
featureImplementationHandler,
featurePhasesHandler,
featureTasksHandler,
phaseDetailHandler,
phaseTasksHandler,
taskDetailHandler
};
```
--------------------------------------------------------------------------------
/instructions/solidity_tic_tac_toe_project.md:
--------------------------------------------------------------------------------
```markdown
# Solidity Tic Tac Toe Project
**Feature ID:** feature-hy5l98td
## Clarification Responses
### What specific problem does this feature solve?
This feature solves the problem of creating verifiable, tamper-proof gaming experiences on blockchain. It demonstrates how traditional games can be implemented in a decentralized manner where the game state and rules are enforced by smart contracts rather than a central server, ensuring fair play and transparent outcomes.
### Who are the target users for this feature?
The target users are:
1) Blockchain enthusiasts who want to experience decentralized gaming
2) Ethereum developers learning how to implement game logic in smart contracts
3) Educational platforms teaching blockchain concepts through familiar games
4) Players interested in crypto-gaming who want to try simple blockchain games with potential for token rewards
### What are the key requirements for this feature?
Key requirements include:
1) A Solidity smart contract that implements the complete Tic Tac Toe game logic
2) Functions for player registration and turn management
3) Win condition detection and game state verification
4) Prevention of illegal moves and proper handling of drawn games
5) Option for players to wager ETH on game outcomes
6) Events that can be used by a frontend to track game progress
7) Gas-efficient implementation to minimize transaction costs
### What are the technical constraints or considerations?
Technical constraints include:
1) Gas optimization is critical - each move costs ETH to execute
2) The contract must be compatible with Solidity 0.8.x and deployable to Ethereum and compatible chains like Polygon
3) State management must be efficient as blockchain storage is expensive
4) Proper access control is needed to ensure only valid players can make moves in their own games
5) Time constraints should be considered to prevent abandoned games
6) Input validation is crucial to prevent exploits or illegal game states
7) No external dependencies should be required beyond standard Solidity libraries
### How will we measure the success of this feature?
Success will be measured by:
1) Successful deployment of the contract to a testnet with verified gameplay
2) Completion of full games without errors or invalid states
3) Gas usage below 200,000 gas per move
4) Proper detection of all win conditions and draws
5) Correct handling of wagers and payouts
6) Successful integration with a basic web frontend for demonstration
7) Security audit passing without critical vulnerabilities
8) Documentation that allows developers to understand and interact with the contract
### Are there any dependencies on other features or systems?
Dependencies include:
1) Ethereum or compatible blockchain network for deployment
2) Solidity compiler version 0.8.x
3) Hardhat or Truffle for development, testing and deployment
4) Ethers.js or Web3.js for frontend integration
5) MetaMask or similar Web3 provider for user interaction
6) A basic frontend application (HTML/JS/CSS) for user interface, though this could be developed separately
7) OpenZeppelin contracts for secure implementation patterns (optional)
### What are the potential risks or challenges in implementing this feature?
The potential risks and challenges include:
1) High gas costs making gameplay expensive if not optimized
2) Smart contract security vulnerabilities like reentrancy attacks or integer overflows
3) Poor user experience due to blockchain transaction delays
4) Abandoned games if players stop participating
5) Complexity in handling edge cases like ties or unexpected termination
6) Difficulties in upgrading the contract if bugs are found since blockchain code is immutable
7) Ensuring fairness in the game mechanics while maintaining decentralization
8) Testing challenges due to the distributed nature of blockchain applications
## Development Phases
### Phase 1: Smart Contract Development
**Status**: In Progress
**Description**: Develop the core Solidity smart contract for the Tic Tac Toe game, including game logic, state management, and event emission.
**Tasks**:
- Set up Hardhat development environment with Solidity 0.8.x
- Implement game board representation and state management
- Implement player registration and game creation functionality
- Implement move validation and game state updates
- Implement win condition detection and game completion logic
### Phase 2: Testing and Optimization
**Description**: Write test cases, optimize gas usage, and ensure security of the Tic Tac Toe contract.
**Tasks**:
- Write unit tests for all game functionality
- Perform gas optimization for core game functions
- Conduct security review and implement safeguards
### Phase 3: Deployment and Documentation
**Description**: Deploy the contract to testnets and create comprehensive documentation for developers and users.
**Tasks**:
- Deploy contract to Ethereum testnet (Goerli/Sepolia)
- Create technical documentation with API references
- Create a demo frontend for interacting with the contract
## Product Requirements Document
### 1. Introduction
#### 1.1 Purpose
This document outlines the requirements and specifications for a decentralized Tic Tac Toe game implemented as a Solidity smart contract on the Ethereum blockchain. The goal is to create a fully functional, gas-efficient, and secure implementation that demonstrates how traditional games can be implemented in a trustless, decentralized environment.
#### 1.2 Scope
The scope of this product includes the smart contract implementation of Tic Tac Toe, including game creation, player management, game logic, win condition detection, and wagering functionality. A basic web frontend for interacting with the contract will be developed as part of the demonstration but is not the primary focus of this PRD.
#### 1.3 Background
Blockchain games represent a growing segment of the web3 ecosystem, offering new possibilities for transparent and verifiable gameplay. Tic Tac Toe, as a simple yet widely understood game, provides an excellent introduction to smart contract gaming concepts while being straightforward enough to implement efficiently on-chain.
### 2. Product Overview
#### 2.1 Product Description
A decentralized Tic Tac Toe game built on Ethereum that allows two players to compete against each other with all game actions and state changes recorded on the blockchain. Players can create games, join existing games, make moves, and potentially wager ETH on the outcome.
#### 2.2 Target Users
- Blockchain enthusiasts interested in decentralized applications
- Ethereum developers learning smart contract development
- Educational platforms teaching blockchain concepts
- Casual players interested in crypto gaming experiences
### 3. Functional Requirements
#### 3.1 Game Creation and Setup
- Users must be able to create a new game, becoming player 1 (X)
- Users must be able to join an existing open game as player 2 (O)
- Game creators must be able to specify whether ETH wagering is enabled
- If wagering is enabled, both players must contribute the same amount of ETH to participate
#### 3.2 Gameplay Mechanics
- The game board must be represented as a 3x3 grid
- Players must take turns making moves, with player 1 (X) always going first
- The contract must validate moves to ensure they are made:
- By the correct player
- On a valid, empty position on the board
- During an active game
- The contract must prevent players from making moves when it's not their turn
- The contract must update and store the game state after each valid move
#### 3.3 Win Conditions and Game Completion
- The contract must detect win conditions: 3 in a row horizontally, vertically, or diagonally
- The contract must detect draw conditions when all positions are filled with no winner
- When a game is completed, the contract must:
- Record the winner (or draw)
- Prevent further moves
- Distribute any wagered ETH to the winner (or refund in case of a draw)
#### 3.4 Event Emission
- The contract must emit events for:
- Game creation
- Player joining a game
- Each valid move made
- Game completion (win/draw)
- ETH distribution if wagering is enabled
### 4. Non-Functional Requirements
#### 4.1 Performance and Optimization
- Gas usage for moves should be below 200,000 gas
- The contract should minimize on-chain storage to reduce gas costs
- The game logic should be optimized for minimal computational complexity
#### 4.2 Security
- The contract must implement proper access controls
- Input validation must be comprehensive to prevent unexpected game states
- The contract should be resistant to common smart contract vulnerabilities
- The contract must handle ETH transfers securely if wagering is enabled
#### 4.3 Compatibility
- The contract must be compatible with Solidity 0.8.x
- The contract must be deployable to Ethereum mainnet and testnets
- The contract should work with standard Web3 providers like MetaMask
### 5. Technical Specifications
#### 5.1 Smart Contract Architecture
- `TicTacToe.sol`: Main contract implementing the game logic
- Data structures:
- Game struct containing board state, player addresses, current turn, game status, and wager information
- Mapping from game ID to Game struct for multi-game support
- Key functions:
- `createGame()`: Create a new game instance
- `joinGame(uint gameId)`: Join an existing game
- `makeMove(uint gameId, uint8 x, uint8 y)`: Make a move on the board
- `getGameState(uint gameId)`: Return the current state of a specific game
#### 5.2 Development Environment
- Hardhat for development, testing, and deployment
- Solidity 0.8.x as the smart contract language
- Ethers.js for frontend integration
- OpenZeppelin contracts for security patterns (optional)
### 6. User Interface (Frontend)
While the frontend is not the primary focus, a basic web interface should be developed to demonstrate interaction with the smart contract:
#### 6.1 Key Frontend Features
- Connect wallet functionality
- Create new game / Join existing game options
- Visual representation of the game board
- Move selection interface
- Game status display
- Transaction status and confirmation
### 7. Testing Strategy
#### 7.1 Unit Testing
- Test all contract functions in isolation
- Verify win condition logic covers all possible winning configurations
- Test error conditions and invalid inputs
#### 7.2 Integration Testing
- Test full game scenarios from creation to completion
- Test wagering functionality if implemented
- Verify correct event emission
#### 7.3 Security Testing
- Review for common smart contract vulnerabilities
- Test for correct access control enforcement
- Verify proper handling of ETH transfers
### 8. Milestones and Implementation Plan
The implementation will be organized into three main phases:
#### 8.1 Phase 1: Smart Contract Development
- Set up development environment
- Implement core game logic and state management
- Build player registration and turn management
- Create win condition detection
#### 8.2 Phase 2: Testing and Optimization
- Develop comprehensive test suite
- Optimize for gas efficiency
- Conduct security review
#### 8.3 Phase 3: Deployment and Documentation
- Deploy to Ethereum testnet
- Create technical documentation
- Develop demo frontend
## Implementation Plan
### 1. Environment Setup
#### 1.1 Development Environment
- Set up a Hardhat project for Solidity development
- Configure for Solidity version 0.8.17
- Install necessary dependencies:
- `@openzeppelin/contracts` (optional, for security patterns)
- `@nomiclabs/hardhat-ethers` for testing
- `@nomiclabs/hardhat-waffle` for testing
- Configure testing environment with Hardhat
#### 1.2 Project Structure
```
/contracts
/TicTacToe.sol
/test
/TicTacToe.test.js
/scripts
/deploy.js
/frontend (optional for demo)
/src
/components
/utils
hardhat.config.js
package.json
README.md
```
### 2. Smart Contract Development
#### 2.1 Contract Data Structures
Define the core data structures:
```solidity
// Game status enum
enum GameStatus { OPEN, IN_PROGRESS, WINNER_X, WINNER_O, DRAW }
// Game struct to track game state
struct Game {
address playerX; // Player 1 (X)
address playerO; // Player 2 (O)
address currentTurn; // Whose turn it is
uint8[9] board; // Board state (0=empty, 1=X, 2=O)
GameStatus status; // Current game status
uint256 wagerAmount; // ETH wagered (if any)
uint256 createdAt; // Timestamp for game creation
}
// Mapping to track all games
mapping(uint256 => Game) public games;
uint256 public gameCount;
```
#### 2.2 Core Game Functions
Implement these core functions:
1. **Game Creation**
```solidity
function createGame(bool withWager) external payable returns (uint256 gameId) {
// Input validation for wager
// Create new game with player X as msg.sender
// Set initial game state
// Emit GameCreated event
}
```
2. **Joining a Game**
```solidity
function joinGame(uint256 gameId) external payable {
// Validate game exists and is open
// Validate wager amount if needed
// Set player O as msg.sender
// Update game status to IN_PROGRESS
// Emit PlayerJoined event
}
```
3. **Making a Move**
```solidity
function makeMove(uint256 gameId, uint8 position) external {
// Validate game state and player turn
// Validate move is legal (position is in range and empty)
// Update board state
// Check for win conditions
// Update game status if game is complete
// Handle ETH distribution if game is complete and has wager
// Emit MoveExecuted event
}
```
4. **Game State Retrieval**
```solidity
function getGameState(uint256 gameId) external view returns (
address playerX,
address playerO,
address currentTurn,
uint8[9] memory board,
GameStatus status
) {
// Return all relevant game state information
}
```
#### 2.3 Win Condition Detection
Implement an efficient algorithm to check for wins:
```solidity
function checkWinner(uint8[9] memory board) internal pure returns (bool hasWinner, uint8 winner) {
// Check rows, columns, and diagonals for 3 in a row
// Return winner (1 for X, 2 for O) if found
// Otherwise return no winner
}
```
#### 2.4 ETH Handling
Handle wagering functionality:
```solidity
function _distributeWinnings(Game storage game) internal {
// Send ETH to winner or refund in case of draw
// Handle potential transfer failures safely
}
```
### 3. Testing Strategy
#### 3.1 Unit Tests
Write tests for:
- Game creation with and without wager
- Joining games
- Making valid and invalid moves
- Win detection for all winning positions
- Draw detection
- ETH distribution logic
#### 3.2 Gas Optimization
- Use uint8 for board positions (0, 1, 2)
- Optimize win condition checking
- Minimize storage operations
- Consider using bit manipulation for board state
### 4. Deployment and Documentation
#### 4.1 Deployment Scripts
Create scripts for:
- Testnet deployment
- Local testing deployment
#### 4.2 Documentation
Document:
- Contract functions and parameters
- Game mechanics and flow
- Integration guidelines for frontends
- Examples of interaction using ethers.js
#### 4.3 Frontend Demo (Optional)
If time permits, create a basic React frontend:
- Wallet connection
- Game creation interface
- Game board visualization
- Move submission
### 5. Implementation Timeline
#### Week 1: Smart Contract Development
- Day 1-2: Set up environment and implement data structures
- Day 3-4: Implement core game functions
- Day 5: Implement win condition detection and ETH handling
#### Week 2: Testing and Optimization
- Day 1-2: Write comprehensive tests
- Day 3-4: Gas optimization
- Day 5: Security review
#### Week 3: Deployment and Documentation
- Day 1-2: Create deployment scripts and deploy to testnet
- Day 3-4: Write documentation
- Day 5: (Optional) Create basic frontend demo
```
--------------------------------------------------------------------------------
/instructions/plan.md:
--------------------------------------------------------------------------------
```markdown
# Vibe-Coder MCP Server Implementation Plan
After examining the existing template MCP server and the provided documents, I'll outline a detailed implementation plan for creating the Vibe-Coder MCP server according to the PRD and Implementation documents.
## Overview
The Vibe-Coder MCP server will guide casual coders through a structured development process using features exposed via the Model Context Protocol. It will transform the existing notes system into a comprehensive development workflow tool, helping LLMs organize code development through clarification workflows, documentation generation, and phased implementation.
## Implementation Steps
### Step 1: Server Configuration and Core Structure
I'll start by modifying the existing MCP server to align with the Vibe-Coder requirements:
1. Update server metadata and capabilities
2. Define core data structures for features, PRDs, and implementation plans
3. Create in-memory storage for projects and their phases
```typescript
// Core data types
type Feature = {
id: string;
name: string;
description: string;
clarificationResponses: ClarificationResponse[];
prdDoc?: string;
implDoc?: string;
phases: Phase[];
};
type ClarificationResponse = {
question: string;
answer: string;
};
type Phase = {
id: string;
name: string;
description: string;
tasks: Task[];
status: "pending" | "in_progress" | "completed" | "reviewed";
};
type Task = {
description: string;
completed: boolean;
};
// In-memory storage
const features: { [id: string]: Feature } = {};
```
### Step 2: Feature Clarification Implementation
The clarification module will use MCP tools to engage users in iterative questioning:
1. Create a tool to initiate feature clarification
2. Implement a tool to receive clarification responses
3. Create a resource to retrieve clarification status
```typescript
// Tool for initiating clarification
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "start_feature_clarification") {
const featureName = String(request.params.arguments?.featureName || "");
const featureId = generateId();
features[featureId] = {
id: featureId,
name: featureName,
description: String(request.params.arguments?.initialDescription || ""),
clarificationResponses: [],
phases: []
};
// Return first clarification question
return {
content: [{
type: "text",
text: `Feature ID: ${featureId}\n\nLet's clarify your feature request. What specific problem does this feature solve?`
}]
};
}
// Handle other tools...
});
```
### Step 3: PRD and Implementation Plan Generation
Implement tools to generate documentation based on clarification responses:
1. Create a tool to generate the PRD document
2. Implement a tool to create the implementation plan
3. Add resources to access these documents
```typescript
// Tool for generating PRD
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "generate_prd") {
const featureId = String(request.params.arguments?.featureId);
const feature = features[featureId];
if (!feature) {
throw new Error("Feature not found");
}
// Generate PRD content based on clarification responses
const prdContent = generatePRD(feature);
feature.prdDoc = prdContent;
return {
content: [{
type: "text",
text: `PRD generated successfully for ${feature.name}. You can view it using the "feature_prd" resource.`
}]
};
}
// Similar implementation for implementation plan generation...
});
```
### Step 4: Phase Management Implementation
Create tools to manage development phases:
1. Implement a tool to create phases
2. Add a tool to update phase status
3. Create resources to view phase details
```typescript
// Tool for creating a new phase
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "create_phase") {
const featureId = String(request.params.arguments?.featureId);
const phaseName = String(request.params.arguments?.name);
const phaseDescription = String(request.params.arguments?.description);
const tasks = JSON.parse(String(request.params.arguments?.tasks || "[]"));
const feature = features[featureId];
if (!feature) {
throw new Error("Feature not found");
}
const phaseId = generateId();
feature.phases.push({
id: phaseId,
name: phaseName,
description: phaseDescription,
tasks: tasks.map((task: string) => ({ description: task, completed: false })),
status: "pending"
});
return {
content: [{
type: "text",
text: `Phase "${phaseName}" created with ID: ${phaseId}`
}]
};
}
// Handle other tools...
});
```
### Step 5: Resource Implementation
Implement resources to retrieve feature data, PRDs, and implementation plans:
1. Create resources for listing all features
2. Add resources for accessing feature details
3. Implement resources for retrieving PRDs and implementation plans
```typescript
// Handler for listing available features as resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "features://list",
mimeType: "text/plain",
name: "Features List",
description: "Lists all features being developed"
},
...Object.values(features).flatMap(feature => [
{
uri: `feature://${feature.id}`,
mimeType: "text/plain",
name: feature.name,
description: `Details about feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/prd`,
mimeType: "text/markdown",
name: `${feature.name} PRD`,
description: `PRD document for feature: ${feature.name}`
},
{
uri: `feature://${feature.id}/implementation`,
mimeType: "text/markdown",
name: `${feature.name} Implementation Plan`,
description: `Implementation plan for feature: ${feature.name}`
}
])
]
};
});
// Handler for reading feature resources
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const url = new URL(request.params.uri);
// Handle various resource types...
if (url.protocol === "features:") {
if (url.pathname === "/list") {
return {
contents: [{
uri: request.params.uri,
mimeType: "text/plain",
text: Object.values(features).map(f => `${f.id}: ${f.name}`).join("\n")
}]
};
}
}
if (url.protocol === "feature:") {
const parts = url.pathname.split('/').filter(Boolean);
const featureId = parts[0];
const feature = features[featureId];
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
if (parts.length === 1) {
// Return feature details
return {
contents: [{
uri: request.params.uri,
mimeType: "text/plain",
text: formatFeatureDetails(feature)
}]
};
}
if (parts[1] === "prd") {
return {
contents: [{
uri: request.params.uri,
mimeType: "text/markdown",
text: feature.prdDoc || "PRD not yet generated"
}]
};
}
// Handle other resource types...
}
throw new Error("Resource not found");
});
```
### Step 6: Tool Implementation
Expose the main tools for interacting with the Vibe-Coder MCP server:
```typescript
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "start_feature_clarification",
description: "Start the clarification process for a new feature",
inputSchema: {
type: "object",
properties: {
featureName: {
type: "string",
description: "Name of the feature"
},
initialDescription: {
type: "string",
description: "Initial description of the feature"
}
},
required: ["featureName"]
}
},
{
name: "provide_clarification",
description: "Provide answer to a clarification question",
inputSchema: {
type: "object",
properties: {
featureId: {
type: "string",
description: "ID of the feature"
},
question: {
type: "string",
description: "Clarification question"
},
answer: {
type: "string",
description: "Answer to the clarification question"
}
},
required: ["featureId", "question", "answer"]
}
},
{
name: "generate_prd",
description: "Generate a PRD document based on clarification responses",
inputSchema: {
type: "object",
properties: {
featureId: {
type: "string",
description: "ID of the feature"
}
},
required: ["featureId"]
}
},
{
name: "generate_implementation_plan",
description: "Generate an implementation plan document",
inputSchema: {
type: "object",
properties: {
featureId: {
type: "string",
description: "ID of the feature"
}
},
required: ["featureId"]
}
},
{
name: "create_phase",
description: "Create a new implementation phase",
inputSchema: {
type: "object",
properties: {
featureId: {
type: "string",
description: "ID of the feature"
},
name: {
type: "string",
description: "Name of the phase"
},
description: {
type: "string",
description: "Description of the phase"
},
tasks: {
type: "string",
description: "JSON array of task descriptions"
}
},
required: ["featureId", "name", "description"]
}
},
{
name: "update_phase_status",
description: "Update the status of a phase",
inputSchema: {
type: "object",
properties: {
featureId: {
type: "string",
description: "ID of the feature"
},
phaseId: {
type: "string",
description: "ID of the phase"
},
status: {
type: "string",
description: "New status (pending, in_progress, completed, reviewed)"
}
},
required: ["featureId", "phaseId", "status"]
}
},
{
name: "update_task_status",
description: "Mark a task as completed or not completed",
inputSchema: {
type: "object",
properties: {
featureId: {
type: "string",
description: "ID of the feature"
},
phaseId: {
type: "string",
description: "ID of the phase"
},
taskIndex: {
type: "number",
description: "Index of the task"
},
completed: {
type: "boolean",
description: "Whether the task is completed"
}
},
required: ["featureId", "phaseId", "taskIndex", "completed"]
}
}
]
};
});
```
### Step 7: Prompt Implementation
Create prompts to guide users through the development workflow:
```typescript
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "clarify_feature",
description: "Guide to clarify a feature request through questioning"
},
{
name: "generate_prd_template",
description: "Guide to generate a PRD document from clarifications"
},
{
name: "phase_implementation_guide",
description: "Guide for implementing a development phase"
}
]
};
});
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
if (request.params.name === "clarify_feature") {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: "Help me clarify this feature request by asking questions about:"
}
},
{
role: "user",
content: {
type: "text",
text: "1. The specific problem it solves\n2. The target users\n3. Key requirements\n4. Success criteria\n5. Technical constraints\n\nAsk one question at a time, analyze the response, then proceed to the next most relevant question."
}
}
]
};
}
// Handle other prompts...
});
```
## Helper Functions to Implement
Here are the core helper functions needed for the implementation:
```typescript
// Generate unique IDs
function generateId(): string {
return Math.random().toString(36).substring(2, 15);
}
// Format feature details for display
function formatFeatureDetails(feature: Feature): string {
return `
Feature: ${feature.name}
ID: ${feature.id}
Description: ${feature.description}
Clarification Responses:
${feature.clarificationResponses.map(cr => `Q: ${cr.question}\nA: ${cr.answer}`).join('\n\n')}
Phases (${feature.phases.length}):
${feature.phases.map(p => `- ${p.name} (${p.status}): ${p.tasks.filter(t => t.completed).length}/${p.tasks.length} tasks completed`).join('\n')}
`;
}
// Generate PRD document
function generatePRD(feature: Feature): string {
const clarifications = feature.clarificationResponses;
return `# ${feature.name} PRD
## 1. Introduction
${feature.description}
## 2. Feature Objectives
${extractObjectivesFromClarifications(clarifications)}
## 3. Scope and Requirements
${extractRequirementsFromClarifications(clarifications)}
## 4. High-Level Implementation Overview
To be determined based on the implementation plan.
## 5. Feedback and Iteration Process
This PRD will be updated as the implementation progresses and feedback is received.
`;
}
// Generate implementation plan
function generateImplementationPlan(feature: Feature): string {
// Similar to PRD generation, but creating an implementation plan
// ...
}
// Extract objectives from clarification responses
function extractObjectivesFromClarifications(responses: ClarificationResponse[]): string {
// Logic to extract objectives based on responses to clarification questions
// ...
}
// Extract requirements from clarification responses
function extractRequirementsFromClarifications(responses: ClarificationResponse[]): string {
// Logic to extract requirements based on responses to clarification questions
// ...
}
```
## File Structure
The implementation will be organized as follows:
```
src/
├── index.ts # Main server entry point
├── types.ts # Type definitions
├── storage.ts # Feature storage management
├── clarification.ts # Clarification workflow logic
├── documentation.ts # PRD and implementation plan generation
├── phases.ts # Phase and task management
├── utils.ts # Helper utilities
├── handlers/ # MCP request handlers
│ ├── resources.ts # Resource request handlers
│ ├── tools.ts # Tool request handlers
│ └── prompts.ts # Prompt request handlers
└── templates/ # Documentation templates
├── prd-template.md # PRD document template
└── impl-template.md # Implementation plan template
```
## Implementation Timeline
1. **Week 1**: Set up core server structure and data models
2. **Week 2**: Implement feature clarification workflow
3. **Week 3**: Build PRD and implementation plan generation
4. **Week 4**: Develop phase management functionality
5. **Week 5**: Complete resources and refinements
6. **Week 6**: Testing and documentation
## Next Steps
1. Start by modifying the existing server to match the Vibe-Coder requirements
2. Implement the core data structures and storage
3. Build feature clarification tools and resources
4. Add document generation capabilities
5. Implement phase management tools
6. Complete integration with MCP
```
--------------------------------------------------------------------------------
/src/mcp-server.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
/**
* @file Vibe-Coder MCP Server
* @version 0.3.0
* @status STABLE - DO NOT MODIFY WITHOUT TESTS
* @lastModified 2023-03-23
*
* This MCP server implements a structured development workflow that helps
* LLM-based coders build features in an organized, clean, and safe manner.
*
* IMPORTANT:
* - Test any modifications thoroughly
* - Maintain backward compatibility
*
* Functionality:
* - Feature request clarification through iterative questioning
* - PRD and implementation plan generation
* - Phased development with tasks and status tracking
*/
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Import core modules
import { Feature, Phase, Task, ClarificationResponse, PhaseStatus } from './types.js';
import { getFeature, updateFeature, listFeatures } from './storage.js';
import {
getNextClarificationQuestion,
addClarificationResponse,
formatClarificationResponses,
isClarificationComplete
} from './clarification.js';
import { generatePRD, generateImplementationPlan } from './documentation.js';
import { createFeatureObject, generateFeatureProgressSummary, createPhaseObject, createTaskObject, now } from './utils.js';
import { documentStorage, DocumentType } from './document-storage.js';
/**
* Create an MCP server
*/
const server = new McpServer({
name: "Vibe-Coder",
version: "0.3.0"
});
// --------- Helper Functions ---------
/**
* Create a new phase for a feature directly
*/
function createPhaseDirectly(feature: Feature, name: string, description: string): Phase {
const newPhase = createPhaseObject(name, description);
feature.phases.push(newPhase);
feature.updatedAt = new Date();
return newPhase;
}
/**
* Update phase status directly
*/
function updatePhaseStatusDirectly(feature: Feature, phaseId: string, status: PhaseStatus): void {
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
throw new Error(`Phase ${phaseId} not found`);
}
phase.status = status;
phase.updatedAt = now();
feature.updatedAt = now();
}
/**
* Add task directly
*/
function addTaskDirectly(feature: Feature, phaseId: string, description: string): Task {
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
throw new Error(`Phase ${phaseId} not found`);
}
const newTask = createTaskObject(description);
// Convert task ID to string to ensure it's not an object
if (typeof newTask.id !== 'string') {
newTask.id = String(newTask.id);
}
phase.tasks.push(newTask);
phase.updatedAt = now();
feature.updatedAt = now();
return newTask;
}
/**
* Update task status directly
*/
function updateTaskStatusDirectly(feature: Feature, phaseId: string, taskId: string, completed: boolean): void {
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
throw new Error(`Phase ${phaseId} not found`);
}
const task = phase.tasks.find(t => t.id === taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
task.completed = completed;
task.updatedAt = now();
phase.updatedAt = now();
feature.updatedAt = now();
}
// --------- Register Resources ---------
// Features list resource
server.resource(
"features-list",
"features://list",
async (uri) => ({
contents: [{
uri: uri.href,
text: listFeatures().map(f => `${f.id}: ${f.name}`).join("\n")
}]
})
);
// Features status resource
server.resource(
"features-status",
"features://status",
async (uri) => {
const features = listFeatures();
if (features.length === 0) {
return {
contents: [{
uri: uri.href,
text: "# Project Status\n\nNo features have been created yet."
}]
};
}
const featuresStatus = features.map(feature => {
const totalPhases = feature.phases.length;
const completedPhases = feature.phases.filter(p => p.status === 'completed' || p.status === 'reviewed').length;
const totalTasks = feature.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
const completedTasks = feature.phases.reduce(
(acc, phase) => acc + phase.tasks.filter(t => t.completed).length, 0
);
return `## ${feature.name}
- ID: ${feature.id}
- Status: ${completedPhases === totalPhases && totalPhases > 0 ? 'Completed' : 'In Progress'}
- Phases: ${completedPhases}/${totalPhases} completed
- Tasks: ${completedTasks}/${totalTasks} completed
- [View Details](feature://${feature.id}/progress)
`;
}).join('\n');
return {
contents: [{
uri: uri.href,
text: `# Project Status\n\n${featuresStatus}`
}]
};
}
);
// Feature detail resource with parameter
server.resource(
"feature-detail",
new ResourceTemplate("feature://{featureId}", { list: undefined }),
async (uri, { featureId }) => {
const feature = getFeature(featureId as string);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
const timestamp = feature.updatedAt.toISOString();
const clarifications = formatClarificationResponses(feature.clarificationResponses);
const phasesText = feature.phases.map(p =>
`- ${p.name} (${p.status}): ${p.tasks.filter(t => t.completed).length}/${p.tasks.length} tasks completed`
).join('\n');
const featureDetails = `
Feature: ${feature.name}
ID: ${feature.id}
Description: ${feature.description}
Last Updated: ${timestamp}
Clarification Responses:
${clarifications}
Phases (${feature.phases.length}):
${phasesText}
`;
return {
contents: [{
uri: uri.href,
text: featureDetails
}]
};
}
);
// Feature progress resource
server.resource(
"feature-progress",
new ResourceTemplate("feature://{featureId}/progress", { list: undefined }),
async (uri, { featureId }) => {
const feature = getFeature(featureId as string);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
const progressReport = generateFeatureProgressSummary(feature);
return {
contents: [{
uri: uri.href,
text: progressReport
}]
};
}
);
// Feature PRD resource
server.resource(
"feature-prd",
new ResourceTemplate("feature://{featureId}/prd", { list: undefined }),
async (uri, { featureId }) => {
const feature = getFeature(featureId as string);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
if (!isClarificationComplete(feature)) {
return {
contents: [{
uri: uri.href,
text: "# PRD Not Available\n\nThe clarification process is not complete. Please answer all clarification questions first."
}]
};
}
const prd = generatePRD(feature);
return {
contents: [{
uri: uri.href,
text: prd
}]
};
}
);
// Feature implementation plan resource
server.resource(
"feature-implementation-plan",
new ResourceTemplate("feature://{featureId}/implementation", { list: undefined }),
async (uri, { featureId }) => {
const feature = getFeature(featureId as string);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
if (!isClarificationComplete(feature)) {
return {
contents: [{
uri: uri.href,
text: "# Implementation Plan Not Available\n\nThe clarification process is not complete. Please answer all clarification questions first."
}]
};
}
const implementationPlan = generateImplementationPlan(feature);
return {
contents: [{
uri: uri.href,
text: implementationPlan
}]
};
}
);
// --------- Register Tools ---------
// Start feature clarification tool
server.tool(
"start_feature_clarification",
{
featureName: z.string().min(2).max(100),
initialDescription: z.string().optional().default("")
},
async ({ featureName, initialDescription }) => {
// Create a new feature
const feature = createFeatureObject(featureName, initialDescription);
updateFeature(feature.id, feature);
// Get the first clarification question
const firstQuestion = getNextClarificationQuestion(feature);
return {
content: [{
type: "text",
text: `Feature ID: ${feature.id}\n\nLet's clarify your feature request. ${firstQuestion}`
}]
};
}
);
// Provide clarification tool
server.tool(
"provide_clarification",
{
featureId: z.string().min(1),
question: z.string().min(1),
answer: z.string().min(1)
},
async ({ featureId, question, answer }) => {
// Get the feature
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
// Add the clarification response to the feature
feature.clarificationResponses.push({
question,
answer,
timestamp: new Date()
});
// Save the feature with the updated clarification response
updateFeature(featureId, feature);
// Get the next question or indicate all questions are answered
const nextQuestion = getNextClarificationQuestion(feature);
if (nextQuestion) {
return {
content: [{
type: "text",
text: `Response recorded. ${nextQuestion}`
}]
};
} else {
// All questions answered
return {
content: [{
type: "text",
text: "All clarification questions have been answered. You can now generate a PRD for this feature."
}]
};
}
}
);
// Generate PRD tool
server.tool(
"generate_prd",
{
featureId: z.string().min(1)
},
async ({ featureId }) => {
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
if (!isClarificationComplete(feature)) {
throw new Error("Clarification process not complete. Please answer all clarification questions.");
}
const prd = generatePRD(feature);
feature.prd = prd;
updateFeature(featureId, feature);
return {
content: [{
type: "text",
text: `PRD generated successfully for "${feature.name}". You can view it at feature://${featureId}/prd`
}]
};
}
);
// Create phase tool
server.tool(
"create_phase",
{
featureId: z.string().min(1),
name: z.string().min(1),
description: z.string().min(1)
},
async ({ featureId, name, description }) => {
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
const phase = createPhaseDirectly(feature, name, description);
updateFeature(featureId, feature);
return {
content: [{
type: "text",
text: `Created phase "${name}" with ID ${phase.id} for feature "${feature.name}"`
}]
};
}
);
// Update phase status tool
server.tool(
"update_phase_status",
{
featureId: z.string().min(1),
phaseId: z.string().min(1),
status: z.enum(["pending", "in_progress", "completed", "reviewed"])
},
async ({ featureId, phaseId, status }) => {
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
throw new Error(`Phase ${phaseId} not found in feature ${featureId}`);
}
const oldStatus = phase.status;
updatePhaseStatusDirectly(feature, phaseId, status);
updateFeature(featureId, feature);
return {
content: [{
type: "text",
text: `Updated phase "${phase.name}" status from "${oldStatus}" to "${status}"`
}]
};
}
);
// Add task tool
server.tool(
"add_task",
{
featureId: z.string().min(1),
phaseId: z.string().min(1),
description: z.string().min(1)
},
async ({ featureId, phaseId, description }) => {
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
throw new Error(`Phase ${phaseId} not found in feature ${featureId}`);
}
const task = addTaskDirectly(feature, phaseId, description);
updateFeature(featureId, feature);
// Debug log
console.error(`DEBUG: Task created with ID: ${task.id}, type: ${typeof task.id}`);
console.error(`DEBUG: Task object: ${JSON.stringify(task)}`);
// Ensure task.id is explicitly converted to string and check if it's an object
let taskId: string;
if (typeof task.id === 'object') {
taskId = JSON.stringify(task.id);
} else {
taskId = String(task.id);
}
return {
content: [{
type: "text",
text: `Added task "${description.substring(0, 30)}${description.length > 30 ? '...' : ''}" with ID: ${taskId} to phase "${phase.name}"`
}]
};
}
);
// Update task status tool
server.tool(
"update_task_status",
{
featureId: z.string().min(1),
phaseId: z.string().min(1),
taskId: z.string().min(1),
completed: z.boolean()
},
async ({ featureId, phaseId, taskId, completed }) => {
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
const phase = feature.phases.find(p => p.id === phaseId);
if (!phase) {
throw new Error(`Phase ${phaseId} not found in feature ${featureId}`);
}
const task = phase.tasks.find(t => t.id === taskId);
if (!task) {
throw new Error(`Task ${taskId} not found in phase ${phaseId}`);
}
updateTaskStatusDirectly(feature, phaseId, taskId, completed);
updateFeature(featureId, feature);
return {
content: [{
type: "text",
text: `Updated task "${task.description.substring(0, 30)}${task.description.length > 30 ? '...' : ''}" status to ${completed ? 'completed' : 'not completed'}`
}]
};
}
);
// Get next phase action tool
server.tool(
"get_next_phase_action",
{
featureId: z.string().min(1)
},
async ({ featureId }) => {
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
// Check if we need implementation plan first
if (!feature.implementationPlan) {
return {
content: [{
type: "text",
text: "You should generate an implementation plan before creating phases."
}]
};
}
// If no phases, suggest creating first phase
if (feature.phases.length === 0) {
return {
content: [{
type: "text",
text: "You should create the first development phase based on the implementation plan."
}]
};
}
// Find the current active phase (the first non-completed phase)
const currentPhase = feature.phases.find(p => p.status !== 'completed' && p.status !== 'reviewed');
if (!currentPhase) {
return {
content: [{
type: "text",
text: "All phases are complete. You should mark the final phase as reviewed."
}]
};
}
// Check if all tasks in the phase are completed
const allTasksCompleted = currentPhase.tasks.every(t => t.completed);
if (currentPhase.tasks.length === 0) {
return {
content: [{
type: "text",
text: `Current phase "${currentPhase.name}" has no tasks. You should add tasks based on the implementation plan.`
}]
};
} else if (!allTasksCompleted) {
const pendingTasks = currentPhase.tasks.filter(t => !t.completed);
return {
content: [{
type: "text",
text: `Current phase "${currentPhase.name}" has ${pendingTasks.length} incomplete tasks. Complete these tasks before moving to the next phase.`
}]
};
} else {
return {
content: [{
type: "text",
text: `All tasks in the current phase "${currentPhase.name}" are complete. You can now mark this phase as completed and proceed to the next phase.`
}]
};
}
}
);
// Define prompt for feature planning
server.prompt(
"feature-planning",
{
featureId: z.string()
},
({ featureId }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `I need to plan the development of feature with ID ${featureId}. Please help me:
1. Understand what the feature is about
2. Break down the feature into development phases
3. Create tasks for each phase
4. Track progress through completion`
}
}]
})
);
// Document path tool
server.tool(
"get_document_path",
{
featureId: z.string().min(1),
documentType: z.string().min(1)
},
async ({ featureId, documentType }) => {
try {
// Check if the feature exists
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
// Map the string to DocumentType enum
let docType: DocumentType;
if (documentType === 'prd') {
docType = DocumentType.PRD;
} else if (documentType === 'implementation-plan') {
docType = DocumentType.IMPLEMENTATION_PLAN;
} else {
throw new Error(`Invalid document type: ${documentType}. Expected 'prd' or 'implementation-plan'`);
}
// Check if the document exists
if (!documentStorage.hasDocument(feature.id, docType)) {
throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`);
}
// Get the default file path for the document
const filePath = documentStorage.getDefaultFilePath(feature.id, docType);
// Get the document to check if it's been saved
const document = documentStorage.getDocument(feature.id, docType);
return {
content: [{
type: "text",
text: `Document path: ${filePath}\nSaved to disk: ${document?.metadata.isSaved ? 'Yes' : 'No'}`
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error retrieving document path: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Document save tool
server.tool(
"save_document",
{
featureId: z.string().min(1),
documentType: z.string().min(1),
filePath: z.string().min(1).optional()
},
async ({ featureId, documentType, filePath }) => {
try {
// Check if the feature exists
const feature = getFeature(featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
// Map the string to DocumentType enum
let docType: DocumentType;
if (documentType === 'prd') {
docType = DocumentType.PRD;
} else if (documentType === 'implementation-plan') {
docType = DocumentType.IMPLEMENTATION_PLAN;
} else {
throw new Error(`Invalid document type: ${documentType}. Expected 'prd' or 'implementation-plan'`);
}
// Check if the document exists
if (!documentStorage.hasDocument(feature.id, docType)) {
throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`);
}
let savedPath: string;
// If a custom path was provided, use it; otherwise, save to the default path
if (filePath) {
savedPath = await documentStorage.saveDocumentToCustomPath(feature.id, docType, filePath);
} else {
savedPath = await documentStorage.saveDocumentToFile(feature.id, docType);
}
return {
content: [{
type: "text",
text: `Document saved successfully to: ${savedPath}`
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error saving document: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Start the server
async function main() {
console.error("Starting Vibe-Coder MCP Server...");
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(error => {
console.error("Error in Vibe-Coder MCP Server:", error);
process.exit(1);
});
```