This is page 1 of 4. Use http://codebase.md/crazyrabbitltc/mcp-vibecoder?lines=true&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:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Vibe-Coder MCP Server
2 |
3 | A Model Context Protocol server that implements a structured development workflow for LLM-based coding.
4 |
5 | ## Overview
6 |
7 | This MCP server helps LLMs build features in an organized, clean, and safe manner by providing:
8 |
9 | - A structured feature clarification process with guided questions
10 | - PRD and implementation plan generation
11 | - Phased development with task tracking
12 | - Progress tracking and status reporting
13 | - Document storage and retrieval capabilities
14 |
15 | ## Features
16 |
17 | ### Resources
18 | - Feature details, PRDs, and implementation plans
19 | - Progress reports and status tracking
20 | - Phase and task details
21 |
22 | ### Tools
23 | - `start_feature_clarification` - Begin the feature clarification process
24 | - `provide_clarification` - Answer clarification questions about a feature
25 | - `generate_prd` - Generate a Product Requirements Document and implementation plan
26 | - `create_phase` - Create a development phase for a feature
27 | - `add_task` - Add tasks to a development phase
28 | - `update_phase_status` - Update the status of a phase
29 | - `update_task_status` - Update the completion status of a task
30 | - `get_next_phase_action` - Get guidance on what to do next
31 | - `get_document_path` - Get the path of a generated document
32 | - `save_document` - Save a document to a specific location
33 |
34 | ### Prompts
35 | - `feature-planning` - A prompt template for planning feature development
36 |
37 | ## Document Storage
38 |
39 | The server includes a hybrid document storage system that:
40 |
41 | 1. Automatically saves generated documents (PRDs, implementation plans) to files
42 | 2. Maintains an in-memory copy for quick access
43 | 3. Allows clients to retrieve document paths and save to custom locations
44 |
45 | ### Default Storage Location
46 |
47 | Documents are stored in the `documents/{featureId}/` directory by default, with filenames based on document type:
48 |
49 | - `documents/{featureId}/prd.md` - Product Requirements Document
50 | - `documents/{featureId}/implementation-plan.md` - Implementation Plan
51 |
52 | ### Custom Storage
53 |
54 | You can use the `save_document` tool to save documents to custom locations:
55 |
56 | ```json
57 | {
58 | "featureId": "feature-123",
59 | "documentType": "prd",
60 | "filePath": "/custom/path/feature-123-prd.md"
61 | }
62 | ```
63 |
64 | ### Path Retrieval
65 |
66 | To get the path of a document, use the `get_document_path` tool:
67 |
68 | ```json
69 | {
70 | "featureId": "feature-123",
71 | "documentType": "prd"
72 | }
73 | ```
74 |
75 | This returns both the path and whether the document has been saved to disk.
76 |
77 | ## Development
78 |
79 | Install dependencies:
80 | ```bash
81 | npm install
82 | ```
83 |
84 | Build the server:
85 | ```bash
86 | npm run build
87 | ```
88 |
89 | For development with auto-rebuild:
90 | ```bash
91 | npm run watch
92 | ```
93 |
94 | ## Installation
95 |
96 | To use with compatible MCP clients:
97 |
98 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
99 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
100 |
101 | ```json
102 | {
103 | "mcpServers": {
104 | "vibe-coder-mcp": {
105 | "command": "/path/to/vibe-coder-mcp/build/mcp-server.js"
106 | }
107 | }
108 | }
109 | ```
110 |
111 | ### Debugging
112 |
113 | 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:
114 |
115 | ```bash
116 | npm run inspector
117 | ```
118 |
119 | The Inspector will provide a URL to access debugging tools in your browser.
120 |
121 | ## Implementation Notes
122 |
123 | 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.
124 |
125 | ```typescript
126 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
127 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
128 |
129 | // Create an MCP server
130 | const server = new McpServer({
131 | name: "Vibe-Coder",
132 | version: "0.3.0"
133 | });
134 |
135 | // Add a resource
136 | server.resource(
137 | "features-list",
138 | "features://list",
139 | async (uri) => ({ /* ... */ })
140 | );
141 |
142 | // Add a tool
143 | server.tool(
144 | "start_feature_clarification",
145 | { /* parameters schema */ },
146 | async (params) => ({ /* ... */ })
147 | );
148 |
149 | // Add a prompt
150 | server.prompt(
151 | "feature-planning",
152 | { /* parameters schema */ },
153 | (params) => ({ /* ... */ })
154 | );
155 |
156 | // Start the server
157 | const transport = new StdioServerTransport();
158 | await server.connect(transport);
159 | ```
160 |
161 | ## Workflow
162 |
163 | The Vibe-Coder MCP server is designed to guide the development process through the following steps:
164 |
165 | 1. **Feature Clarification**: Start by gathering requirements and understanding the feature's purpose, target users, and constraints
166 | 2. **Documentation**: Generate a PRD and implementation plan based on the clarified requirements
167 | 3. **Phased Development**: Break down the implementation into logical phases with clear tasks
168 | 4. **Progress Tracking**: Monitor the completion of tasks and phases to guide development
169 | 5. **Completion**: Verify that all requirements have been implemented and the feature is ready for use
```
--------------------------------------------------------------------------------
/documents/test-feature-custom.md:
--------------------------------------------------------------------------------
```markdown
1 | # Test Feature - PRD
2 |
3 | This is a test PRD document.
```
--------------------------------------------------------------------------------
/documents/test-feature/prd.md:
--------------------------------------------------------------------------------
```markdown
1 | # Test Feature - PRD
2 |
3 | This is a test PRD document.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/src/templates/impl-template.md:
--------------------------------------------------------------------------------
```markdown
1 | # {{featureName}} Implementation Plan
2 |
3 | ## Overview
4 |
5 | {{featureDescription}}
6 |
7 | ## Implementation Steps
8 |
9 | {{#each phases}}
10 | ### Phase {{phaseNumber}}: {{phaseName}}
11 |
12 | #### Objectives
13 | {{objectives}}
14 |
15 | #### Tasks
16 | {{#each tasks}}
17 | - [ ] {{description}}
18 | {{/each}}
19 |
20 | #### Code Style & Practices
21 | {{codeStyle}}
22 |
23 | {{/each}}
24 |
25 | ## File Structure
26 |
27 | ```
28 | {{fileStructure}}
29 | ```
30 |
31 | ## Implementation Timeline
32 |
33 | {{#each phases}}
34 | {{phaseNumber}}. **{{phaseName}}**: {{timeEstimate}}
35 | {{/each}}
36 |
37 | ## Next Steps
38 |
39 | 1. {{nextStep1}}
40 | 2. {{nextStep2}}
41 | 3. {{nextStep3}}
42 | 4. {{nextStep4}}
43 | 5. {{nextStep5}}
```
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": [
14 | "src/mcp-server.ts",
15 | "src/types.ts",
16 | "src/storage.ts",
17 | "src/utils.ts",
18 | "src/clarification.ts",
19 | "src/documentation.ts",
20 | "src/document-storage.ts"
21 | ],
22 | "exclude": [
23 | "node_modules",
24 | "src/registry.ts",
25 | "src/resource-handlers.ts",
26 | "src/tool-handlers.ts",
27 | "src/index.ts",
28 | "src/index-updated.ts"
29 | ]
30 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "vibe-coder-mcp",
3 | "version": "0.3.0",
4 | "description": "A custom MCP for vibecoding",
5 | "private": true,
6 | "type": "module",
7 | "bin": {
8 | "vibe-coder-mcp": "./build/mcp-server.js"
9 | },
10 | "files": [
11 | "build"
12 | ],
13 | "scripts": {
14 | "build": "tsc --project tsconfig.build.json && node -e \"require('fs').chmodSync('build/mcp-server.js', '755')\"",
15 | "watch": "tsc --project tsconfig.build.json --watch",
16 | "inspector": "npx @modelcontextprotocol/inspector build/mcp-server.js"
17 | },
18 | "dependencies": {
19 | "@modelcontextprotocol/sdk": "1.7.0",
20 | "zod": "^3.22.4"
21 | },
22 | "devDependencies": {
23 | "@types/node": "^20.11.24",
24 | "typescript": "^5.3.3"
25 | }
26 | }
27 |
```
--------------------------------------------------------------------------------
/debug.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | // This file is just for inspecting the features state
4 | // Since we can't access the runtime state directly,
5 | // we'll examine how the clarification functions work
6 |
7 | import { getNextClarificationQuestion, DEFAULT_CLARIFICATION_QUESTIONS } from './build/clarification.js';
8 |
9 | console.log('Default questions:', DEFAULT_CLARIFICATION_QUESTIONS);
10 |
11 | // Test the function with a mock feature
12 | const mockFeature = {
13 | id: 'test',
14 | clarificationResponses: []
15 | };
16 |
17 | console.log('Initial question:', getNextClarificationQuestion(mockFeature));
18 |
19 | // Add a response and check next question
20 | mockFeature.clarificationResponses.push({
21 | question: DEFAULT_CLARIFICATION_QUESTIONS[0],
22 | answer: 'Test answer',
23 | timestamp: new Date()
24 | });
25 |
26 | console.log('After 1 response, next question:', getNextClarificationQuestion(mockFeature));
27 |
28 | // Add another response
29 | mockFeature.clarificationResponses.push({
30 | question: DEFAULT_CLARIFICATION_QUESTIONS[1],
31 | answer: 'Test answer 2',
32 | timestamp: new Date()
33 | });
34 |
35 | console.log('After 2 responses, next question:', getNextClarificationQuestion(mockFeature));
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Core data types for the Vibe-Coder MCP Server
3 | */
4 |
5 | /**
6 | * Represents a feature being developed
7 | */
8 | export type Feature = {
9 | id: string;
10 | name: string;
11 | description: string;
12 | clarificationResponses: ClarificationResponse[];
13 | prd?: string;
14 | prdDoc?: string;
15 | implementationPlan?: string;
16 | implDoc?: string;
17 | phases: Phase[];
18 | createdAt: Date;
19 | updatedAt: Date;
20 | };
21 |
22 | /**
23 | * Represents a question and answer pair from the clarification process
24 | */
25 | export type ClarificationResponse = {
26 | question: string;
27 | answer: string;
28 | timestamp: Date;
29 | };
30 |
31 | /**
32 | * Represents a development phase within a feature
33 | */
34 | export type Phase = {
35 | id: string;
36 | name: string;
37 | description: string;
38 | tasks: Task[];
39 | status: PhaseStatus;
40 | createdAt: Date;
41 | updatedAt: Date;
42 | };
43 |
44 | /**
45 | * Possible statuses for a development phase
46 | */
47 | export type PhaseStatus = "pending" | "in_progress" | "completed" | "reviewed";
48 |
49 | /**
50 | * Represents a task within a development phase
51 | */
52 | export type Task = {
53 | id: string;
54 | description: string;
55 | completed: boolean;
56 | createdAt: Date;
57 | updatedAt: Date;
58 | };
59 |
60 | /**
61 | * Storage interface for features
62 | */
63 | export interface FeatureStorage {
64 | [id: string]: Feature;
65 | }
```
--------------------------------------------------------------------------------
/src/templates/prd-template.md:
--------------------------------------------------------------------------------
```markdown
1 | # {{featureName}} PRD
2 |
3 | ## 1. Introduction
4 |
5 | {{featureDescription}}
6 |
7 | ## 2. Feature Objectives
8 |
9 | {{objectives}}
10 |
11 | ## 3. Scope and Requirements
12 |
13 | ### 3.1 Feature Request Clarification
14 | {{requirements}}
15 |
16 | ### 3.2 Technical Specifications
17 | {{technicalSpecs}}
18 |
19 | ## 4. High-Level Implementation Overview
20 |
21 | ### 4.1 Development Workflow and Branching Strategy
22 | - **Feature Branch:**
23 | - Begin implementation on a dedicated branch named `feature/{{featureNameSlug}}`.
24 | - **Phase Branches:**
25 | - For each phase, create a branch off the feature branch named `phase/{{featureNameSlug}}/{{phaseNumber}}-{{phaseName}}`.
26 |
27 | ### 4.2 Implementation Phases
28 | {{implementationPhases}}
29 |
30 | ## 5. Feedback and Iteration Process
31 |
32 | - **User Feedback Integration:**
33 | - Regularly incorporate user feedback to refine feature objectives and requirements.
34 | - **Iterative Updates:**
35 | - Update both the PRD and the implementation plan as new details emerge during the development process.
36 | - **Review and Approval:**
37 | - Ensure that each phase is reviewed and approved before moving to subsequent phases.
38 |
39 | ## 6. File Naming Conventions
40 | - **PRD File:** `{{stepNumber}}_{{featureName}}_PRD.md`
41 | - **Implementation Plan File:** `{{stepNumber}}_{{featureName}}_Implementation.md`
```
--------------------------------------------------------------------------------
/src/storage.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * In-memory storage for features and related data.
3 | */
4 | import { Feature, FeatureStorage } from './types.js';
5 |
6 | /**
7 | * In-memory storage for features
8 | */
9 | export const features: FeatureStorage = {};
10 |
11 | /**
12 | * Add a new feature to storage
13 | * @param feature The feature to store
14 | * @returns The stored feature
15 | */
16 | export function storeFeature(feature: Feature): Feature {
17 | features[feature.id] = feature;
18 | return feature;
19 | }
20 |
21 | /**
22 | * Retrieve a feature by ID
23 | * @param id The feature ID
24 | * @returns The feature or undefined if not found
25 | */
26 | export function getFeature(id: string): Feature | undefined {
27 | return features[id];
28 | }
29 |
30 | /**
31 | * Update an existing feature
32 | * @param id The feature ID
33 | * @param updatedFields Fields to update on the feature
34 | * @returns The updated feature or undefined if not found
35 | */
36 | export function updateFeature(
37 | id: string,
38 | updatedFields: Partial<Omit<Feature, 'id' | 'createdAt'>>
39 | ): Feature | undefined {
40 | const feature = features[id];
41 |
42 | if (!feature) {
43 | return undefined;
44 | }
45 |
46 | // Update the feature with new fields
47 | const updatedFeature = {
48 | ...feature,
49 | ...updatedFields,
50 | updatedAt: new Date()
51 | };
52 |
53 | features[id] = updatedFeature;
54 | return updatedFeature;
55 | }
56 |
57 | /**
58 | * List all features
59 | * @returns Array of all features
60 | */
61 | export function listFeatures(): Feature[] {
62 | return Object.values(features);
63 | }
64 |
65 | /**
66 | * Delete a feature by ID
67 | * @param id The feature ID
68 | * @returns True if deleted, false if not found
69 | */
70 | export function deleteFeature(id: string): boolean {
71 | if (features[id]) {
72 | delete features[id];
73 | return true;
74 | }
75 | return false;
76 | }
```
--------------------------------------------------------------------------------
/instructions/checklist.md:
--------------------------------------------------------------------------------
```markdown
1 | # Vibe-Coder MCP Server Implementation Checklist
2 |
3 | ## Step 1: Server Configuration and Core Structure
4 | - [x] Update server metadata and name
5 | - [x] Define core data types (Feature, ClarificationResponse, Phase, Task)
6 | - [x] Create in-memory storage for features and projects
7 | - [x] Set up directory structure and file organization
8 | - [x] Implement basic utilities (generateId, etc.)
9 |
10 | ## Step 2: Feature Clarification Implementation
11 | - [x] Create tool for initiating feature clarification
12 | - [x] Implement tool for receiving clarification responses
13 | - [x] Create resource to retrieve clarification status
14 | - [x] Add support for structured questioning workflow
15 |
16 | ## Step 3: PRD and Implementation Plan Generation
17 | - [x] Create tool for generating PRD document
18 | - [x] Implement tool for creating implementation plan
19 | - [x] Add helper functions for extracting requirements from clarifications
20 | - [x] Implement markdown formatting for documentation generation
21 | - [x] Create template structure for PRDs and implementation plans
22 |
23 | ## Step 4: Phase Management Implementation
24 | - [x] Implement tool for creating phases
25 | - [x] Add tool for updating phase status
26 | - [x] Create tool for managing tasks within phases
27 | - [x] Implement phase workflow progression logic
28 |
29 | ## Step 5: Resource Implementation
30 | - [x] Create resources for listing all features
31 | - [x] Add resources for accessing feature details
32 | - [x] Implement resources for retrieving PRDs
33 | - [x] Create resources for retrieving implementation plans
34 | - [x] Add support for phase and task status viewing
35 |
36 | ## Step 6: Tool Implementation
37 | - [x] Define all tool schemas and validations
38 | - [x] Implement error handling for tools
39 | - [x] Ensure proper argument validation
40 | - [x] Add support for tool discoverability
41 | - [x] Test tool invocations with example data
42 |
43 | ## Step 7: Prompt Implementation
44 | - [ ] Create clarify_feature prompt
45 | - [ ] Implement generate_prd_template prompt
46 | - [ ] Add phase_implementation_guide prompt
47 | - [ ] Ensure prompts are well-structured for LLM use
48 |
49 | ## Testing and Documentation
50 | - [ ] Test each tool individually
51 | - [ ] Test complete workflows end-to-end
52 | - [ ] Create example usage documentation
53 | - [ ] Add README with setup and usage instructions
```
--------------------------------------------------------------------------------
/instructions/mcp_sdk_improvements.md:
--------------------------------------------------------------------------------
```markdown
1 | # Implementation Plan for MCP SDK Improvements
2 |
3 | ## 1. Replace Switch Statement with Tool Handler Map
4 |
5 | ### Tasks:
6 | - [x] Create a tool handler type definition in `types.ts`
7 | - [x] Create a tools registry object that maps tool names to handler functions
8 | - [x] Refactor the `CallToolRequestSchema` handler to use this registry
9 | - [x] Add validation and error handling for unknown tools
10 | - [x] Update tool registration to automatically populate the registry
11 | - [ ] Test all tools to ensure functionality remains the same
12 |
13 | ## 2. Implement Robust URI Parsing
14 |
15 | ### Tasks:
16 | - [x] Create a `ResourceRegistry` class that handles URI templates and matching
17 | - [x] Update resource definitions to use proper URI templates
18 | - [x] Implement pattern matching for incoming resource URIs
19 | - [x] Refactor `ReadResourceRequestSchema` handler to use the registry
20 | - [x] Add validation and error handling for malformed URIs
21 | - [ ] Test with various URI patterns to ensure correct matching
22 |
23 | ## 3. Improve Error Handling Consistency
24 |
25 | ### Tasks:
26 | - [x] Define standard error response types following JSON-RPC 2.0 spec
27 | - [x] Create utility functions for generating consistent error responses
28 | - [x] Update all error returns to use these utility functions
29 | - [x] Ensure error codes are appropriate and consistent
30 | - [x] Add more detailed error messages with context
31 | - [x] Improve validation error reporting
32 | - [ ] Test error scenarios to verify consistent responses
33 |
34 | ## 4. Use More Specific Types and Reduce Type Assertions
35 |
36 | ### Tasks:
37 | - [x] Create Zod schemas for core data structures (Feature, Phase, Task)
38 | - [x] Implement branded types for identifiers (FeatureId, PhaseId, TaskId)
39 | - [x] Replace string types with more specific types where appropriate
40 | - [ ] Eliminate `@ts-ignore` comments by fixing underlying type issues
41 | - [x] Add runtime type validation at critical boundaries
42 | - [x] Update function signatures to use the new types
43 | - [ ] Test type safety with various inputs
44 |
45 | ## Implementation Order and Dependencies
46 |
47 | 1. **First Phase**: Type improvements (foundation for other changes) ✅
48 | - Implement specific types and Zod schemas ✅
49 | - Remove type assertions where possible ✅
50 |
51 | 2. **Second Phase**: Error handling improvements ✅
52 | - Create error utilities ✅
53 | - Update error responses to use common format ✅
54 |
55 | 3. **Third Phase**: Resource URI handling ✅
56 | - Implement resource registry ✅
57 | - Update URI parsing and matching ✅
58 |
59 | 4. **Fourth Phase**: Tool handler refactoring ✅
60 | - Create tools registry ✅
61 | - Refactor call tool request handling ✅
62 |
63 | ## Next Steps
64 |
65 | 1. Testing strategy:
66 | - Create unit tests for each component
67 | - Test edge cases and error scenarios
68 | - Compare outputs before and after changes to ensure consistency
69 |
70 | 2. Create a PR with the changes
71 | - Update the documentation
72 | - Add examples of using the new APIs
```
--------------------------------------------------------------------------------
/test-document-storage.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * @file test-document-storage.js
5 | *
6 | * Simple test script for the document storage functionality.
7 | * Run with: node test-document-storage.js
8 | */
9 |
10 | import { documentStorage, DocumentType } from './build/document-storage.js';
11 | import * as fs from 'fs/promises';
12 | import * as path from 'path';
13 |
14 | // Test feature ID
15 | const featureId = 'test-feature';
16 |
17 | /**
18 | * Check if a file exists
19 | * @param {string} filePath Path to check
20 | * @returns {Promise<boolean>} True if file exists
21 | */
22 | async function fileExists(filePath) {
23 | try {
24 | await fs.access(filePath);
25 | return true;
26 | } catch {
27 | return false;
28 | }
29 | }
30 |
31 | /**
32 | * Run document storage tests
33 | */
34 | async function runTests() {
35 | console.log('=== Document Storage Test ===\n');
36 |
37 | try {
38 | // Create documents directory if it doesn't exist
39 | const testDirPath = path.join(process.cwd(), 'documents', featureId);
40 | try {
41 | await fs.mkdir(testDirPath, { recursive: true });
42 | } catch (err) {
43 | // Ignore if directory already exists
44 | }
45 |
46 | // Test 1: Store a PRD document
47 | console.log('Test 1: Storing PRD document');
48 | const prdContent = `# Test Feature - PRD\n\nThis is a test PRD document.`;
49 | const prdDocument = await documentStorage.storeDocument(featureId, DocumentType.PRD, prdContent);
50 | console.log(`- Document stored in memory: ${Boolean(prdDocument)}`);
51 | console.log(`- Document metadata:`, prdDocument.metadata);
52 | console.log(`- Document saved to disk: ${prdDocument.metadata.isSaved}`);
53 |
54 | // Test 2: Retrieve the document
55 | console.log('\nTest 2: Retrieving document');
56 | const retrievedDoc = documentStorage.getDocument(featureId, DocumentType.PRD);
57 | console.log(`- Document retrieved: ${Boolean(retrievedDoc)}`);
58 | console.log(`- Document content preview: ${retrievedDoc?.content.substring(0, 30)}...`);
59 |
60 | // Test 3: Save to custom path
61 | console.log('\nTest 3: Saving to custom path');
62 | const customPath = path.join(process.cwd(), 'documents', `${featureId}-custom.md`);
63 | try {
64 | const savedPath = await documentStorage.saveDocumentToCustomPath(
65 | featureId,
66 | DocumentType.PRD,
67 | customPath
68 | );
69 | console.log(`- Document saved to custom path: ${savedPath}`);
70 |
71 | // Check if the file was created
72 | const fileExistsResult = await fileExists(customPath);
73 | console.log(`- Custom file exists: ${fileExistsResult}`);
74 | } catch (error) {
75 | console.error(`- Error saving to custom path: ${error.message}`);
76 | }
77 |
78 | // Test 4: Path validation
79 | console.log('\nTest 4: Path validation');
80 | try {
81 | // Try to save outside the documents directory (should fail)
82 | const invalidPath = path.join(process.cwd(), '..', 'invalid-path.md');
83 | await documentStorage.saveDocumentToCustomPath(
84 | featureId,
85 | DocumentType.PRD,
86 | invalidPath
87 | );
88 | console.log('- Path validation FAILED: Allowed path outside documents directory');
89 | } catch (error) {
90 | console.log(`- Path validation successful: ${error.message}`);
91 | }
92 |
93 | console.log('\n=== Tests completed successfully ===');
94 | } catch (error) {
95 | console.error('\nTest failed with error:', error);
96 | }
97 | }
98 |
99 | // Run the tests
100 | runTests().catch(error => {
101 | console.error('Unhandled error:', error);
102 | process.exit(1);
103 | });
```
--------------------------------------------------------------------------------
/src/schemas.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @file schemas.ts
3 | * @version 1.0.0
4 | *
5 | * Provides Zod schemas and branded types for the Vibe-Coder MCP Server.
6 | * These schemas enable runtime validation and improve type safety across the codebase.
7 | */
8 |
9 | import { z } from 'zod';
10 | import { PhaseStatus } from './types.js';
11 |
12 | // --------- Branded Types for IDs ---------
13 |
14 | /**
15 | * Branded type for Feature IDs
16 | */
17 | export type FeatureId = string & { readonly _brand: unique symbol };
18 |
19 | /**
20 | * Branded type for Phase IDs
21 | */
22 | export type PhaseId = string & { readonly _brand: unique symbol };
23 |
24 | /**
25 | * Branded type for Task IDs
26 | */
27 | export type TaskId = string & { readonly _brand: unique symbol };
28 |
29 | /**
30 | * Type guards for branded types
31 | */
32 | export const isFeatureId = (id: string): id is FeatureId => id.startsWith('feature-');
33 | export const isPhaseId = (id: string): id is PhaseId => id.startsWith('phase-');
34 | export const isTaskId = (id: string): id is TaskId => id.startsWith('task-');
35 |
36 | /**
37 | * Brand creator functions
38 | */
39 | export const createFeatureId = (id: string): FeatureId => {
40 | if (!isFeatureId(id)) throw new Error(`Invalid feature ID format: ${id}`);
41 | return id as FeatureId;
42 | };
43 |
44 | export const createPhaseId = (id: string): PhaseId => {
45 | if (!isPhaseId(id)) throw new Error(`Invalid phase ID format: ${id}`);
46 | return id as PhaseId;
47 | };
48 |
49 | export const createTaskId = (id: string): TaskId => {
50 | if (!isTaskId(id)) throw new Error(`Invalid task ID format: ${id}`);
51 | return id as TaskId;
52 | };
53 |
54 | // --------- Core Zod Schemas ---------
55 |
56 | /**
57 | * Schema for ClarificationResponse
58 | */
59 | export const ClarificationResponseSchema = z.object({
60 | question: z.string().min(1, "Question cannot be empty"),
61 | answer: z.string().min(1, "Answer cannot be empty"),
62 | timestamp: z.date(),
63 | });
64 |
65 | /**
66 | * Schema for Task
67 | */
68 | export const TaskSchema = z.object({
69 | id: z.string().refine(isTaskId, "Invalid task ID format"),
70 | description: z.string().min(3, "Task description must be at least 3 characters"),
71 | completed: z.boolean(),
72 | createdAt: z.date(),
73 | updatedAt: z.date(),
74 | });
75 |
76 | /**
77 | * Schema for PhaseStatus
78 | */
79 | export const PhaseStatusSchema = z.enum(["pending", "in_progress", "completed", "reviewed"]);
80 |
81 | /**
82 | * Schema for Phase
83 | */
84 | export const PhaseSchema = z.object({
85 | id: z.string().refine(isPhaseId, "Invalid phase ID format"),
86 | name: z.string().min(2, "Phase name must be at least 2 characters").max(100, "Phase name must be at most 100 characters"),
87 | description: z.string(),
88 | tasks: z.array(TaskSchema),
89 | status: PhaseStatusSchema,
90 | createdAt: z.date(),
91 | updatedAt: z.date(),
92 | });
93 |
94 | /**
95 | * Schema for Feature
96 | */
97 | export const FeatureSchema = z.object({
98 | id: z.string().refine(isFeatureId, "Invalid feature ID format"),
99 | name: z.string().min(2, "Feature name must be at least 2 characters").max(100, "Feature name must be at most 100 characters"),
100 | description: z.string(),
101 | clarificationResponses: z.array(ClarificationResponseSchema),
102 | prd: z.string().optional(),
103 | prdDoc: z.string().optional(),
104 | implementationPlan: z.string().optional(),
105 | implDoc: z.string().optional(),
106 | phases: z.array(PhaseSchema),
107 | createdAt: z.date(),
108 | updatedAt: z.date(),
109 | });
110 |
111 | // --------- Type Inference ---------
112 |
113 | /**
114 | * Zod-inferred types to ensure schema and type compatibility
115 | */
116 | export type ZodFeature = z.infer<typeof FeatureSchema>;
117 | export type ZodPhase = z.infer<typeof PhaseSchema>;
118 | export type ZodTask = z.infer<typeof TaskSchema>;
119 | export type ZodClarificationResponse = z.infer<typeof ClarificationResponseSchema>;
120 |
121 | /**
122 | * Validation functions for inputs
123 | */
124 | export const validateFeature = (feature: unknown): ZodFeature => {
125 | return FeatureSchema.parse(feature);
126 | };
127 |
128 | export const validatePhase = (phase: unknown): ZodPhase => {
129 | return PhaseSchema.parse(phase);
130 | };
131 |
132 | export const validateTask = (task: unknown): ZodTask => {
133 | return TaskSchema.parse(task);
134 | };
135 |
136 | export const validateClarificationResponse = (response: unknown): ZodClarificationResponse => {
137 | return ClarificationResponseSchema.parse(response);
138 | };
```
--------------------------------------------------------------------------------
/src/errors.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @file errors.ts
3 | * @version 1.0.0
4 | *
5 | * Provides standardized error handling utilities for the Vibe-Coder MCP Server.
6 | * These utilities ensure consistent error responses following the JSON-RPC 2.0 specification.
7 | */
8 |
9 | import { z } from 'zod';
10 |
11 | // --------- Error Codes ---------
12 |
13 | /**
14 | * Standard JSON-RPC 2.0 error codes
15 | * https://www.jsonrpc.org/specification#error_object
16 | */
17 | export enum ErrorCode {
18 | // JSON-RPC 2.0 Standard Error Codes
19 | PARSE_ERROR = -32700,
20 | INVALID_REQUEST = -32600,
21 | METHOD_NOT_FOUND = -32601,
22 | INVALID_PARAMS = -32602,
23 | INTERNAL_ERROR = -32603,
24 |
25 | // Custom Error Codes (must be between -32000 and -32099)
26 | VALIDATION_ERROR = -32000,
27 | RESOURCE_NOT_FOUND = -32001,
28 | TOOL_NOT_FOUND = -32002,
29 | PROMPT_NOT_FOUND = -32003,
30 | UNAUTHORIZED = -32004,
31 | FEATURE_NOT_FOUND = -32010,
32 | PHASE_NOT_FOUND = -32011,
33 | TASK_NOT_FOUND = -32012,
34 | INVALID_PHASE_TRANSITION = -32013,
35 | CLARIFICATION_INCOMPLETE = -32014,
36 | }
37 |
38 | // --------- Error Responses ---------
39 |
40 | /**
41 | * Standard JSON-RPC 2.0 error response structure
42 | */
43 | export type ErrorResponse = {
44 | error: {
45 | code: ErrorCode;
46 | message: string;
47 | data?: any;
48 | }
49 | };
50 |
51 | /**
52 | * Tool error response
53 | */
54 | export type ToolErrorResponse = {
55 | content: [{
56 | type: "text";
57 | text: string;
58 | }];
59 | isError?: boolean;
60 | };
61 |
62 | /**
63 | * Create a standard error response for JSON-RPC 2.0
64 | */
65 | export const createErrorResponse = (
66 | code: ErrorCode,
67 | message: string,
68 | data?: any
69 | ): ErrorResponse => {
70 | return {
71 | error: {
72 | code,
73 | message,
74 | data
75 | }
76 | };
77 | };
78 |
79 | /**
80 | * Create a tool error response
81 | */
82 | export const createToolErrorResponse = (
83 | message: string,
84 | details?: any
85 | ): ToolErrorResponse => {
86 | return {
87 | content: [{
88 | type: "text",
89 | text: `Error: ${message}`
90 | }],
91 | isError: true
92 | };
93 | };
94 |
95 | /**
96 | * Capture and format Zod validation errors
97 | */
98 | export const handleZodError = (error: z.ZodError): ErrorResponse => {
99 | const formattedErrors = error.errors.map(err => ({
100 | path: err.path.join('.'),
101 | message: err.message
102 | }));
103 |
104 | return createErrorResponse(
105 | ErrorCode.VALIDATION_ERROR,
106 | "Validation error",
107 | formattedErrors
108 | );
109 | };
110 |
111 | /**
112 | * Handle feature not found errors
113 | */
114 | export const featureNotFoundError = (featureId: string): ToolErrorResponse => {
115 | return createToolErrorResponse(`Feature with ID ${featureId} not found`);
116 | };
117 |
118 | /**
119 | * Handle phase not found errors
120 | */
121 | export const phaseNotFoundError = (
122 | phaseId: string,
123 | featureName: string
124 | ): ToolErrorResponse => {
125 | return createToolErrorResponse(
126 | `Phase with ID ${phaseId} not found in feature ${featureName}`
127 | );
128 | };
129 |
130 | /**
131 | * Handle task not found errors
132 | */
133 | export const taskNotFoundError = (
134 | taskId: string,
135 | phaseName: string
136 | ): ToolErrorResponse => {
137 | return createToolErrorResponse(
138 | `Task with ID ${taskId} not found in phase ${phaseName}`
139 | );
140 | };
141 |
142 | /**
143 | * Handle invalid phase transition errors
144 | */
145 | export const invalidPhaseTransitionError = (
146 | currentStatus: string,
147 | newStatus: string
148 | ): ToolErrorResponse => {
149 | return createToolErrorResponse(
150 | `Cannot transition phase from "${currentStatus}" to "${newStatus}"`
151 | );
152 | };
153 |
154 | /**
155 | * Handle clarification incomplete errors
156 | */
157 | export const clarificationIncompleteError = (
158 | status: any
159 | ): ToolErrorResponse => {
160 | return createToolErrorResponse(
161 | `Cannot proceed until clarification is complete`,
162 | { clarificationStatus: status }
163 | );
164 | };
165 |
166 | /**
167 | * Try-catch wrapper for Zod validation
168 | */
169 | export const tryValidate = <T>(
170 | schema: z.ZodType<T>,
171 | data: unknown
172 | ): { success: true; data: T } | { success: false; error: ErrorResponse } => {
173 | try {
174 | return { success: true, data: schema.parse(data) };
175 | } catch (error) {
176 | if (error instanceof z.ZodError) {
177 | return { success: false, error: handleZodError(error) };
178 | }
179 | return {
180 | success: false,
181 | error: createErrorResponse(
182 | ErrorCode.INTERNAL_ERROR,
183 | error instanceof Error ? error.message : "Unknown error"
184 | )
185 | };
186 | }
187 | };
```
--------------------------------------------------------------------------------
/test-mcp.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | import { spawn } from 'child_process';
4 | import { createInterface } from 'readline';
5 |
6 | console.log('Starting Vibe-Coder MCP server...');
7 | // Start the MCP server
8 | const serverProcess = spawn('node', ['./build/index.js']);
9 |
10 | // Create readline interface to read server output
11 | const rl = createInterface({
12 | input: serverProcess.stdout,
13 | output: process.stdout,
14 | terminal: false
15 | });
16 |
17 | // Set up error handling
18 | serverProcess.stderr.on('data', (data) => {
19 | console.error(`Server stderr: ${data.toString()}`);
20 | });
21 |
22 | // Wait for server to start
23 | setTimeout(() => {
24 | console.log('Sending initialize request...');
25 |
26 | // First, send the initialize request
27 | const initializeMsg = {
28 | jsonrpc: "2.0",
29 | id: "init1",
30 | method: "initialize",
31 | params: {
32 | client: {
33 | name: "Test Client",
34 | version: "1.0.0"
35 | },
36 | capabilities: {
37 | resources: {},
38 | tools: {},
39 | prompts: {}
40 | }
41 | }
42 | };
43 |
44 | serverProcess.stdin.write(JSON.stringify(initializeMsg) + '\n');
45 |
46 | // Wait for response and send clarification
47 | let featureId = null;
48 | let questionIndex = 0;
49 |
50 | rl.on('line', (line) => {
51 | try {
52 | console.log('Raw server output:', line);
53 | const response = JSON.parse(line);
54 | console.log('Parsed response:', JSON.stringify(response, null, 2));
55 |
56 | // After initialization, list available tools
57 | if (response.id === "init1") {
58 | console.log('Initialization successful, listing tools...');
59 | const listToolsMsg = {
60 | jsonrpc: "2.0",
61 | id: "list1",
62 | method: "listTools",
63 | params: {}
64 | };
65 | serverProcess.stdin.write(JSON.stringify(listToolsMsg) + '\n');
66 | }
67 |
68 | // After listing tools, call the start_feature_clarification tool
69 | if (response.id === "list1") {
70 | console.log('Tools listed, starting feature clarification...');
71 | const callToolMsg = {
72 | jsonrpc: "2.0",
73 | id: "call1",
74 | method: "callTool",
75 | params: {
76 | name: "start_feature_clarification",
77 | arguments: {
78 | featureName: "Test Automated Feature",
79 | initialDescription: "This is a test automated feature for testing the clarification flow"
80 | }
81 | }
82 | };
83 | serverProcess.stdin.write(JSON.stringify(callToolMsg) + '\n');
84 | }
85 |
86 | // Check if this is a response to start_feature_clarification
87 | if (response.id === "call1" && response.result?.content) {
88 | // Extract feature ID from response text
89 | const text = response.result.content[0].text;
90 | const match = text.match(/Feature ID: ([a-z0-9]+)/);
91 |
92 | if (match && match[1]) {
93 | featureId = match[1];
94 | console.log(`Extracted feature ID: ${featureId}`);
95 |
96 | // Send first clarification
97 | sendClarification(featureId, questionIndex, "This is an answer to question 0");
98 | }
99 | }
100 |
101 | // Check if this is a response to any of the clarification messages
102 | if (response.id && response.id.startsWith("clarify") && response.result?.content) {
103 | const text = response.result.content[0].text;
104 | console.log('Clarification response text:', text);
105 |
106 | // Check if we need to send the next question
107 | if (text.startsWith("Response recorded.")) {
108 | questionIndex++;
109 | if (questionIndex < 7) { // We have 7 default questions
110 | setTimeout(() => {
111 | sendClarification(featureId, questionIndex, `This is an answer to question ${questionIndex}`);
112 | }, 500);
113 | } else {
114 | console.log('All questions answered, test complete');
115 | serverProcess.kill();
116 | process.exit(0);
117 | }
118 | }
119 | }
120 | } catch (e) {
121 | console.error('Error parsing JSON response:', e);
122 | }
123 | });
124 |
125 | function sendClarification(featureId, index, answer) {
126 | const clarifyMsg = {
127 | jsonrpc: "2.0",
128 | id: `clarify${index}`,
129 | method: "callTool",
130 | params: {
131 | name: "provide_clarification",
132 | arguments: {
133 | featureId: featureId,
134 | question: `Question #${index}`,
135 | answer: answer
136 | }
137 | }
138 | };
139 | console.log(`Sending clarification ${index}...`);
140 | serverProcess.stdin.write(JSON.stringify(clarifyMsg) + '\n');
141 | }
142 | }, 1000);
143 |
144 | // Handle process exit
145 | process.on('exit', () => {
146 | serverProcess.kill();
147 | });
```
--------------------------------------------------------------------------------
/src/clarification.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Feature clarification module for the Vibe-Coder MCP Server.
3 | * This module handles the iterative questioning to clarify feature requests.
4 | */
5 | import { Feature, ClarificationResponse } from './types.js';
6 | import { updateFeature, getFeature } from './storage.js';
7 | import { now } from './utils.js';
8 |
9 | /**
10 | * Default questions to ask during feature clarification
11 | */
12 | export const DEFAULT_CLARIFICATION_QUESTIONS = [
13 | "What specific problem does this feature solve?",
14 | "Who are the target users for this feature?",
15 | "What are the key requirements for this feature?",
16 | "What are the technical constraints or considerations?",
17 | "How will we measure the success of this feature?",
18 | "Are there any dependencies on other features or systems?",
19 | "What are the potential risks or challenges in implementing this feature?"
20 | ];
21 |
22 | /**
23 | * Result when all clarification questions are complete
24 | */
25 | export interface ClarificationComplete {
26 | done: true;
27 | message: string;
28 | }
29 |
30 | /**
31 | * Get the next clarification question for a feature
32 | * @param feature The feature to get the next question for
33 | * @returns The next question to ask or a completion object if all questions have been answered
34 | * @throws Error if the feature is missing the clarificationResponses array
35 | */
36 | export function getNextClarificationQuestion(feature: Feature): string | ClarificationComplete {
37 | if (!feature.clarificationResponses) {
38 | // Throw a proper error instead of just logging
39 | throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
40 | }
41 |
42 | // Check if we've asked all the default questions
43 | if (feature.clarificationResponses.length >= DEFAULT_CLARIFICATION_QUESTIONS.length) {
44 | return {
45 | done: true,
46 | message: "All clarification questions have been answered. You can now generate a PRD for this feature."
47 | };
48 | }
49 |
50 | // Get the next question based on the number of responses
51 | return DEFAULT_CLARIFICATION_QUESTIONS[feature.clarificationResponses.length];
52 | }
53 |
54 | /**
55 | * Add a clarification response to a feature
56 | * @param featureId The ID of the feature to add the response to
57 | * @param question The question that was asked
58 | * @param answer The user's answer
59 | * @returns The updated feature
60 | * @throws Error if the feature is not found
61 | */
62 | export function addClarificationResponse(
63 | featureId: string,
64 | question: string,
65 | answer: string
66 | ): Feature {
67 | const feature = getFeature(featureId);
68 | if (!feature) {
69 | throw new Error(`Feature with ID ${featureId} not found`);
70 | }
71 |
72 | // Create a new response
73 | const newResponse: ClarificationResponse = {
74 | question,
75 | answer,
76 | timestamp: now()
77 | };
78 |
79 | // Update the feature with the new response
80 | const updatedFeature = updateFeature(featureId, {
81 | clarificationResponses: [...feature.clarificationResponses, newResponse]
82 | });
83 |
84 | if (!updatedFeature) {
85 | throw new Error(`Failed to update feature ${featureId} with new clarification response`);
86 | }
87 |
88 | return updatedFeature;
89 | }
90 |
91 | /**
92 | * Format clarification responses as text
93 | * @param responses The responses to format
94 | * @returns Formatted text
95 | */
96 | export function formatClarificationResponses(responses: ClarificationResponse[]): string {
97 | if (!responses || responses.length === 0) {
98 | return "No clarification responses yet.";
99 | }
100 |
101 | return responses.map(cr => `Q: ${cr.question}\nA: ${cr.answer}`).join('\n\n');
102 | }
103 |
104 | /**
105 | * Check if a feature has completed the clarification process
106 | * @param feature The feature to check
107 | * @returns True if clarification is complete, false otherwise
108 | * @throws Error if the feature is missing the clarificationResponses array
109 | */
110 | export function isClarificationComplete(feature: Feature): boolean {
111 | if (!feature.clarificationResponses) {
112 | throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
113 | }
114 |
115 | return feature.clarificationResponses.length >= DEFAULT_CLARIFICATION_QUESTIONS.length;
116 | }
117 |
118 | /**
119 | * Get a summary of the clarification status
120 | * @param feature The feature to get the status for
121 | * @returns A text summary of the clarification status
122 | * @throws Error if the feature is missing the clarificationResponses array
123 | */
124 | export function getClarificationStatus(feature: Feature): string {
125 | if (!feature.clarificationResponses) {
126 | throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
127 | }
128 |
129 | const total = DEFAULT_CLARIFICATION_QUESTIONS.length;
130 | const completed = feature.clarificationResponses.length;
131 |
132 | return `Clarification progress: ${completed}/${total} questions answered.`;
133 | }
```
--------------------------------------------------------------------------------
/instructions/mcp_server_improvements.md:
--------------------------------------------------------------------------------
```markdown
1 | # Implementation Plan: MCP Server Improvements
2 |
3 | ## Phase 1: Critical Fixes (High Priority)
4 |
5 | ### Task 1: Fix Resource Registry Import
6 | - [x] Update `index-updated.ts` to import `resourceRegistry` at the top of the file
7 | - [x] Remove the `require` call from the `ReadResourceRequestSchema` handler
8 | - [x] Test the handler to ensure it works properly with the imported registry
9 |
10 | ### Task 2: Improve Error Handling
11 | - [x] Modify `documentation.ts` to throw proper errors when template loading fails
12 | - [x] Update `clarification.ts` to throw specific errors and return completion objects
13 | - [x] Add try/catch blocks where necessary to handle these new errors properly
14 | - [ ] Test the error handling with invalid inputs
15 |
16 | ## Phase 2: Architecture Improvements (Medium Priority)
17 |
18 | ### Task 1: Enhance Tool Registry with Metadata
19 | - [x] Modify the `ToolRegistry` class in `registry.ts` to store and manage tool metadata
20 | - [x] Add `listTools()` method to return all registered tools with their metadata
21 | - [x] Update the `register` method to accept description and input schema
22 | - [x] Modify `index-updated.ts` to use `toolRegistry.listTools()` in the handler
23 |
24 | ### Task 2: Implement Self-Registration Pattern
25 | - [ ] Create a new file `tool-registry.ts` to define the tool registration process
26 | - [ ] Modify each tool handler file to self-register on import
27 | - [ ] Remove the centralized `registerToolHandlers` function
28 | - [ ] Repeat the same pattern for resource handlers
29 | - [ ] Test that all tools and resources are properly registered
30 |
31 | ## Phase 3: Enhance Testing Framework (Medium Priority)
32 |
33 | ### Task 1: Set Up Jest Testing Framework
34 | - [ ] Install Jest and related dependencies
35 | - [ ] Configure Jest for TypeScript testing
36 | - [ ] Create a basic test setup file with server startup/shutdown
37 |
38 | ### Task 2: Migrate Existing Tests to Jest
39 | - [ ] Convert the `test-mcp-improved.js` to use Jest's test structure
40 | - [ ] Replace timeouts with proper async/await and promises
41 | - [ ] Add assertions to verify responses
42 |
43 | ### Task 3: Add Comprehensive Tests
44 | - [ ] Add tests for error cases and edge conditions
45 | - [ ] Test URI matching with various patterns
46 | - [ ] Test tool execution with valid and invalid inputs
47 | - [ ] Test resource handling with various URIs
48 |
49 | ## Phase 4: Additional Improvements (Lower Priority)
50 |
51 | ### Task 1: Implement Better Templating
52 | - [ ] Research and select an appropriate templating engine
53 | - [ ] Update `documentation.ts` to use the selected templating engine
54 | - [ ] Create improved templates with proper variable handling
55 |
56 | ### Task 2: Ensure Consistent Code Style
57 | - [ ] Apply consistent naming conventions throughout the codebase
58 | - [ ] Add JSDoc comments to all functions and classes
59 | - [ ] Set up a code formatter like Prettier
60 |
61 | ## Implementation Steps
62 |
63 | ### For Phase 1:
64 | 1. Fix Resource Registry Import: ✅
65 | ```typescript
66 | // At the top of index-updated.ts
67 | import { toolRegistry, resourceRegistry } from './registry.js';
68 |
69 | // In the ReadResourceRequestSchema handler
70 | const match = resourceRegistry.findMatch(uri);
71 | ```
72 |
73 | 2. Improve Error Handling: ✅
74 | ```typescript
75 | // In documentation.ts
76 | function loadTemplate(templateName: string): string {
77 | try {
78 | return fs.readFileSync(templatePath, 'utf-8');
79 | } catch (error) {
80 | throw new Error(`Failed to load template ${templateName}: ${error}`);
81 | }
82 | }
83 |
84 | // In clarification.ts
85 | export function getNextClarificationQuestion(feature: Feature): string | { done: true } {
86 | if (!feature.clarificationResponses) {
87 | throw new Error(`Feature is missing clarificationResponses array: ${feature.id}`);
88 | }
89 |
90 | if (feature.clarificationResponses.length >= DEFAULT_CLARIFICATION_QUESTIONS.length) {
91 | return { done: true };
92 | }
93 |
94 | return DEFAULT_CLARIFICATION_QUESTIONS[feature.clarificationResponses.length];
95 | }
96 | ```
97 |
98 | ### For Phase 2:
99 | 1. Enhance Tool Registry: ✅
100 | ```typescript
101 | // In registry.ts
102 | export class ToolRegistry {
103 | private handlers: Map<string, ToolHandler> = new Map();
104 | private toolMetadata: Map<string, { description: string; inputSchema: any }> = new Map();
105 |
106 | register<T>(
107 | name: string,
108 | handler: ToolHandler<T>,
109 | description: string,
110 | inputSchema: any
111 | ): void {
112 | this.handlers.set(name, handler);
113 | this.toolMetadata.set(name, { description, inputSchema });
114 | }
115 |
116 | // ... existing methods ...
117 |
118 | listTools(): { name: string; description: string; inputSchema: any }[] {
119 | return Array.from(this.handlers.keys()).map((name) => {
120 | const metadata = this.toolMetadata.get(name);
121 | return {
122 | name,
123 | description: metadata?.description || "No description provided.",
124 | inputSchema: metadata?.inputSchema || {},
125 | };
126 | });
127 | }
128 | }
129 |
130 | // In index-updated.ts
131 | server.setRequestHandler(ListToolsRequestSchema, async () => {
132 | return {
133 | tools: toolRegistry.listTools(),
134 | };
135 | });
136 | ```
137 |
138 | ## Timeline and Dependencies
139 |
140 | 1. Phase 1 (1-2 days): ✅
141 | - Resource registry fix: 0.5 day ✅
142 | - Error handling improvements: 1-1.5 days ✅
143 | - No dependencies
144 |
145 | 2. Phase 2 (2-3 days): 🔄
146 | - Tool registry enhancements: 1-1.5 days ✅
147 | - Self-registration pattern: 1-1.5 days 🔄
148 | - Depends on Phase 1 completion ✅
```
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @file utils.ts
3 | * @version 1.1.0
4 | *
5 | * Utility functions for the Vibe-Coder MCP Server
6 | */
7 |
8 | import { Feature, Phase, Task, PhaseStatus } from './types.js';
9 | import { FeatureId, PhaseId, TaskId, createFeatureId, createPhaseId, createTaskId } from './schemas.js';
10 |
11 | /**
12 | * Generate a unique ID for features with proper prefix
13 | * @returns A feature ID string
14 | */
15 | export function generateFeatureId(): FeatureId {
16 | const randomPart = Math.random().toString(36).substring(2, 10);
17 | return createFeatureId(`feature-${randomPart}`);
18 | }
19 |
20 | /**
21 | * Generate a unique ID for phases with proper prefix
22 | * @returns A phase ID string
23 | */
24 | export function generatePhaseId(): PhaseId {
25 | const randomPart = Math.random().toString(36).substring(2, 10);
26 | return createPhaseId(`phase-${randomPart}`);
27 | }
28 |
29 | /**
30 | * Generate a unique ID for tasks with proper prefix
31 | * @returns A task ID string
32 | */
33 | export function generateTaskId(): TaskId {
34 | const randomPart = Math.random().toString(36).substring(2, 10);
35 | return createTaskId(`task-${randomPart}`);
36 | }
37 |
38 | /**
39 | * Legacy ID generator for backward compatibility
40 | * @deprecated Use the typed ID generators instead
41 | * @returns A random string ID
42 | */
43 | export function generateId(): string {
44 | return Math.random().toString(36).substring(2, 15) +
45 | Math.random().toString(36).substring(2, 15);
46 | }
47 |
48 | /**
49 | * Format a date to ISO string without milliseconds
50 | * @param date The date to format
51 | * @returns Formatted date string
52 | */
53 | export function formatDate(date: Date): string {
54 | return date.toISOString().split('.')[0] + 'Z';
55 | }
56 |
57 | /**
58 | * Create a new timestamp
59 | * @returns Current date
60 | */
61 | export function now(): Date {
62 | return new Date();
63 | }
64 |
65 | /**
66 | * Create a default feature object with the given name and description
67 | * @param name The feature name
68 | * @param description The feature description
69 | * @returns A new feature object
70 | */
71 | export function createFeatureObject(name: string, description: string = ""): Feature {
72 | const timestamp = now();
73 |
74 | return {
75 | id: generateFeatureId(),
76 | name,
77 | description,
78 | clarificationResponses: [],
79 | phases: [],
80 | createdAt: timestamp,
81 | updatedAt: timestamp
82 | };
83 | }
84 |
85 | /**
86 | * Create a default phase object with the given name and description
87 | * @param name The phase name
88 | * @param description The phase description
89 | * @returns A new phase object
90 | */
91 | export function createPhaseObject(name: string, description: string): Phase {
92 | const timestamp = now();
93 |
94 | return {
95 | id: generatePhaseId(),
96 | name,
97 | description,
98 | tasks: [],
99 | status: "pending" as PhaseStatus,
100 | createdAt: timestamp,
101 | updatedAt: timestamp
102 | };
103 | }
104 |
105 | /**
106 | * Create a default task object with the given description
107 | * @param description The task description
108 | * @returns A new task object
109 | */
110 | export function createTaskObject(description: string): Task {
111 | const timestamp = now();
112 |
113 | return {
114 | id: generateTaskId(),
115 | description,
116 | completed: false,
117 | createdAt: timestamp,
118 | updatedAt: timestamp
119 | };
120 | }
121 |
122 | /**
123 | * Validate if a status is a valid phase status
124 | * @param status The status to validate
125 | * @returns True if valid, false otherwise
126 | */
127 | export function isValidPhaseStatus(status: string): boolean {
128 | return ['pending', 'in_progress', 'completed', 'reviewed'].includes(status);
129 | }
130 |
131 | /**
132 | * Generate a detailed progress summary for a feature
133 | * @param feature The feature to generate a summary for
134 | * @returns A formatted string with the feature's progress details
135 | */
136 | export function generateFeatureProgressSummary(feature: Feature): string {
137 | const totalPhases = feature.phases.length;
138 | const completedPhases = feature.phases.filter(p => p.status === 'completed' || p.status === 'reviewed').length;
139 | const inProgressPhases = feature.phases.filter(p => p.status === 'in_progress').length;
140 |
141 | const totalTasks = feature.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
142 | const completedTasks = feature.phases.reduce(
143 | (acc, phase) => acc + phase.tasks.filter(t => t.completed).length, 0
144 | );
145 |
146 | const phaseProgress = totalPhases > 0
147 | ? Math.round((completedPhases / totalPhases) * 100)
148 | : 0;
149 |
150 | const taskProgress = totalTasks > 0
151 | ? Math.round((completedTasks / totalTasks) * 100)
152 | : 0;
153 |
154 | let summary = `
155 | # Feature Progress: ${feature.name}
156 |
157 | ## Overview
158 | - Feature ID: ${feature.id}
159 | - Created: ${feature.createdAt.toISOString()}
160 | - Last Updated: ${feature.updatedAt.toISOString()}
161 | - Description: ${feature.description}
162 |
163 | ## Progress Summary
164 | - Phases: ${completedPhases}/${totalPhases} completed (${phaseProgress}%)
165 | - Tasks: ${completedTasks}/${totalTasks} completed (${taskProgress}%)
166 | - Phases in Progress: ${inProgressPhases}
167 |
168 | ## Phase Details
169 | `;
170 |
171 | if (totalPhases === 0) {
172 | summary += "\nNo phases defined for this feature yet.";
173 | } else {
174 | feature.phases.forEach(phase => {
175 | const phaseTasks = phase.tasks.length;
176 | const phaseCompletedTasks = phase.tasks.filter(t => t.completed).length;
177 | const phaseTaskProgress = phaseTasks > 0
178 | ? Math.round((phaseCompletedTasks / phaseTasks) * 100)
179 | : 0;
180 |
181 | summary += `
182 | ### ${phase.name} (${phase.status})
183 | - ID: ${phase.id}
184 | - Progress: ${phaseCompletedTasks}/${phaseTasks} tasks (${phaseTaskProgress}%)
185 | - Description: ${phase.description}
186 |
187 | Tasks:
188 | ${phase.tasks.map(task => `- [${task.completed ? 'x' : ' '}] ${task.description}`).join('\n')}
189 | `;
190 | });
191 | }
192 |
193 | return summary;
194 | }
```
--------------------------------------------------------------------------------
/src/validators.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @file validators.ts
3 | * @version 1.0.0
4 | *
5 | * Provides validation utilities for the Vibe-Coder MCP Server tools.
6 | * These validators ensure that tool inputs are valid and provide consistent
7 | * error handling across all tools.
8 | */
9 |
10 | import { Feature, Phase, Task, PhaseStatus } from './types.js';
11 | import { getFeature } from './storage.js';
12 | import { isValidPhaseStatus } from './utils.js';
13 |
14 | /**
15 | * Standard validation result type
16 | */
17 | export type ValidationResult = {
18 | valid: boolean;
19 | message: string;
20 | data?: any; // Optional validated data
21 | };
22 |
23 | /**
24 | * Validate feature ID and return the feature if valid
25 | * @param featureId The feature ID to validate
26 | * @returns Validation result with feature in data if valid
27 | */
28 | export function validateFeatureId(featureId: string): ValidationResult {
29 | // Check if feature ID is provided
30 | if (!featureId || featureId.trim() === '') {
31 | return {
32 | valid: false,
33 | message: 'Feature ID is required'
34 | };
35 | }
36 |
37 | // Check if feature exists
38 | const feature = getFeature(featureId);
39 | if (!feature) {
40 | return {
41 | valid: false,
42 | message: `Feature with ID ${featureId} not found`
43 | };
44 | }
45 |
46 | return {
47 | valid: true,
48 | message: 'Feature is valid',
49 | data: feature
50 | };
51 | }
52 |
53 | /**
54 | * Validate phase ID in the context of a feature
55 | * @param feature The feature containing the phase
56 | * @param phaseId The phase ID to validate
57 | * @returns Validation result with phase in data if valid
58 | */
59 | export function validatePhaseId(feature: Feature, phaseId: string): ValidationResult {
60 | // Check if phase ID is provided
61 | if (!phaseId || phaseId.trim() === '') {
62 | return {
63 | valid: false,
64 | message: 'Phase ID is required'
65 | };
66 | }
67 |
68 | // Check if phase exists in feature
69 | const phase = feature.phases.find(p => p.id === phaseId);
70 | if (!phase) {
71 | return {
72 | valid: false,
73 | message: `Phase with ID ${phaseId} not found in feature ${feature.name}`
74 | };
75 | }
76 |
77 | return {
78 | valid: true,
79 | message: 'Phase is valid',
80 | data: phase
81 | };
82 | }
83 |
84 | /**
85 | * Validate task ID in the context of a phase
86 | * @param phase The phase containing the task
87 | * @param taskId The task ID to validate
88 | * @returns Validation result with task in data if valid
89 | */
90 | export function validateTaskId(phase: Phase, taskId: string): ValidationResult {
91 | // Check if task ID is provided
92 | if (!taskId || taskId.trim() === '') {
93 | return {
94 | valid: false,
95 | message: 'Task ID is required'
96 | };
97 | }
98 |
99 | // Check if task exists in phase
100 | const task = phase.tasks.find(t => t.id === taskId);
101 | if (!task) {
102 | return {
103 | valid: false,
104 | message: `Task with ID ${taskId} not found in phase ${phase.name}`
105 | };
106 | }
107 |
108 | return {
109 | valid: true,
110 | message: 'Task is valid',
111 | data: task
112 | };
113 | }
114 |
115 | /**
116 | * Validate phase status
117 | * @param status The status to validate
118 | * @returns Validation result
119 | */
120 | export function validatePhaseStatusValue(status: string): ValidationResult {
121 | if (!status || status.trim() === '') {
122 | return {
123 | valid: false,
124 | message: 'Phase status is required'
125 | };
126 | }
127 |
128 | if (!isValidPhaseStatus(status)) {
129 | return {
130 | valid: false,
131 | message: `Invalid phase status: ${status}. Valid values are: pending, in_progress, completed, reviewed`
132 | };
133 | }
134 |
135 | return {
136 | valid: true,
137 | message: 'Phase status is valid',
138 | data: status as PhaseStatus
139 | };
140 | }
141 |
142 | /**
143 | * Validate required text field
144 | * @param value The text value
145 | * @param fieldName The name of the field for error messages
146 | * @param minLength Minimum allowed length
147 | * @param maxLength Maximum allowed length
148 | * @returns Validation result
149 | */
150 | export function validateRequiredText(
151 | value: string,
152 | fieldName: string,
153 | minLength: number = 1,
154 | maxLength: number = 1000
155 | ): ValidationResult {
156 | if (!value || value.trim() === '') {
157 | return {
158 | valid: false,
159 | message: `${fieldName} is required`
160 | };
161 | }
162 |
163 | if (value.length < minLength) {
164 | return {
165 | valid: false,
166 | message: `${fieldName} must be at least ${minLength} characters`
167 | };
168 | }
169 |
170 | if (value.length > maxLength) {
171 | return {
172 | valid: false,
173 | message: `${fieldName} must be no more than ${maxLength} characters`
174 | };
175 | }
176 |
177 | return {
178 | valid: true,
179 | message: `${fieldName} is valid`,
180 | data: value.trim()
181 | };
182 | }
183 |
184 | /**
185 | * Validate feature, phase, and task IDs together
186 | * @param featureId Feature ID
187 | * @param phaseId Phase ID
188 | * @param taskId Task ID (optional)
189 | * @returns Validation result with feature, phase, and task in data if valid
190 | */
191 | export function validateFeaturePhaseTask(
192 | featureId: string,
193 | phaseId: string,
194 | taskId?: string
195 | ): ValidationResult {
196 | // Validate feature
197 | const featureResult = validateFeatureId(featureId);
198 | if (!featureResult.valid) {
199 | return featureResult;
200 | }
201 |
202 | // Validate phase
203 | const phaseResult = validatePhaseId(featureResult.data, phaseId);
204 | if (!phaseResult.valid) {
205 | return phaseResult;
206 | }
207 |
208 | // If taskId is provided, validate it
209 | if (taskId) {
210 | const taskResult = validateTaskId(phaseResult.data, taskId);
211 | if (!taskResult.valid) {
212 | return taskResult;
213 | }
214 |
215 | return {
216 | valid: true,
217 | message: 'Feature, phase, and task are valid',
218 | data: {
219 | feature: featureResult.data,
220 | phase: phaseResult.data,
221 | task: taskResult.data
222 | }
223 | };
224 | }
225 |
226 | return {
227 | valid: true,
228 | message: 'Feature and phase are valid',
229 | data: {
230 | feature: featureResult.data,
231 | phase: phaseResult.data
232 | }
233 | };
234 | }
```
--------------------------------------------------------------------------------
/instructions/PRD.md:
--------------------------------------------------------------------------------
```markdown
1 |
2 |
3 | # Vibe-Coder MCP Server PRD
4 |
5 | ## 1. Introduction
6 |
7 | 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.
8 |
9 | ## 2. Feature Objectives
10 |
11 | - **Assist LLM-based Coders:** Enable casual developers (vibe coders) to request and build features efficiently using an LLM-driven workflow.
12 | - **Structured Development:** Organize the feature creation process into clearly defined stages, ensuring each phase is documented, reviewed, and implemented according to best practices.
13 | - **Clean and Safe Code:** Enforce code quality standards, safe implementation practices, and thorough documentation throughout the development cycle.
14 | - **Iterative Clarification:** Engage users in rounds of questions to continuously refine and narrow down the feature requirements.
15 |
16 | ## 3. Scope and Requirements
17 |
18 | ### 3.1 Feature Request Clarification
19 | - **Iterative Engagement:**
20 | - The system will prompt users with follow-up questions to clarify the exact goals and nuances of the requested feature.
21 | - User responses will be recorded and used to refine the feature’s scope.
22 |
23 | ### 3.2 PRD Documentation
24 | - **Concise PRD Drafting:**
25 | - Develop a narrow and specific PRD that captures:
26 | - **Feature Objectives:** A clear description of what the feature should achieve.
27 | - **Detailed Requirements:** Specific, actionable requirements and constraints.
28 | - **Documentation References:** Links or references to relevant code formats, style guides, and best practices.
29 | - **File Organization:**
30 | - Save the PRD as a Markdown file using the naming convention:
31 | `StepNumber_FeatureName_PRD.md`
32 | *(Example: “01_VibeCoder_PRD.md”)*
33 |
34 | ### 3.3 Implementation Specification
35 | - **Phased Breakdown:**
36 | - Divide the feature’s implementation into discrete phases, where each phase addresses a single focused task.
37 | - **Checklists and Standards:**
38 | - For each phase, include a checklist detailing:
39 | - Task requirements
40 | - Code style and practices that must be adhered to
41 | - **Separate Documentation:**
42 | - Save the implementation plan in its own Markdown file, following the naming convention:
43 | `StepNumber_FeatureName_Implementation.md`
44 |
45 | ## 4. High-Level Implementation Overview
46 |
47 | ### 4.1 Development Workflow and Branching Strategy
48 | - **Feature Branch:**
49 | - Begin each new feature on a dedicated branch.
50 | - **Phase Branches:**
51 | - For every discrete phase within a feature, create a new branch off the feature branch.
52 | - **Commit and Code Review Process:**
53 | - After completing each phase:
54 | - Create detailed git commits capturing all changes.
55 | - Request a targeted code review that includes the PRD, the phase-specific implementation plan, and the relevant code changes.
56 | - Merge the phase branch back into the feature branch (retain phase branches for history).
57 |
58 | ### 4.2 Finalization Process
59 | - **Project Summary:**
60 | - Upon feature completion, update the project README with a summary of the new functionality.
61 | - **Feature-Specific README:**
62 | - Create a dedicated README that details:
63 | - The new feature’s functionality
64 | - How to use the feature
65 | - The adherence to the documented practices and checklists
66 | - **Final Code Review:**
67 | - Conduct a comprehensive code review covering all documentation and code, ensuring only the relevant portions are evaluated.
68 |
69 | ## 5. Structuring the MCP for LLM Compliance
70 |
71 | To ensure the LLM follows the outlined instructions accurately, the following practices are essential:
72 |
73 | - **Clear Hierarchical Sections:**
74 | - Use distinct headings and subheadings for each major section (e.g., Introduction, Objectives, Scope, etc.).
75 | - **Step-by-Step Breakdown:**
76 | - Organize tasks in numbered steps with clear, actionable directives (e.g., “Draft the PRD,” “Create phase-specific branches”).
77 | - **Consistent Naming Conventions:**
78 | - Specify precise file and branch naming formats to eliminate ambiguity.
79 | - **Imperative Language:**
80 | - Use direct commands (e.g., “Create,” “Define,” “Save,” “Merge”) to ensure clarity.
81 | - **Checklists and Examples:**
82 | - Provide concrete examples or templates for PRD sections, implementation plans, and commit messages to serve as models.
83 | - **Feedback Loops:**
84 | - Include instructions to ask clarifying questions if any step is ambiguous.
85 | - **Documentation Focus:**
86 | - Emphasize thorough documentation of every phase with clear checklists and version control histories.
87 |
88 | ## 6. Feedback and Iteration Process
89 |
90 | - **User Feedback Integration:**
91 | - Regularly incorporate user feedback to refine feature objectives and requirements.
92 | - **Iterative Updates:**
93 | - Update both the PRD and the implementation plan as new details emerge during the development process.
94 | - **Review and Approval:**
95 | - Ensure that each phase is reviewed and approved before moving to subsequent phases.
96 |
97 | ## 7. Appendix
98 |
99 | ### 7.1 File Naming Conventions
100 | - **PRD File:** `StepNumber_FeatureName_PRD.md`
101 | *(Example: “01_VibeCoder_PRD.md”)*
102 | - **Implementation Plan File:** `StepNumber_FeatureName_Implementation.md`
103 |
104 | ### 7.2 Example Checklists and Templates
105 | - **Feature Request Checklist:**
106 | - [ ] Define feature objective
107 | - [ ] Clarify user requirements through iterative questions
108 | - [ ] Record user responses for scope refinement
109 | - **Phase Implementation Checklist:**
110 | - [ ] Define phase-specific tasks
111 | - [ ] Outline required code standards and practices
112 | - [ ] Document progress with commit messages
113 | - [ ] Request targeted code review
114 | - [ ] Merge phase branch into feature branch after review
115 |
116 |
```
--------------------------------------------------------------------------------
/test-document-tools.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env bun
2 |
3 | // Import the necessary modules from our codebase
4 | import { createFeatureObject } from './build/utils.js';
5 | import { storeFeature } from './build/storage.js';
6 | import { addClarificationResponse } from './build/clarification.js';
7 | import { generatePRD, generateImplementationPlan } from './build/documentation.js';
8 | import { documentStorage, DocumentType } from './build/document-storage.js';
9 |
10 | // Function to create a test feature with some clarification responses
11 | async function createTestFeature() {
12 | console.log('Creating test feature...');
13 |
14 | // Create a new feature
15 | const feature = createFeatureObject(
16 | 'Test Document Tools',
17 | 'A feature to test document storage tools'
18 | );
19 |
20 | // Store the feature first
21 | storeFeature(feature);
22 |
23 | // Add some clarification responses
24 | addClarificationResponse(feature.id,
25 | 'What specific problem does this feature solve?',
26 | 'Testing document tools after fixing enum serialization issues'
27 | );
28 |
29 | addClarificationResponse(feature.id,
30 | 'Who are the target users for this feature?',
31 | 'Developers who need to use document storage tools'
32 | );
33 |
34 | addClarificationResponse(feature.id,
35 | 'What are the key requirements for this feature?',
36 | 'Both sets of document tools must work correctly'
37 | );
38 |
39 | addClarificationResponse(feature.id,
40 | 'What are the technical constraints or considerations?',
41 | 'Must work with various MCP clients'
42 | );
43 |
44 | addClarificationResponse(feature.id,
45 | 'How will we measure the success of this feature?',
46 | 'All document tools work correctly'
47 | );
48 |
49 | addClarificationResponse(feature.id,
50 | 'Are there any dependencies on other features or systems?',
51 | 'Document storage system'
52 | );
53 |
54 | addClarificationResponse(feature.id,
55 | 'What are the potential risks or challenges in implementing this feature?',
56 | 'Enum serialization issues with MCP'
57 | );
58 |
59 | // Generate documents
60 | const prd = await generatePRD(feature);
61 | console.log('Generated PRD');
62 |
63 | const implementationPlan = await generateImplementationPlan(feature);
64 | console.log('Generated Implementation Plan');
65 |
66 | return feature;
67 | }
68 |
69 | // Function to test the document_path tool logic
70 | async function testDocumentPath(feature) {
71 | console.log('\nTesting document_path tool logic...');
72 |
73 | try {
74 | // Map string to DocumentType enum
75 | const documentType = 'prd';
76 | let docType;
77 |
78 | if (documentType === 'prd') {
79 | docType = DocumentType.PRD;
80 | } else if (documentType === 'implementation-plan') {
81 | docType = DocumentType.IMPLEMENTATION_PLAN;
82 | } else {
83 | throw new Error(`Invalid document type: ${documentType}`);
84 | }
85 |
86 | // Check if the document exists
87 | if (!documentStorage.hasDocument(feature.id, docType)) {
88 | throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`);
89 | }
90 |
91 | // Get the default file path for the document
92 | const filePath = documentStorage.getDefaultFilePath(feature.id, docType);
93 |
94 | // Get the document to check if it's been saved
95 | const document = documentStorage.getDocument(feature.id, docType);
96 |
97 | console.log(`Document path: ${filePath}`);
98 | console.log(`Saved to disk: ${document?.metadata.isSaved ? 'Yes' : 'No'}`);
99 |
100 | return {
101 | filePath,
102 | isSaved: document?.metadata.isSaved
103 | };
104 | } catch (error) {
105 | console.error(`Error in document_path: ${error.message}`);
106 | return null;
107 | }
108 | }
109 |
110 | // Function to test the document_save tool logic
111 | async function testDocumentSave(feature, customPath = null) {
112 | console.log('\nTesting document_save tool logic...');
113 |
114 | try {
115 | // Map string to DocumentType enum
116 | const documentType = 'prd';
117 | let docType;
118 |
119 | if (documentType === 'prd') {
120 | docType = DocumentType.PRD;
121 | } else if (documentType === 'implementation-plan') {
122 | docType = DocumentType.IMPLEMENTATION_PLAN;
123 | } else {
124 | throw new Error(`Invalid document type: ${documentType}`);
125 | }
126 |
127 | // Check if the document exists
128 | if (!documentStorage.hasDocument(feature.id, docType)) {
129 | throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`);
130 | }
131 |
132 | let savedPath;
133 |
134 | // If a custom path was provided, use it; otherwise, save to the default path
135 | if (customPath) {
136 | savedPath = await documentStorage.saveDocumentToCustomPath(feature.id, docType, customPath);
137 | } else {
138 | savedPath = await documentStorage.saveDocumentToFile(feature.id, docType);
139 | }
140 |
141 | console.log(`Document saved successfully to: ${savedPath}`);
142 |
143 | return savedPath;
144 | } catch (error) {
145 | console.error(`Error in document_save: ${error.message}`);
146 | return null;
147 | }
148 | }
149 |
150 | // Main function to run all tests
151 | async function main() {
152 | try {
153 | console.log('=== Document Tools Test ===');
154 |
155 | // Create a test feature and generate documents
156 | const feature = await createTestFeature();
157 | console.log(`Feature created with ID: ${feature.id}`);
158 |
159 | // Test document_path tool logic
160 | const pathResult = await testDocumentPath(feature);
161 |
162 | // Test document_save tool logic
163 | const saveResult = await testDocumentSave(feature);
164 |
165 | // Test with a custom path
166 | const customPath = `./documents/test-document-tools-${Date.now()}.md`;
167 | const customSaveResult = await testDocumentSave(feature, customPath);
168 |
169 | // Print summary
170 | console.log('\n=== Test Summary ===');
171 | console.log(`Feature ID: ${feature.id}`);
172 | console.log(`Document path: ${pathResult?.filePath || 'Error'}`);
173 | console.log(`Document saved to default path: ${saveResult || 'Error'}`);
174 | console.log(`Document saved to custom path: ${customSaveResult || 'Error'}`);
175 |
176 | console.log('\nTest completed successfully!');
177 |
178 | } catch (error) {
179 | console.error('Error in test:', error);
180 | }
181 | }
182 |
183 | // Run the main function
184 | main().catch(console.error);
```
--------------------------------------------------------------------------------
/src/registry.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @file registry.ts
3 | * @version 1.1.0
4 | *
5 | * Provides registries for tools and resources in the Vibe-Coder MCP Server.
6 | * These registries manage URI templates, matching, and tool handlers.
7 | */
8 |
9 | import { ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
10 | import { ErrorCode, createErrorResponse, createToolErrorResponse } from './errors.js';
11 | import { Feature } from './types.js';
12 |
13 | // --------- Tool Registry ---------
14 |
15 | /**
16 | * Type for tool handler function
17 | */
18 | export type ToolHandler<T = any> = (params: T) => Promise<any>;
19 |
20 | /**
21 | * Tool metadata type
22 | */
23 | export interface ToolMetadata {
24 | description: string;
25 | inputSchema: any;
26 | examples?: any[];
27 | }
28 |
29 | /**
30 | * Tool registry to map tool names to their handler functions and metadata
31 | */
32 | export class ToolRegistry {
33 | private handlers: Map<string, ToolHandler> = new Map();
34 | private toolMetadata: Map<string, ToolMetadata> = new Map();
35 |
36 | /**
37 | * Register a tool handler with metadata
38 | * @param name Tool name
39 | * @param handler Tool handler function
40 | * @param description Tool description
41 | * @param inputSchema Tool input schema
42 | * @param examples Optional examples for the tool
43 | */
44 | register<T>(
45 | name: string,
46 | handler: ToolHandler<T>,
47 | description: string,
48 | inputSchema: any,
49 | examples?: any[]
50 | ): void {
51 | this.handlers.set(name, handler);
52 | this.toolMetadata.set(name, {
53 | description,
54 | inputSchema,
55 | examples
56 | });
57 | }
58 |
59 | /**
60 | * Get a tool handler by name
61 | * @param name Tool name
62 | * @returns The tool handler function or undefined if not found
63 | */
64 | getHandler(name: string): ToolHandler | undefined {
65 | return this.handlers.get(name);
66 | }
67 |
68 | /**
69 | * Get tool metadata by name
70 | * @param name Tool name
71 | * @returns The tool metadata or undefined if not found
72 | */
73 | getMetadata(name: string): ToolMetadata | undefined {
74 | return this.toolMetadata.get(name);
75 | }
76 |
77 | /**
78 | * Check if a tool exists
79 | * @param name Tool name
80 | * @returns True if the tool exists
81 | */
82 | hasHandler(name: string): boolean {
83 | return this.handlers.has(name);
84 | }
85 |
86 | /**
87 | * Execute a tool by name with parameters
88 | * @param name Tool name
89 | * @param params Tool parameters
90 | * @returns The tool execution result
91 | */
92 | async execute(name: string, params: any): Promise<any> {
93 | const handler = this.getHandler(name);
94 |
95 | if (!handler) {
96 | return createToolErrorResponse(`Unknown tool "${name}"`);
97 | }
98 |
99 | try {
100 | return await handler(params);
101 | } catch (error) {
102 | console.error(`Error executing tool ${name}:`, error);
103 | return createToolErrorResponse(
104 | error instanceof Error ? error.message : "Internal server error"
105 | );
106 | }
107 | }
108 |
109 | /**
110 | * List all registered tools with their metadata
111 | * @returns Array of tool information objects
112 | */
113 | listTools(): { name: string; description: string; inputSchema: any; examples?: any[] }[] {
114 | return Array.from(this.handlers.keys()).map((name) => {
115 | const metadata = this.toolMetadata.get(name);
116 | return {
117 | name,
118 | description: metadata?.description || "No description provided",
119 | inputSchema: metadata?.inputSchema || {},
120 | examples: metadata?.examples
121 | };
122 | });
123 | }
124 | }
125 |
126 | // --------- Resource Registry ---------
127 |
128 | /**
129 | * Resource handler type
130 | */
131 | export type ResourceHandler = (uri: URL, params: Record<string, string>) => Promise<any>;
132 |
133 | /**
134 | * Resource registry entry
135 | */
136 | type ResourceRegistryEntry = {
137 | template: ResourceTemplate;
138 | handler: ResourceHandler;
139 | };
140 |
141 | /**
142 | * Resource registry for managing URI templates and handlers
143 | */
144 | export class ResourceRegistry {
145 | private resources: ResourceRegistryEntry[] = [];
146 |
147 | /**
148 | * Register a resource with a template and handler
149 | */
150 | register(template: ResourceTemplate, handler: ResourceHandler): void {
151 | this.resources.push({ template, handler });
152 | }
153 |
154 | /**
155 | * Find a matching resource and extract parameters
156 | */
157 | findMatch(uri: string): { handler: ResourceHandler; params: Record<string, string> } | undefined {
158 | const url = new URL(uri);
159 |
160 | for (const { template, handler } of this.resources) {
161 | const match = this.matchTemplate(url, template);
162 |
163 | if (match) {
164 | return { handler, params: match };
165 | }
166 | }
167 |
168 | return undefined;
169 | }
170 |
171 | /**
172 | * Match a URL against a template and extract parameters
173 | */
174 | private matchTemplate(url: URL, template: ResourceTemplate): Record<string, string> | undefined {
175 | // Convert template to regex pattern
176 | const pattern = this.templateToPattern(template.pattern);
177 | const regex = new RegExp(pattern.pattern);
178 |
179 | // Match against the full URL
180 | const match = regex.exec(url.href);
181 |
182 | if (!match) {
183 | return undefined;
184 | }
185 |
186 | // Extract named parameters
187 | const params: Record<string, string> = {};
188 |
189 | pattern.paramNames.forEach((name, index) => {
190 | params[name] = match[index + 1] || '';
191 | });
192 |
193 | return params;
194 | }
195 |
196 | /**
197 | * Convert a URI template to a regex pattern
198 | * Handles {param} syntax in templates
199 | */
200 | private templateToPattern(template: string): { pattern: string; paramNames: string[] } {
201 | const paramNames: string[] = [];
202 | let pattern = template.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
203 |
204 | // Replace {param} with capture groups
205 | pattern = pattern.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, name) => {
206 | paramNames.push(name);
207 | return '([^/]+)';
208 | });
209 |
210 | // Replace * with wildcard
211 | pattern = pattern.replace(/\\\*/g, '.*');
212 |
213 | // Anchor to start of string
214 | pattern = `^${pattern}$`;
215 |
216 | return { pattern, paramNames };
217 | }
218 |
219 | /**
220 | * List all registered resources
221 | * @returns Array of resource templates
222 | */
223 | listResources(): { template: string }[] {
224 | return this.resources.map(({ template }) => ({
225 | template: template.pattern
226 | }));
227 | }
228 | }
229 |
230 | // Create global instances
231 | export const toolRegistry = new ToolRegistry();
232 | export const resourceRegistry = new ResourceRegistry();
```
--------------------------------------------------------------------------------
/docs/mcp-sdk-improvements.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP SDK Implementation Improvements
2 |
3 | ## Overview
4 |
5 | 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.
6 |
7 | ## Key Improvements
8 |
9 | ### 1. Tool Handler Registry
10 |
11 | We replaced the large switch statement in the main file with a tool handler registry that maps tool names to their handler functions:
12 |
13 | ```typescript
14 | // Before (in index.ts)
15 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
16 | try {
17 | switch (request.params.name) {
18 | case "start_feature_clarification": {
19 | // Implementation...
20 | }
21 | case "provide_clarification": {
22 | // Implementation...
23 | }
24 | // Many more cases...
25 | }
26 | } catch (error) {
27 | // Error handling...
28 | }
29 | });
30 |
31 | // After
32 | // In registry.ts
33 | export class ToolRegistry {
34 | private handlers: Map<string, ToolHandler> = new Map();
35 |
36 | register<T>(name: string, handler: ToolHandler<T>): void {
37 | this.handlers.set(name, handler);
38 | }
39 |
40 | async execute(name: string, params: any): Promise<any> {
41 | const handler = this.getHandler(name);
42 | if (!handler) {
43 | return createToolErrorResponse(`Unknown tool "${name}"`);
44 | }
45 | return await handler(params);
46 | }
47 | }
48 |
49 | // In index-updated.ts
50 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
51 | try {
52 | const toolName = request.params.name;
53 | const toolArguments = request.params.arguments || {};
54 | return await toolRegistry.execute(toolName, toolArguments);
55 | } catch (error) {
56 | // Improved error handling...
57 | }
58 | });
59 | ```
60 |
61 | ### 2. Robust Resource URI Handling
62 |
63 | We implemented a robust resource URI handling mechanism using a `ResourceRegistry` class:
64 |
65 | ```typescript
66 | // In registry.ts
67 | export class ResourceRegistry {
68 | private resources: ResourceRegistryEntry[] = [];
69 |
70 | register(template: ResourceTemplate, handler: ResourceHandler): void {
71 | this.resources.push({ template, handler });
72 | }
73 |
74 | findMatch(uri: string): { handler: ResourceHandler; params: Record<string, string> } | undefined {
75 | const url = new URL(uri);
76 | for (const { template, handler } of this.resources) {
77 | const match = this.matchTemplate(url, template);
78 | if (match) {
79 | return { handler, params: match };
80 | }
81 | }
82 | return undefined;
83 | }
84 | }
85 |
86 | // In index-updated.ts
87 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
88 | try {
89 | const uri = request.params.uri;
90 | const match = resourceRegistry.findMatch(uri);
91 | if (match) {
92 | return await match.handler(new URL(uri), match.params);
93 | }
94 | return createErrorResponse(ErrorCode.RESOURCE_NOT_FOUND, `Resource not found: ${uri}`);
95 | } catch (error) {
96 | // Improved error handling...
97 | }
98 | });
99 | ```
100 |
101 | ### 3. Improved Error Handling
102 |
103 | We standardized error handling across the codebase with consistent JSON-RPC 2.0 error responses:
104 |
105 | ```typescript
106 | // In errors.ts
107 | export enum ErrorCode {
108 | // JSON-RPC 2.0 Standard Error Codes
109 | PARSE_ERROR = -32700,
110 | INVALID_REQUEST = -32600,
111 | METHOD_NOT_FOUND = -32601,
112 | INVALID_PARAMS = -32602,
113 | INTERNAL_ERROR = -32603,
114 |
115 | // Custom Error Codes
116 | VALIDATION_ERROR = -32000,
117 | RESOURCE_NOT_FOUND = -32001,
118 | // More custom error codes...
119 | }
120 |
121 | export const createErrorResponse = (
122 | code: ErrorCode,
123 | message: string,
124 | data?: any
125 | ): ErrorResponse => {
126 | return {
127 | error: {
128 | code,
129 | message,
130 | data
131 | }
132 | };
133 | };
134 |
135 | export const createToolErrorResponse = (
136 | message: string,
137 | details?: any
138 | ): ToolErrorResponse => {
139 | return {
140 | content: [{
141 | type: "text",
142 | text: `Error: ${message}`
143 | }],
144 | isError: true
145 | };
146 | };
147 | ```
148 |
149 | ### 4. Stronger Type Safety
150 |
151 | We improved type safety throughout the codebase with Zod schemas and branded types:
152 |
153 | ```typescript
154 | // In schemas.ts
155 | // Branded types for IDs
156 | export type FeatureId = string & { readonly _brand: unique symbol };
157 | export type PhaseId = string & { readonly _brand: unique symbol };
158 | export type TaskId = string & { readonly _brand: unique symbol };
159 |
160 | // Zod schemas for validation
161 | export const FeatureSchema = z.object({
162 | id: z.string().refine(isFeatureId, "Invalid feature ID format"),
163 | name: z.string().min(2).max(100),
164 | // More fields...
165 | });
166 |
167 | // Type guards
168 | export const isFeatureId = (id: string): id is FeatureId => id.startsWith('feature-');
169 |
170 | // ID creators
171 | export const createFeatureId = (id: string): FeatureId => {
172 | if (!isFeatureId(id)) throw new Error(`Invalid feature ID format: ${id}`);
173 | return id as FeatureId;
174 | };
175 |
176 | // In utils.ts
177 | export function generateFeatureId(): FeatureId {
178 | const randomPart = Math.random().toString(36).substring(2, 10);
179 | return createFeatureId(`feature-${randomPart}`);
180 | }
181 | ```
182 |
183 | ### 5. Modular Code Structure
184 |
185 | We reorganized the codebase into more modular components with clear separation of concerns:
186 |
187 | - `registry.ts` - Tool and resource registries
188 | - `tool-handlers.ts` - Tool handler implementations
189 | - `resource-handlers.ts` - Resource handler implementations
190 | - `errors.ts` - Error handling utilities
191 | - `schemas.ts` - Type definitions and validation schemas
192 |
193 | ## Benefits
194 |
195 | 1. **Improved Maintainability**: The modular code structure makes it easier to understand, maintain, and extend the codebase.
196 |
197 | 2. **Better Type Safety**: Branded types and Zod schemas provide stronger type checking and runtime validation.
198 |
199 | 3. **Consistent Error Handling**: Standardized error responses across the codebase with proper JSON-RPC 2.0 error codes.
200 |
201 | 4. **More Robust URI Handling**: Proper URI template matching for resources following MCP best practices.
202 |
203 | 5. **Cleaner Code**: Separation of concerns between different components and elimination of large switch statements.
204 |
205 | ## Testing
206 |
207 | The improvements have been tested with a comprehensive test script (`test-mcp-improved.js`) that verifies the functionality of:
208 |
209 | - Tool registration and execution
210 | - Resource URI matching and handling
211 | - Error reporting
212 | - End-to-end workflows (feature clarification, PRD generation, etc.)
213 |
214 | ## Conclusion
215 |
216 | 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
1 |
2 |
3 | # Vibe-Coder MCP Server Implementation Plan
4 |
5 | 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.
6 |
7 | ---
8 |
9 | ## Phase 1: Environment Setup and Repository Initialization
10 |
11 | ### Objectives
12 | - Set up the development environment and repository.
13 | - Install required dependencies (including the MCP TypeScript SDK).
14 | - Establish a working base by validating a simple MCP server.
15 |
16 | ### Tasks
17 | - [ ] **Repository Initialization:**
18 | - Create a new repository or branch dedicated to the Vibe-Coder feature.
19 | - Follow the naming convention: `feature/vibe-coder`.
20 | - [ ] **Environment Setup:**
21 | - Ensure Node.js and npm are installed.
22 | - Install the MCP TypeScript SDK:
23 | ```bash
24 | npm install @modelcontextprotocol/sdk
25 | ```
26 | - [ ] **Basic MCP Server Setup:**
27 | - Create a minimal MCP server (e.g., an Echo server) based on the MCP TypeScript examples.
28 | - Validate that the server correctly processes simple requests (using `StdioServerTransport` or HTTP with SSE).
29 | - [ ] **Establish Branching Guidelines:**
30 | - Document and enforce the branch naming convention (e.g., each phase creates a branch off the main feature branch).
31 |
32 | ### Code Style & Practices
33 | - Use TypeScript best practices.
34 | - Follow the MCP SDK examples for a clean code structure.
35 | - Maintain clear commit messages describing changes made.
36 |
37 | ---
38 |
39 | ## Phase 2: Implement Feature Request Clarification Module
40 |
41 | ### Objectives
42 | - Build a module to interactively engage users to clarify feature requests.
43 | - Record and structure user responses to refine the feature scope.
44 |
45 | ### Tasks
46 | - [ ] **Design Clarification Workflow:**
47 | - Define a flow that asks iterative questions and records responses.
48 | - Use clear prompts to ensure the user’s requirements are well defined.
49 | - [ ] **Implement Clarification Module:**
50 | - Create a dedicated TypeScript module (e.g., `clarification.ts`) that:
51 | - Sends questions to the user.
52 | - Stores responses in a structured format (e.g., JSON).
53 | - [ ] **Integrate with MCP Tools/Prompts:**
54 | - Utilize MCP prompts to ask clarifying questions if appropriate.
55 | - [ ] **Testing:**
56 | - Create unit tests that simulate user interactions and verify the response recording.
57 |
58 | ### Checklist
59 | - [ ] Define a list of initial questions.
60 | - [ ] Implement response recording with validation.
61 | - [ ] Integrate and test prompt handling via MCP.
62 |
63 | ---
64 |
65 | ## Phase 3: PRD and Documentation Generation Module
66 |
67 | ### Objectives
68 | - Automate the generation and saving of PRD documentation.
69 | - Ensure that the PRD is saved in the correct folder with the proper naming convention.
70 |
71 | ### Tasks
72 | - [ ] **Template Creation:**
73 | - Develop a Markdown template for the PRD (reuse sections from the PRD document).
74 | - [ ] **File Generation Module:**
75 | - Create a module (e.g., `docGenerator.ts`) that:
76 | - Populates the template with feature details.
77 | - Saves the file to `vibe-instructions/` with a name like `01_VibeCoder_PRD.md`.
78 | - [ ] **Validation:**
79 | - Implement checks to ensure the generated file meets naming and format requirements.
80 |
81 | ### Checklist
82 | - [ ] Define PRD template sections.
83 | - [ ] Write file I/O functions to save Markdown files.
84 | - [ ] Test file generation with dummy data.
85 |
86 | ---
87 |
88 | ## Phase 4: MCP Server Integration and Feature Implementation
89 |
90 | ### Objectives
91 | - Integrate the clarified feature request and documentation modules into the MCP server.
92 | - Build the core functionality of the Vibe-Coder using the MCP TypeScript SDK.
93 |
94 | ### Tasks
95 | - [ ] **Server Initialization:**
96 | - Initialize an MCP server instance using the MCP TypeScript SDK.
97 | - Example:
98 | ```typescript
99 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
100 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
101 |
102 | const server = new McpServer({
103 | name: "Vibe-Coder",
104 | version: "1.0.0"
105 | });
106 | ```
107 | - [ ] **Integrate Clarification Module:**
108 | - Connect the clarification module to the MCP server’s tool or prompt mechanisms.
109 | - [ ] **Feature Request Handling:**
110 | - Define MCP tools or resources to process and store feature requests and clarifications.
111 | - [ ] **Documentation Integration:**
112 | - Link the generated PRD and Implementation Plan documents to the MCP server’s workflow (e.g., as resources that can be fetched by clients).
113 | - [ ] **Testing:**
114 | - Test server interactions using both local transports (stdio) and, if applicable, HTTP with SSE.
115 |
116 | ### Checklist
117 | - [ ] Initialize MCP server using the TypeScript SDK.
118 | - [ ] Integrate the clarification module with MCP prompts.
119 | - [ ] Validate tool/resource functionality with sample requests.
120 | - [ ] Ensure documentation files are retrievable by the server.
121 |
122 | ---
123 |
124 | ## Phase 5: Phased Implementation Workflow and Branching
125 |
126 | ### Objectives
127 | - Establish and enforce the phased development workflow.
128 | - Ensure each phase’s changes are documented, committed, and reviewed before merging.
129 |
130 | ### Tasks
131 | - [ ] **Branch Creation:**
132 | - Create a new branch for each phase from the main feature branch (e.g., `phase/clarification`, `phase/documentation`, `phase/integration`).
133 | - [ ] **Commit Process:**
134 | - Write detailed commit messages that reference the checklist and completed tasks.
135 | - [ ] **Code Reviews:**
136 | - Request targeted code reviews for each phase.
137 | - Share the PRD, phase-specific Implementation Plan, and corresponding code changes.
138 | - [ ] **Merge and History:**
139 | - Merge phase branches into the feature branch once approved.
140 | - Retain phase branches for historical reference.
141 |
142 | ### Checklist
143 | - [ ] Create and document branch naming conventions.
144 | - [ ] Write commit message guidelines.
145 | - [ ] Schedule code reviews for each completed phase.
146 | - [ ] Merge phase branches after successful review.
147 |
148 | ---
149 |
150 | ## Phase 6: Finalization, Project Summary, and Feature README
151 |
152 | ### Objectives
153 | - Complete the feature by updating project documentation.
154 | - Summarize the new functionality and provide usage instructions.
155 |
156 | ### Tasks
157 | - [ ] **Project README Update:**
158 | - Update the main project README with a summary of the Vibe-Coder feature.
159 | - [ ] **Feature-Specific README:**
160 | - Create a dedicated README that details:
161 | - The new feature’s functionality.
162 | - How to use and integrate the Vibe-Coder MCP Server.
163 | - References to the PRD and Implementation Plan.
164 | - [ ] **Final Code Review:**
165 | - Request a comprehensive review covering the entire feature.
166 | - Ensure all documentation, tests, and code changes meet the defined standards.
167 |
168 | ### Checklist
169 | - [ ] Write a project summary for the new feature.
170 | - [ ] Generate a feature-specific README with detailed instructions.
171 | - [ ] Conduct a final, comprehensive code review.
172 | - [ ] Merge final changes into the main branch.
173 |
174 | ---
175 |
176 | ## Summary
177 |
178 | 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.
179 |
180 | 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.
181 |
182 |
```
--------------------------------------------------------------------------------
/src/test-mcp-improved.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * @file test-mcp-improved.js
5 | *
6 | * Test script for the improved MCP server implementation.
7 | * Tests the tool registry, resource registry, and error handling.
8 | */
9 |
10 | const { spawn } = require('child_process');
11 | const path = require('path');
12 |
13 | // Path to the MCP server
14 | const SERVER_PATH = path.join(__dirname, 'index-updated.ts');
15 |
16 | // Store responses and state
17 | const responses = [];
18 | let featureId = null;
19 |
20 | // Start the MCP server process
21 | const server = spawn('ts-node', [SERVER_PATH], {
22 | stdio: ['pipe', 'pipe', process.stderr]
23 | });
24 |
25 | // Handle server's stdout
26 | server.stdout.on('data', (data) => {
27 | try {
28 | const lines = data.toString().trim().split('\n');
29 | for (const line of lines) {
30 | if (!line.trim()) continue;
31 |
32 | // Parse the JSON response
33 | const response = JSON.parse(line);
34 | responses.push(response);
35 |
36 | // Process the response based on its type
37 | processResponse(response);
38 | }
39 | } catch (error) {
40 | console.error('Error processing server response:', error);
41 | }
42 | });
43 |
44 | // Process a server response
45 | function processResponse(response) {
46 | if (response.id === 'init') {
47 | sendInitialized();
48 | } else if (response.id === 'start-feature') {
49 | // Extract feature ID from the response
50 | const content = response.result?.content[0]?.text;
51 | if (content) {
52 | const match = content.match(/Feature ID: ([a-z0-9-]+)/);
53 | if (match) {
54 | featureId = match[1];
55 | console.log(`\nCreated feature with ID: ${featureId}`);
56 |
57 | // Ask the first clarification question
58 | sendProvideClarification(featureId, 'What problem does this feature solve?', 'The feature solves the problem of managing test data across different environments.');
59 | }
60 | }
61 | } else if (response.id === 'clarify-1') {
62 | sendProvideClarification(featureId, 'Who are the target users?', 'Developers and QA engineers who need to create, manage, and share test data.');
63 | } else if (response.id === 'clarify-2') {
64 | sendProvideClarification(featureId, 'What are the key requirements?', 'Data import/export, version control integration, and user-friendly interface.');
65 | } else if (response.id === 'clarify-3') {
66 | sendProvideClarification(featureId, 'What are the success criteria?', 'Users can create and manage test data across environments with minimal effort.');
67 | } else if (response.id === 'clarify-4') {
68 | sendProvideClarification(featureId, 'Any technical constraints?', 'Must work with existing database systems and be containerizable.');
69 | } else if (response.id === 'clarify-5') {
70 | console.log('\nAll clarification questions answered.');
71 |
72 | // Generate PRD
73 | sendGeneratePRD(featureId);
74 | } else if (response.id === 'generate-prd') {
75 | // Generate implementation plan
76 | sendGenerateImplementationPlan(featureId);
77 | } else if (response.id === 'generate-implementation-plan') {
78 | // Create a phase
79 | sendCreatePhase(featureId);
80 | } else if (response.id === 'create-phase') {
81 | // Extract phase ID from the response
82 | const content = response.result?.content[0]?.text;
83 | if (content) {
84 | const match = content.match(/ID: ([a-z0-9-]+)/);
85 | if (match) {
86 | const phaseId = match[1];
87 | console.log(`\nCreated phase with ID: ${phaseId}`);
88 |
89 | // Update phase status
90 | sendUpdatePhaseStatus(featureId, phaseId, 'in_progress');
91 | }
92 | }
93 | } else if (response.id === 'update-phase-status') {
94 | // Get feature details
95 | sendReadResource(`feature://${featureId}`);
96 | } else if (response.id === 'read-feature') {
97 | // Test resource handler
98 | sendReadResource(`feature://${featureId}/progress`);
99 | } else if (response.id === 'read-progress') {
100 | // Test list resources
101 | sendListResources();
102 | } else if (response.id === 'list-resources') {
103 | // Test list tools
104 | sendListTools();
105 | } else if (response.id === 'list-tools') {
106 | // All tests completed
107 | console.log('\nAll tests completed successfully.');
108 |
109 | // Clean up
110 | server.stdin.end();
111 | process.exit(0);
112 | }
113 | }
114 |
115 | // Send initialization message
116 | function sendInitialize() {
117 | const message = {
118 | jsonrpc: '2.0',
119 | id: 'init',
120 | method: 'initialize',
121 | params: {
122 | processId: process.pid,
123 | clientInfo: {
124 | name: 'MCP Test Client',
125 | version: '1.0.0'
126 | },
127 | capabilities: {
128 | resources: {},
129 | tools: {},
130 | prompts: {}
131 | }
132 | }
133 | };
134 |
135 | sendMessage(message);
136 | }
137 |
138 | // Send initialized notification
139 | function sendInitialized() {
140 | const message = {
141 | jsonrpc: '2.0',
142 | method: 'initialized',
143 | params: {}
144 | };
145 |
146 | sendMessage(message);
147 |
148 | // Start the tests after initialization
149 | console.log('\nStarting tests...');
150 | sendStartFeature();
151 | }
152 |
153 | // Send a message to start feature clarification
154 | function sendStartFeature() {
155 | const message = {
156 | jsonrpc: '2.0',
157 | id: 'start-feature',
158 | method: 'callTool',
159 | params: {
160 | name: 'start_feature_clarification',
161 | arguments: {
162 | featureName: 'Test Data Manager',
163 | initialDescription: 'A tool for managing test data across various environments.'
164 | }
165 | }
166 | };
167 |
168 | sendMessage(message);
169 | }
170 |
171 | // Send a message to provide clarification
172 | function sendProvideClarification(featureId, question, answer) {
173 | // Use a different ID for each clarification to track them
174 | const clarificationId = `clarify-${responses.filter(r => r.id && r.id.startsWith('clarify-')).length + 1}`;
175 |
176 | const message = {
177 | jsonrpc: '2.0',
178 | id: clarificationId,
179 | method: 'callTool',
180 | params: {
181 | name: 'provide_clarification',
182 | arguments: {
183 | featureId,
184 | question,
185 | answer
186 | }
187 | }
188 | };
189 |
190 | sendMessage(message);
191 | }
192 |
193 | // Send a message to generate a PRD
194 | function sendGeneratePRD(featureId) {
195 | const message = {
196 | jsonrpc: '2.0',
197 | id: 'generate-prd',
198 | method: 'callTool',
199 | params: {
200 | name: 'generate_prd',
201 | arguments: {
202 | featureId
203 | }
204 | }
205 | };
206 |
207 | sendMessage(message);
208 | }
209 |
210 | // Send a message to generate an implementation plan
211 | function sendGenerateImplementationPlan(featureId) {
212 | const message = {
213 | jsonrpc: '2.0',
214 | id: 'generate-implementation-plan',
215 | method: 'callTool',
216 | params: {
217 | name: 'generate_implementation_plan',
218 | arguments: {
219 | featureId
220 | }
221 | }
222 | };
223 |
224 | sendMessage(message);
225 | }
226 |
227 | // Send a message to create a phase
228 | function sendCreatePhase(featureId) {
229 | const message = {
230 | jsonrpc: '2.0',
231 | id: 'create-phase',
232 | method: 'callTool',
233 | params: {
234 | name: 'create_phase',
235 | arguments: {
236 | featureId,
237 | name: 'Design',
238 | description: 'Design the architecture and user interface for the Test Data Manager.'
239 | }
240 | }
241 | };
242 |
243 | sendMessage(message);
244 | }
245 |
246 | // Send a message to update phase status
247 | function sendUpdatePhaseStatus(featureId, phaseId, status) {
248 | const message = {
249 | jsonrpc: '2.0',
250 | id: 'update-phase-status',
251 | method: 'callTool',
252 | params: {
253 | name: 'update_phase_status',
254 | arguments: {
255 | featureId,
256 | phaseId,
257 | status
258 | }
259 | }
260 | };
261 |
262 | sendMessage(message);
263 | }
264 |
265 | // Send a message to read a resource
266 | function sendReadResource(uri) {
267 | const message = {
268 | jsonrpc: '2.0',
269 | id: uri.includes('progress') ? 'read-progress' : 'read-feature',
270 | method: 'readResource',
271 | params: {
272 | uri
273 | }
274 | };
275 |
276 | sendMessage(message);
277 | }
278 |
279 | // Send a message to list resources
280 | function sendListResources() {
281 | const message = {
282 | jsonrpc: '2.0',
283 | id: 'list-resources',
284 | method: 'listResources',
285 | params: {}
286 | };
287 |
288 | sendMessage(message);
289 | }
290 |
291 | // Send a message to list tools
292 | function sendListTools() {
293 | const message = {
294 | jsonrpc: '2.0',
295 | id: 'list-tools',
296 | method: 'listTools',
297 | params: {}
298 | };
299 |
300 | sendMessage(message);
301 | }
302 |
303 | // Send a message to the server
304 | function sendMessage(message) {
305 | const json = JSON.stringify(message);
306 | server.stdin.write(json + '\n');
307 | }
308 |
309 | // Start the test
310 | sendInitialize();
311 |
312 | // Handle process exit
313 | process.on('exit', () => {
314 | server.kill();
315 | });
```
--------------------------------------------------------------------------------
/src/document-storage.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @file document-storage.ts
3 | * @version 1.0.0
4 | * @status STABLE - DO NOT MODIFY WITHOUT TESTS
5 | * @lastModified 2023-03-23
6 | *
7 | * Document storage module for Vibe-Coder MCP Server.
8 | * Provides both in-memory and file-based storage for documents
9 | * generated during the feature development process.
10 | *
11 | * IMPORTANT:
12 | * - Path validation is critical for security
13 | * - All file operations must be properly error-handled
14 | * - Document metadata must be maintained consistently
15 | *
16 | * Functionality:
17 | * - Store documents in memory and file system
18 | * - Retrieve documents from storage
19 | * - Path resolution and validation
20 | * - Error handling for file operations
21 | */
22 |
23 | import * as fs from 'fs/promises';
24 | import * as path from 'path';
25 | import { Feature } from './types.js';
26 |
27 | /**
28 | * Document types that can be stored
29 | */
30 | export enum DocumentType {
31 | PRD = 'prd',
32 | IMPLEMENTATION_PLAN = 'implementation-plan'
33 | }
34 |
35 | /**
36 | * Document storage options
37 | */
38 | export interface DocumentStorageOptions {
39 | /** Root directory for document storage */
40 | rootDir: string;
41 | /** Whether to automatically save documents to files */
42 | autoSave: boolean;
43 | /** Whether to create directories if they don't exist */
44 | createDirs: boolean;
45 | }
46 |
47 | /**
48 | * Document metadata
49 | */
50 | export interface DocumentMetadata {
51 | /** Document type */
52 | type: DocumentType;
53 | /** Feature ID this document belongs to */
54 | featureId: string;
55 | /** Timestamp when the document was generated */
56 | generatedAt: Date;
57 | /** Path to the file, if saved */
58 | filePath?: string;
59 | /** Whether the document has been saved to a file */
60 | isSaved: boolean;
61 | }
62 |
63 | /**
64 | * Document with its content and metadata
65 | */
66 | export interface Document {
67 | /** Document content */
68 | content: string;
69 | /** Document metadata */
70 | metadata: DocumentMetadata;
71 | }
72 |
73 | /**
74 | * Default document storage options
75 | */
76 | const DEFAULT_OPTIONS: DocumentStorageOptions = {
77 | rootDir: path.join(process.cwd(), 'documents'),
78 | autoSave: true,
79 | createDirs: true
80 | };
81 |
82 | /**
83 | * Document storage class
84 | */
85 | export class DocumentStorage {
86 | private options: DocumentStorageOptions;
87 | private documents: Map<string, Document> = new Map();
88 |
89 | /**
90 | * Create a new document storage instance
91 | * @param options Storage options
92 | */
93 | constructor(options: Partial<DocumentStorageOptions> = {}) {
94 | this.options = { ...DEFAULT_OPTIONS, ...options };
95 | }
96 |
97 | /**
98 | * Generate a document key
99 | * @param featureId Feature ID
100 | * @param type Document type
101 | * @returns Document key
102 | */
103 | private getDocumentKey(featureId: string, type: DocumentType): string {
104 | return `${featureId}:${type}`;
105 | }
106 |
107 | /**
108 | * Generate a file path for a document
109 | * @param featureId Feature ID
110 | * @param type Document type
111 | * @returns File path
112 | */
113 | private getDocumentPath(featureId: string, type: DocumentType): string {
114 | const featureDir = path.join(this.options.rootDir, featureId);
115 | const filename = `${type}.md`;
116 | return path.join(featureDir, filename);
117 | }
118 |
119 | /**
120 | * Validate a file path to prevent directory traversal attacks
121 | * @param filePath File path to validate
122 | * @returns Normalized file path if valid, throws an error otherwise
123 | */
124 | private validatePath(filePath: string): string {
125 | // Normalize the path to resolve .. and . segments
126 | const normalizedPath = path.normalize(filePath);
127 |
128 | // Convert both paths to absolute paths for comparison
129 | const absolutePath = path.isAbsolute(normalizedPath)
130 | ? normalizedPath
131 | : path.join(process.cwd(), normalizedPath);
132 |
133 | const rootDir = path.isAbsolute(this.options.rootDir)
134 | ? this.options.rootDir
135 | : path.join(process.cwd(), this.options.rootDir);
136 |
137 | // Check if the path is outside the root directory
138 | if (!absolutePath.startsWith(rootDir)) {
139 | throw new Error(`Invalid file path: Path must be within ${rootDir}`);
140 | }
141 |
142 | return normalizedPath;
143 | }
144 |
145 | /**
146 | * Validate a custom file path provided by a client
147 | * @param filePath File path to validate
148 | * @returns Normalized file path if valid, throws an error otherwise
149 | */
150 | public validateCustomPath(filePath: string): string {
151 | // First validate that it's within the root directory
152 | const normalizedPath = this.validatePath(filePath);
153 |
154 | // Additional validation for custom paths
155 | if (path.extname(normalizedPath) !== '.md') {
156 | throw new Error('Invalid file path: File must have .md extension');
157 | }
158 |
159 | return normalizedPath;
160 | }
161 |
162 | /**
163 | * Ensure directory exists, creating it if necessary and allowed
164 | * @param dirPath Directory path
165 | */
166 | private async ensureDir(dirPath: string): Promise<void> {
167 | try {
168 | await fs.access(dirPath);
169 | } catch (error) {
170 | if (this.options.createDirs) {
171 | await fs.mkdir(dirPath, { recursive: true });
172 | } else {
173 | throw new Error(`Directory does not exist: ${dirPath}`);
174 | }
175 | }
176 | }
177 |
178 | /**
179 | * Store a document
180 | * @param featureId Feature ID
181 | * @param type Document type
182 | * @param content Document content
183 | * @param autoSave Whether to automatically save the document to a file
184 | * @returns Stored document
185 | */
186 | public async storeDocument(
187 | featureId: string,
188 | type: DocumentType,
189 | content: string,
190 | autoSave: boolean = this.options.autoSave
191 | ): Promise<Document> {
192 | const key = this.getDocumentKey(featureId, type);
193 | const filePath = this.getDocumentPath(featureId, type);
194 |
195 | const metadata: DocumentMetadata = {
196 | type,
197 | featureId,
198 | generatedAt: new Date(),
199 | filePath,
200 | isSaved: false
201 | };
202 |
203 | const document: Document = {
204 | content,
205 | metadata
206 | };
207 |
208 | this.documents.set(key, document);
209 |
210 | if (autoSave) {
211 | try {
212 | await this.saveDocumentToFile(featureId, type);
213 | } catch (error) {
214 | console.error(`Failed to auto-save document: ${error}`);
215 | // Note that we still store the document in memory even if file save fails
216 | }
217 | }
218 |
219 | return document;
220 | }
221 |
222 | /**
223 | * Get a document
224 | * @param featureId Feature ID
225 | * @param type Document type
226 | * @returns Document or undefined if not found
227 | */
228 | public getDocument(featureId: string, type: DocumentType): Document | undefined {
229 | const key = this.getDocumentKey(featureId, type);
230 | return this.documents.get(key);
231 | }
232 |
233 | /**
234 | * Check if a document exists
235 | * @param featureId Feature ID
236 | * @param type Document type
237 | * @returns True if the document exists
238 | */
239 | public hasDocument(featureId: string, type: DocumentType): boolean {
240 | const key = this.getDocumentKey(featureId, type);
241 | return this.documents.has(key);
242 | }
243 |
244 | /**
245 | * Save a document to a file using its default path
246 | * @param featureId Feature ID
247 | * @param type Document type
248 | * @returns Path to the saved file
249 | */
250 | public async saveDocumentToFile(featureId: string, type: DocumentType): Promise<string> {
251 | const document = this.getDocument(featureId, type);
252 |
253 | if (!document) {
254 | throw new Error(`Document not found: ${featureId}:${type}`);
255 | }
256 |
257 | const filePath = document.metadata.filePath!;
258 | const dirPath = path.dirname(filePath);
259 |
260 | await this.ensureDir(dirPath);
261 | await fs.writeFile(filePath, document.content, 'utf-8');
262 |
263 | // Update metadata
264 | document.metadata.isSaved = true;
265 |
266 | return filePath;
267 | }
268 |
269 | /**
270 | * Save a document to a custom file path
271 | * @param featureId Feature ID
272 | * @param type Document type
273 | * @param customPath Custom file path
274 | * @returns Path to the saved file
275 | */
276 | public async saveDocumentToCustomPath(
277 | featureId: string,
278 | type: DocumentType,
279 | customPath: string
280 | ): Promise<string> {
281 | const document = this.getDocument(featureId, type);
282 |
283 | if (!document) {
284 | throw new Error(`Document not found: ${featureId}:${type}`);
285 | }
286 |
287 | const validatedPath = this.validateCustomPath(customPath);
288 | const dirPath = path.dirname(validatedPath);
289 |
290 | await this.ensureDir(dirPath);
291 | await fs.writeFile(validatedPath, document.content, 'utf-8');
292 |
293 | // Don't update metadata.filePath as we want to keep the default path
294 | // Just mark it as saved
295 | document.metadata.isSaved = true;
296 |
297 | return validatedPath;
298 | }
299 |
300 | /**
301 | * Get the default file path for a document
302 | * @param featureId Feature ID
303 | * @param type Document type
304 | * @returns Default file path
305 | */
306 | public getDefaultFilePath(featureId: string, type: DocumentType): string {
307 | return this.getDocumentPath(featureId, type);
308 | }
309 | }
310 |
311 | // Create a singleton instance
312 | export const documentStorage = new DocumentStorage();
```
--------------------------------------------------------------------------------
/src/index-updated.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * @file Vibe-Coder MCP Server
5 | * @version 0.2.0
6 | *
7 | * This MCP server implements a structured development workflow that helps
8 | * LLM-based coders build features in an organized, clean, and safe manner.
9 | *
10 | * Core functionalities:
11 | * - Feature request clarification through iterative questioning
12 | * - PRD and implementation plan generation
13 | * - Phased development with tasks and status tracking
14 | */
15 |
16 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
17 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18 | import {
19 | CallToolRequestSchema,
20 | ListResourcesRequestSchema,
21 | ListToolsRequestSchema,
22 | ReadResourceRequestSchema,
23 | ListPromptsRequestSchema,
24 | GetPromptRequestSchema,
25 | ResourceTemplate,
26 | } from "@modelcontextprotocol/sdk/types.js";
27 |
28 | // Import core modules
29 | import { Feature, FeatureStorage, PhaseStatus, Phase, Task, ClarificationResponse } from './types.js';
30 | import { features, storeFeature, getFeature, updateFeature, listFeatures } from './storage.js';
31 | import {
32 | DEFAULT_CLARIFICATION_QUESTIONS as CLARIFICATION_QUESTIONS,
33 | getNextClarificationQuestion,
34 | addClarificationResponse,
35 | formatClarificationResponses,
36 | isClarificationComplete,
37 | getClarificationStatus
38 | } from './clarification.js';
39 | import { generatePRD, generateImplementationPlan } from './documentation.js';
40 | import { createPhase, getPhase, updatePhaseStatus, getNextPhaseStatus, validatePhaseTransition, addTask, updateTaskStatus } from './phases.js';
41 | import { generateId, createFeatureObject, createPhaseObject, createTaskObject, generateFeatureProgressSummary, isValidPhaseStatus } from './utils.js';
42 | import { validateFeatureId, validatePhaseId, validateTaskId, validateFeaturePhaseTask, validateRequiredText, validatePhaseStatusValue } from './validators.js';
43 | import { toolRegistry, resourceRegistry } from './registry.js';
44 | import { registerToolHandlers } from './tool-handlers.js';
45 | import { registerResourceHandlers } from './resource-handlers.js';
46 | import { ErrorCode, createErrorResponse, createToolErrorResponse } from './errors.js';
47 |
48 | /**
49 | * Create an MCP server with capabilities for resources, tools, and prompts
50 | */
51 | const server = new Server(
52 | {
53 | name: "Vibe-Coder MCP Server",
54 | version: "0.2.0",
55 | },
56 | {
57 | capabilities: {
58 | resources: {}, // Expose resources for features, PRDs, and implementation plans
59 | tools: {}, // Provide tools for feature clarification and development
60 | prompts: {}, // Supply prompts for guiding the development process
61 | },
62 | }
63 | );
64 |
65 | /**
66 | * Initialize the server by registering all tool and resource handlers
67 | */
68 | function initializeServer() {
69 | // Register all tool handlers
70 | registerToolHandlers();
71 |
72 | // Register all resource handlers
73 | registerResourceHandlers();
74 |
75 | console.error("Vibe-Coder MCP Server initialized successfully");
76 | }
77 |
78 | /**
79 | * Handler for listing available resources.
80 | */
81 | server.setRequestHandler(ListResourcesRequestSchema, async () => {
82 | return {
83 | resources: [
84 | {
85 | uri: "features://list",
86 | mimeType: "text/plain",
87 | name: "Features List",
88 | description: "Lists all features being developed"
89 | },
90 | {
91 | uri: "features://status",
92 | mimeType: "text/markdown",
93 | name: "Project Status",
94 | description: "Provides a summary of all features and their development status"
95 | },
96 | ...listFeatures().flatMap(feature => [
97 | {
98 | uri: `feature://${feature.id}`,
99 | mimeType: "text/plain",
100 | name: feature.name,
101 | description: `Details about feature: ${feature.name}`
102 | },
103 | {
104 | uri: `feature://${feature.id}/progress`,
105 | mimeType: "text/markdown",
106 | name: `${feature.name} Progress Report`,
107 | description: `Detailed progress report for feature: ${feature.name}`
108 | },
109 | {
110 | uri: `feature://${feature.id}/prd`,
111 | mimeType: "text/markdown",
112 | name: `${feature.name} PRD`,
113 | description: `PRD document for feature: ${feature.name}`
114 | },
115 | {
116 | uri: `feature://${feature.id}/implementation`,
117 | mimeType: "text/markdown",
118 | name: `${feature.name} Implementation Plan`,
119 | description: `Implementation plan for feature: ${feature.name}`
120 | },
121 | {
122 | uri: `feature://${feature.id}/phases`,
123 | mimeType: "text/plain",
124 | name: `${feature.name} Phases`,
125 | description: `Lists all phases for feature: ${feature.name}`
126 | },
127 | {
128 | uri: `feature://${feature.id}/tasks`,
129 | mimeType: "text/plain",
130 | name: `${feature.name} All Tasks`,
131 | description: `Lists all tasks across all phases for feature: ${feature.name}`
132 | },
133 | ...feature.phases.flatMap(phase => [
134 | {
135 | uri: `feature://${feature.id}/phase/${phase.id}`,
136 | mimeType: "text/plain",
137 | name: `${feature.name} - ${phase.name}`,
138 | description: `Details about phase: ${phase.name} for feature: ${feature.name}`
139 | },
140 | {
141 | uri: `feature://${feature.id}/phase/${phase.id}/tasks`,
142 | mimeType: "text/plain",
143 | name: `${feature.name} - ${phase.name} Tasks`,
144 | description: `Lists all tasks for phase: ${phase.name}`
145 | },
146 | ...phase.tasks.map(task => ({
147 | uri: `feature://${feature.id}/phase/${phase.id}/task/${task.id}`,
148 | mimeType: "text/plain",
149 | name: `Task: ${task.description.substring(0, 30)}${task.description.length > 30 ? '...' : ''}`,
150 | description: `Details about task: ${task.description.substring(0, 50)}${task.description.length > 50 ? '...' : ''}`
151 | }))
152 | ])
153 | ])
154 | ]
155 | };
156 | });
157 |
158 | /**
159 | * Handler for reading feature resources.
160 | */
161 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
162 | try {
163 | const uri = request.params.uri;
164 |
165 | const match = resourceRegistry.findMatch(uri);
166 |
167 | if (match) {
168 | return await match.handler(new URL(uri), match.params);
169 | }
170 |
171 | return createErrorResponse(
172 | ErrorCode.RESOURCE_NOT_FOUND,
173 | `Resource not found: ${uri}`
174 | );
175 | } catch (error: any) {
176 | console.error(`Error reading resource ${request.params.uri}:`, error);
177 | return createErrorResponse(
178 | ErrorCode.INTERNAL_ERROR,
179 | error.message || 'Unknown error'
180 | );
181 | }
182 | });
183 |
184 | /**
185 | * Handler that lists available tools.
186 | */
187 | server.setRequestHandler(ListToolsRequestSchema, async () => {
188 | return {
189 | tools: toolRegistry.listTools()
190 | };
191 | });
192 |
193 | /**
194 | * Handler for implementing MCP tools.
195 | */
196 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
197 | try {
198 | const toolName = request.params.name;
199 | const toolArguments = request.params.arguments || {};
200 |
201 | // Execute the tool using the tool registry
202 | return await toolRegistry.execute(toolName, toolArguments);
203 | } catch (error: any) {
204 | console.error('Error executing tool:', error);
205 | return createToolErrorResponse(`An unexpected error occurred: ${error.message || 'Unknown error'}`);
206 | }
207 | });
208 |
209 | /**
210 | * Handler that lists available prompts.
211 | */
212 | server.setRequestHandler(ListPromptsRequestSchema, async () => {
213 | return {
214 | prompts: [
215 | {
216 | name: "clarify_feature",
217 | description: "Guide to clarify a feature request through questioning"
218 | }
219 | ]
220 | };
221 | });
222 |
223 | /**
224 | * Handler for the clarify_feature prompt.
225 | */
226 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
227 | if (request.params.name === "clarify_feature") {
228 | return {
229 | messages: [
230 | {
231 | role: "user",
232 | content: {
233 | type: "text",
234 | text: "Help me clarify this feature request by asking questions about:"
235 | }
236 | },
237 | {
238 | role: "user",
239 | content: {
240 | type: "text",
241 | 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."
242 | }
243 | }
244 | ]
245 | };
246 | }
247 |
248 | return createErrorResponse(
249 | ErrorCode.PROMPT_NOT_FOUND,
250 | `Unknown prompt: ${request.params.name}`
251 | );
252 | });
253 |
254 | /**
255 | * Start the server using stdio transport.
256 | */
257 | async function main() {
258 | console.error("Starting Vibe-Coder MCP Server...");
259 |
260 | // Initialize the server
261 | initializeServer();
262 |
263 | const transport = new StdioServerTransport();
264 | await server.connect(transport);
265 | }
266 |
267 | main().catch((error) => {
268 | console.error("Server error:", error);
269 | process.exit(1);
270 | });
```
--------------------------------------------------------------------------------
/src/phases.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Phase management module for the Vibe-Coder MCP Server.
3 | * This module handles the creation and management of development phases and tasks.
4 | */
5 | import { Feature, Phase, Task, PhaseStatus } from './types.js';
6 | import { updateFeature, getFeature } from './storage.js';
7 | import { generateId, createPhaseObject, createTaskObject, isValidPhaseStatus, now } from './utils.js';
8 |
9 | /**
10 | * Define valid phase status transitions
11 | * A map of current status to valid next statuses
12 | */
13 | const VALID_PHASE_TRANSITIONS: Record<PhaseStatus, PhaseStatus[]> = {
14 | 'pending': ['in_progress'],
15 | 'in_progress': ['completed'],
16 | 'completed': ['reviewed'],
17 | 'reviewed': []
18 | };
19 |
20 | /**
21 | * Create a new phase for a feature
22 | * @param featureId The ID of the feature to create the phase for
23 | * @param name The name of the phase
24 | * @param description The description of the phase
25 | * @returns The created phase or undefined if the feature is not found
26 | */
27 | export function createPhase(
28 | featureId: string,
29 | name: string,
30 | description: string
31 | ): Phase | undefined {
32 | const feature = getFeature(featureId);
33 | if (!feature) return undefined;
34 |
35 | const newPhase = createPhaseObject(name, description);
36 |
37 | updateFeature(featureId, {
38 | phases: [...feature.phases, newPhase]
39 | });
40 |
41 | return newPhase;
42 | }
43 |
44 | /**
45 | * Get a phase by ID
46 | * @param featureId The ID of the feature
47 | * @param phaseId The ID of the phase to get
48 | * @returns The phase or undefined if not found
49 | */
50 | export function getPhase(
51 | featureId: string,
52 | phaseId: string
53 | ): Phase | undefined {
54 | const feature = getFeature(featureId);
55 | if (!feature) return undefined;
56 |
57 | return feature.phases.find(p => p.id === phaseId);
58 | }
59 |
60 | /**
61 | * Add a new phase to a feature
62 | * @param featureId The ID of the feature to add the phase to
63 | * @param name The name of the phase
64 | * @param description The description of the phase
65 | * @returns The updated feature or undefined if not found
66 | */
67 | export function addPhase(
68 | featureId: string,
69 | name: string,
70 | description: string
71 | ): Feature | undefined {
72 | const feature = getFeature(featureId);
73 | if (!feature) return undefined;
74 |
75 | const newPhase = createPhaseObject(name, description);
76 |
77 | return updateFeature(featureId, {
78 | phases: [...feature.phases, newPhase]
79 | });
80 | }
81 |
82 | /**
83 | * Add a task to a phase
84 | * @param featureId The ID of the feature
85 | * @param phaseId The ID of the phase to add the task to
86 | * @param description The description of the task
87 | * @returns The updated feature or undefined if not found
88 | */
89 | export function addTask(
90 | featureId: string,
91 | phaseId: string,
92 | description: string
93 | ): Feature | undefined {
94 | const feature = getFeature(featureId);
95 | if (!feature) return undefined;
96 |
97 | const phaseIndex = feature.phases.findIndex(p => p.id === phaseId);
98 | if (phaseIndex === -1) return undefined;
99 |
100 | const newTask = createTaskObject(description);
101 |
102 | const updatedPhases = [...feature.phases];
103 | updatedPhases[phaseIndex] = {
104 | ...updatedPhases[phaseIndex],
105 | tasks: [...updatedPhases[phaseIndex].tasks, newTask],
106 | updatedAt: now()
107 | };
108 |
109 | return updateFeature(featureId, { phases: updatedPhases });
110 | }
111 |
112 | /**
113 | * Update the status of a phase
114 | * @param featureId The ID of the feature
115 | * @param phaseId The ID of the phase to update
116 | * @param status The new status
117 | * @returns The updated feature or undefined if not found
118 | */
119 | export function updatePhaseStatus(
120 | featureId: string,
121 | phaseId: string,
122 | status: PhaseStatus
123 | ): Feature | undefined {
124 | const feature = getFeature(featureId);
125 | if (!feature) return undefined;
126 |
127 | const phaseIndex = feature.phases.findIndex(p => p.id === phaseId);
128 | if (phaseIndex === -1) return undefined;
129 |
130 | // Validate the status transition
131 | const phase = feature.phases[phaseIndex];
132 | const validationResult = validatePhaseTransition(phase.status, status);
133 | if (!validationResult.valid) {
134 | console.error(validationResult.message);
135 | return undefined;
136 | }
137 |
138 | const updatedPhases = [...feature.phases];
139 | updatedPhases[phaseIndex] = {
140 | ...updatedPhases[phaseIndex],
141 | status,
142 | updatedAt: now()
143 | };
144 |
145 | return updateFeature(featureId, { phases: updatedPhases });
146 | }
147 |
148 | /**
149 | * Update a task's completion status
150 | * @param featureId The ID of the feature
151 | * @param phaseId The ID of the phase
152 | * @param taskId The ID of the task to update
153 | * @param completed Whether the task is completed
154 | * @returns The updated feature or undefined if not found
155 | */
156 | export function updateTaskStatus(
157 | featureId: string,
158 | phaseId: string,
159 | taskId: string,
160 | completed: boolean
161 | ): Feature | undefined {
162 | const feature = getFeature(featureId);
163 | if (!feature) return undefined;
164 |
165 | const phaseIndex = feature.phases.findIndex(p => p.id === phaseId);
166 | if (phaseIndex === -1) return undefined;
167 |
168 | const taskIndex = feature.phases[phaseIndex].tasks.findIndex(t => t.id === taskId);
169 | if (taskIndex === -1) return undefined;
170 |
171 | const updatedPhases = [...feature.phases];
172 | const updatedTasks = [...updatedPhases[phaseIndex].tasks];
173 |
174 | updatedTasks[taskIndex] = {
175 | ...updatedTasks[taskIndex],
176 | completed,
177 | updatedAt: now()
178 | };
179 |
180 | updatedPhases[phaseIndex] = {
181 | ...updatedPhases[phaseIndex],
182 | tasks: updatedTasks,
183 | updatedAt: now()
184 | };
185 |
186 | return updateFeature(featureId, { phases: updatedPhases });
187 | }
188 |
189 | /**
190 | * Validate a phase status transition
191 | * @param currentStatus The current status
192 | * @param newStatus The proposed new status
193 | * @returns An object with valid flag and message
194 | */
195 | export function validatePhaseTransition(
196 | currentStatus: PhaseStatus,
197 | newStatus: PhaseStatus
198 | ): { valid: boolean; message: string } {
199 | // Allow same status
200 | if (currentStatus === newStatus) {
201 | return { valid: true, message: "Status unchanged" };
202 | }
203 |
204 | // Check if the status is valid
205 | if (!isValidPhaseStatus(newStatus)) {
206 | return {
207 | valid: false,
208 | message: `Invalid status: ${newStatus}. Valid statuses are: pending, in_progress, completed, reviewed`
209 | };
210 | }
211 |
212 | // Check if the transition is valid
213 | const validNextStatuses = VALID_PHASE_TRANSITIONS[currentStatus];
214 | if (!validNextStatuses.includes(newStatus)) {
215 | return {
216 | valid: false,
217 | message: `Invalid transition from ${currentStatus} to ${newStatus}. Valid next statuses are: ${validNextStatuses.join(', ')}`
218 | };
219 | }
220 |
221 | return { valid: true, message: "Valid transition" };
222 | }
223 |
224 | /**
225 | * Get the next logical status for a phase
226 | * @param currentStatus The current status
227 | * @returns The recommended next status, or null if no valid transition
228 | */
229 | export function getNextPhaseStatus(currentStatus: PhaseStatus): PhaseStatus | null {
230 | const validNextStatuses = VALID_PHASE_TRANSITIONS[currentStatus];
231 |
232 | if (validNextStatuses.length === 0) {
233 | return null;
234 | }
235 |
236 | return validNextStatuses[0];
237 | }
238 |
239 | /**
240 | * Get a summary of the phases for a feature
241 | * @param featureId The ID of the feature
242 | * @returns A text summary of the phases
243 | */
244 | export function getPhaseSummary(featureId: string): string {
245 | const feature = getFeature(featureId);
246 | if (!feature) return "Feature not found";
247 |
248 | if (feature.phases.length === 0) {
249 | return "No phases defined for this feature yet.";
250 | }
251 |
252 | const phasesSummary = feature.phases.map(phase => {
253 | const completedTasks = phase.tasks.filter(t => t.completed).length;
254 | const totalTasks = phase.tasks.length;
255 | return `${phase.name} (${phase.status}): ${completedTasks}/${totalTasks} tasks completed`;
256 | }).join('\n');
257 |
258 | return `Phases for ${feature.name}:\n${phasesSummary}`;
259 | }
260 |
261 | /**
262 | * Initialize default phases for a feature based on its implementation plan
263 | * @param featureId The ID of the feature
264 | * @returns The updated feature or undefined if not found
265 | */
266 | export function initializeDefaultPhases(featureId: string): Feature | undefined {
267 | const feature = getFeature(featureId);
268 | if (!feature) return undefined;
269 |
270 | // If the feature already has phases, don't override them
271 | if (feature.phases && feature.phases.length > 0) {
272 | return feature;
273 | }
274 |
275 | // Create default phases
276 | const defaultPhases = [
277 | createPhaseObject(
278 | "Requirements Analysis and Design",
279 | "Analyze requirements, design the architecture, and create a detailed implementation plan."
280 | ),
281 | createPhaseObject(
282 | "Core Implementation",
283 | "Implement the core functionality based on the design."
284 | ),
285 | createPhaseObject(
286 | "Testing and Integration",
287 | "Test all components, integrate with existing systems, and refine the implementation."
288 | ),
289 | createPhaseObject(
290 | "Documentation and Finalization",
291 | "Finalize documentation, clean up code, and prepare for deployment."
292 | )
293 | ];
294 |
295 | // Add default tasks for each phase
296 | const phase1 = defaultPhases[0];
297 | addTask(featureId, phase1.id, "Review and analyze clarification responses");
298 | addTask(featureId, phase1.id, "Identify key components and their interactions");
299 | addTask(featureId, phase1.id, "Design the system architecture");
300 | addTask(featureId, phase1.id, "Create UML diagrams if necessary");
301 | addTask(featureId, phase1.id, "Identify potential edge cases and risks");
302 |
303 | const phase2 = defaultPhases[1];
304 | addTask(featureId, phase2.id, "Set up project structure and dependencies");
305 | addTask(featureId, phase2.id, "Implement data models and interfaces");
306 | addTask(featureId, phase2.id, "Build core business logic");
307 | addTask(featureId, phase2.id, "Create unit tests for core functionality");
308 | addTask(featureId, phase2.id, "Ensure code follows best practices");
309 |
310 | const phase3 = defaultPhases[2];
311 | addTask(featureId, phase3.id, "Write unit tests for all components");
312 | addTask(featureId, phase3.id, "Perform integration testing");
313 | addTask(featureId, phase3.id, "Fix bugs and edge cases");
314 | addTask(featureId, phase3.id, "Optimize performance");
315 | addTask(featureId, phase3.id, "Document any known limitations");
316 |
317 | const phase4 = defaultPhases[3];
318 | addTask(featureId, phase4.id, "Complete inline code documentation");
319 | addTask(featureId, phase4.id, "Create user documentation");
320 | addTask(featureId, phase4.id, "Clean up and refactor code");
321 | addTask(featureId, phase4.id, "Prepare deployment strategy");
322 | addTask(featureId, phase4.id, "Create final pull request");
323 |
324 | return updateFeature(featureId, { phases: defaultPhases });
325 | }
```
--------------------------------------------------------------------------------
/MCP-Typescript-readme.txt:
--------------------------------------------------------------------------------
```
1 | MCP TypeScript SDK NPM Version MIT licensed
2 | Table of Contents
3 | Overview
4 | Installation
5 | Quickstart
6 | What is MCP?
7 | Core Concepts
8 | Server
9 | Resources
10 | Tools
11 | Prompts
12 | Running Your Server
13 | stdio
14 | HTTP with SSE
15 | Testing and Debugging
16 | Examples
17 | Echo Server
18 | SQLite Explorer
19 | Advanced Usage
20 | Low-Level Server
21 | Writing MCP Clients
22 | Server Capabilities
23 | Overview
24 | 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:
25 |
26 | Build MCP clients that can connect to any MCP server
27 | Create MCP servers that expose resources, prompts and tools
28 | Use standard transports like stdio and SSE
29 | Handle all MCP protocol messages and lifecycle events
30 | Installation
31 | npm install @modelcontextprotocol/sdk
32 | Quick Start
33 | Let's create a simple MCP server that exposes a calculator tool and some data:
34 |
35 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
36 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
37 | import { z } from "zod";
38 |
39 | // Create an MCP server
40 | const server = new McpServer({
41 | name: "Demo",
42 | version: "1.0.0"
43 | });
44 |
45 | // Add an addition tool
46 | server.tool("add",
47 | { a: z.number(), b: z.number() },
48 | async ({ a, b }) => ({
49 | content: [{ type: "text", text: String(a + b) }]
50 | })
51 | );
52 |
53 | // Add a dynamic greeting resource
54 | server.resource(
55 | "greeting",
56 | new ResourceTemplate("greeting://{name}", { list: undefined }),
57 | async (uri, { name }) => ({
58 | contents: [{
59 | uri: uri.href,
60 | text: `Hello, ${name}!`
61 | }]
62 | })
63 | );
64 |
65 | // Start receiving messages on stdin and sending messages on stdout
66 | const transport = new StdioServerTransport();
67 | await server.connect(transport);
68 | What is MCP?
69 | 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:
70 |
71 | Expose data through Resources (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
72 | Provide functionality through Tools (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
73 | Define interaction patterns through Prompts (reusable templates for LLM interactions)
74 | And more!
75 | Core Concepts
76 | Server
77 | The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
78 |
79 | const server = new McpServer({
80 | name: "My App",
81 | version: "1.0.0"
82 | });
83 | Resources
84 | 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:
85 |
86 | // Static resource
87 | server.resource(
88 | "config",
89 | "config://app",
90 | async (uri) => ({
91 | contents: [{
92 | uri: uri.href,
93 | text: "App configuration here"
94 | }]
95 | })
96 | );
97 |
98 | // Dynamic resource with parameters
99 | server.resource(
100 | "user-profile",
101 | new ResourceTemplate("users://{userId}/profile", { list: undefined }),
102 | async (uri, { userId }) => ({
103 | contents: [{
104 | uri: uri.href,
105 | text: `Profile data for user ${userId}`
106 | }]
107 | })
108 | );
109 | Tools
110 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
111 |
112 | // Simple tool with parameters
113 | server.tool(
114 | "calculate-bmi",
115 | {
116 | weightKg: z.number(),
117 | heightM: z.number()
118 | },
119 | async ({ weightKg, heightM }) => ({
120 | content: [{
121 | type: "text",
122 | text: String(weightKg / (heightM * heightM))
123 | }]
124 | })
125 | );
126 |
127 | // Async tool with external API call
128 | server.tool(
129 | "fetch-weather",
130 | { city: z.string() },
131 | async ({ city }) => {
132 | const response = await fetch(`https://api.weather.com/${city}`);
133 | const data = await response.text();
134 | return {
135 | content: [{ type: "text", text: data }]
136 | };
137 | }
138 | );
139 | Prompts
140 | Prompts are reusable templates that help LLMs interact with your server effectively:
141 |
142 | server.prompt(
143 | "review-code",
144 | { code: z.string() },
145 | ({ code }) => ({
146 | messages: [{
147 | role: "user",
148 | content: {
149 | type: "text",
150 | text: `Please review this code:\n\n${code}`
151 | }
152 | }]
153 | })
154 | );
155 | Running Your Server
156 | 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:
157 |
158 | stdio
159 | For command-line tools and direct integrations:
160 |
161 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
162 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
163 |
164 | const server = new McpServer({
165 | name: "example-server",
166 | version: "1.0.0"
167 | });
168 |
169 | // ... set up server resources, tools, and prompts ...
170 |
171 | const transport = new StdioServerTransport();
172 | await server.connect(transport);
173 | HTTP with SSE
174 | 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:
175 |
176 | import express from "express";
177 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
178 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
179 |
180 | const server = new McpServer({
181 | name: "example-server",
182 | version: "1.0.0"
183 | });
184 |
185 | // ... set up server resources, tools, and prompts ...
186 |
187 | const app = express();
188 |
189 | app.get("/sse", async (req, res) => {
190 | const transport = new SSEServerTransport("/messages", res);
191 | await server.connect(transport);
192 | });
193 |
194 | app.post("/messages", async (req, res) => {
195 | // Note: to support multiple simultaneous connections, these messages will
196 | // need to be routed to a specific matching transport. (This logic isn't
197 | // implemented here, for simplicity.)
198 | await transport.handlePostMessage(req, res);
199 | });
200 |
201 | app.listen(3001);
202 | Testing and Debugging
203 | To test your server, you can use the MCP Inspector. See its README for more information.
204 |
205 | Examples
206 | Echo Server
207 | A simple server demonstrating resources, tools, and prompts:
208 |
209 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
210 | import { z } from "zod";
211 |
212 | const server = new McpServer({
213 | name: "Echo",
214 | version: "1.0.0"
215 | });
216 |
217 | server.resource(
218 | "echo",
219 | new ResourceTemplate("echo://{message}", { list: undefined }),
220 | async (uri, { message }) => ({
221 | contents: [{
222 | uri: uri.href,
223 | text: `Resource echo: ${message}`
224 | }]
225 | })
226 | );
227 |
228 | server.tool(
229 | "echo",
230 | { message: z.string() },
231 | async ({ message }) => ({
232 | content: [{ type: "text", text: `Tool echo: ${message}` }]
233 | })
234 | );
235 |
236 | server.prompt(
237 | "echo",
238 | { message: z.string() },
239 | ({ message }) => ({
240 | messages: [{
241 | role: "user",
242 | content: {
243 | type: "text",
244 | text: `Please process this message: ${message}`
245 | }
246 | }]
247 | })
248 | );
249 | SQLite Explorer
250 | A more complex example showing database integration:
251 |
252 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
253 | import sqlite3 from "sqlite3";
254 | import { promisify } from "util";
255 | import { z } from "zod";
256 |
257 | const server = new McpServer({
258 | name: "SQLite Explorer",
259 | version: "1.0.0"
260 | });
261 |
262 | // Helper to create DB connection
263 | const getDb = () => {
264 | const db = new sqlite3.Database("database.db");
265 | return {
266 | all: promisify<string, any[]>(db.all.bind(db)),
267 | close: promisify(db.close.bind(db))
268 | };
269 | };
270 |
271 | server.resource(
272 | "schema",
273 | "schema://main",
274 | async (uri) => {
275 | const db = getDb();
276 | try {
277 | const tables = await db.all(
278 | "SELECT sql FROM sqlite_master WHERE type='table'"
279 | );
280 | return {
281 | contents: [{
282 | uri: uri.href,
283 | text: tables.map((t: {sql: string}) => t.sql).join("\n")
284 | }]
285 | };
286 | } finally {
287 | await db.close();
288 | }
289 | }
290 | );
291 |
292 | server.tool(
293 | "query",
294 | { sql: z.string() },
295 | async ({ sql }) => {
296 | const db = getDb();
297 | try {
298 | const results = await db.all(sql);
299 | return {
300 | content: [{
301 | type: "text",
302 | text: JSON.stringify(results, null, 2)
303 | }]
304 | };
305 | } catch (err: unknown) {
306 | const error = err as Error;
307 | return {
308 | content: [{
309 | type: "text",
310 | text: `Error: ${error.message}`
311 | }],
312 | isError: true
313 | };
314 | } finally {
315 | await db.close();
316 | }
317 | }
318 | );
319 | Advanced Usage
320 | Low-Level Server
321 | For more control, you can use the low-level Server class directly:
322 |
323 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
324 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
325 | import {
326 | ListPromptsRequestSchema,
327 | GetPromptRequestSchema
328 | } from "@modelcontextprotocol/sdk/types.js";
329 |
330 | const server = new Server(
331 | {
332 | name: "example-server",
333 | version: "1.0.0"
334 | },
335 | {
336 | capabilities: {
337 | prompts: {}
338 | }
339 | }
340 | );
341 |
342 | server.setRequestHandler(ListPromptsRequestSchema, async () => {
343 | return {
344 | prompts: [{
345 | name: "example-prompt",
346 | description: "An example prompt template",
347 | arguments: [{
348 | name: "arg1",
349 | description: "Example argument",
350 | required: true
351 | }]
352 | }]
353 | };
354 | });
355 |
356 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
357 | if (request.params.name !== "example-prompt") {
358 | throw new Error("Unknown prompt");
359 | }
360 | return {
361 | description: "Example prompt",
362 | messages: [{
363 | role: "user",
364 | content: {
365 | type: "text",
366 | text: "Example prompt text"
367 | }
368 | }]
369 | };
370 | });
371 |
372 | const transport = new StdioServerTransport();
373 | await server.connect(transport);
374 | Writing MCP Clients
375 | The SDK provides a high-level client interface:
376 |
377 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
378 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
379 |
380 | const transport = new StdioClientTransport({
381 | command: "node",
382 | args: ["server.js"]
383 | });
384 |
385 | const client = new Client(
386 | {
387 | name: "example-client",
388 | version: "1.0.0"
389 | },
390 | {
391 | capabilities: {
392 | prompts: {},
393 | resources: {},
394 | tools: {}
395 | }
396 | }
397 | );
398 |
399 | await client.connect(transport);
400 |
401 | // List prompts
402 | const prompts = await client.listPrompts();
403 |
404 | // Get a prompt
405 | const prompt = await client.getPrompt("example-prompt", {
406 | arg1: "value"
407 | });
408 |
409 | // List resources
410 | const resources = await client.listResources();
411 |
412 | // Read a resource
413 | const resource = await client.readResource("file:///example.txt");
414 |
415 | // Call a tool
416 | const result = await client.callTool({
417 | name: "example-tool",
418 | arguments: {
419 | arg1: "value"
420 | }
421 | });
422 |
```
--------------------------------------------------------------------------------
/src/documentation.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @file documentation.ts
3 | * @version 1.1.0
4 | * @status STABLE - DO NOT MODIFY WITHOUT TESTS
5 | * @lastModified 2023-03-23
6 | *
7 | * Documentation module for the Vibe-Coder MCP Server.
8 | * This module handles PRD and implementation plan generation
9 | * based on clarification responses.
10 | *
11 | * IMPORTANT:
12 | * - Document generation uses structured templates
13 | * - Output is stored both in-memory and in the file system
14 | *
15 | * Functionality:
16 | * - PRD generation
17 | * - Implementation plan generation
18 | * - Document storage via DocumentStorage module
19 | */
20 |
21 | import { Feature, ClarificationResponse } from './types.js';
22 | import { formatDate } from './utils.js';
23 | import { documentStorage, DocumentType } from './document-storage.js';
24 |
25 | /**
26 | * Extract objectives from clarification responses
27 | * @param responses Clarification responses
28 | * @returns Array of objectives
29 | */
30 | export function extractObjectivesFromClarifications(responses: ClarificationResponse[]): string[] {
31 | // Find the response to the problem question
32 | const problemResponse = responses.find(r =>
33 | r.question.toLowerCase().includes('what specific problem') ||
34 | r.question.toLowerCase().includes('what problem')
35 | );
36 |
37 | if (!problemResponse) return [];
38 |
39 | // Extract objectives from the problem statement
40 | const answer = problemResponse.answer;
41 | const objectives: string[] = [];
42 |
43 | // If the response has numbered points or bullet points
44 | if (answer.includes('\n- ') || answer.includes('\n* ') || /\n\d+\./.test(answer)) {
45 | const lines = answer.split('\n').map(line => line.trim());
46 |
47 | for (const line of lines) {
48 | // Look for bullet points or numbered lists
49 | if (line.startsWith('- ') || line.startsWith('* ') || /^\d+\./.test(line)) {
50 | // Clean up the line (remove the bullet or number)
51 | const cleanLine = line.replace(/^- |^\* |^\d+\.\s*/, '');
52 | objectives.push(cleanLine);
53 | }
54 | }
55 | } else {
56 | // No clear bullet points, try to extract sentences
57 | const sentences = answer.split(/\.(?!\d)/g).map(s => s.trim()).filter(s => s.length > 10);
58 | objectives.push(...sentences);
59 | }
60 |
61 | return objectives;
62 | }
63 |
64 | /**
65 | * Extract requirements from clarification responses
66 | * @param responses Clarification responses
67 | * @returns Array of requirements
68 | */
69 | export function extractRequirementsFromClarifications(responses: ClarificationResponse[]): string[] {
70 | // Find the response to the requirements question
71 | const requirementsResponse = responses.find(r =>
72 | r.question.toLowerCase().includes('key requirements')
73 | );
74 |
75 | if (!requirementsResponse) return [];
76 |
77 | // Extract requirements from the response
78 | const answer = requirementsResponse.answer;
79 | const requirements: string[] = [];
80 |
81 | // If the response has numbered points or bullet points
82 | if (answer.includes('\n- ') || answer.includes('\n* ') || /\n\d+\./.test(answer)) {
83 | const lines = answer.split('\n').map(line => line.trim());
84 |
85 | for (const line of lines) {
86 | // Look for bullet points or numbered lists
87 | if (line.startsWith('- ') || line.startsWith('* ') || /^\d+\.\s*/.test(line)) {
88 | // Clean up the line (remove the bullet or number)
89 | let cleanLine = line.replace(/^- |^\* |^\d+\.\s*/, '');
90 |
91 | // Check if the line itself contains a nested list
92 | if (cleanLine.includes(':')) {
93 | const [req, details] = cleanLine.split(':');
94 | requirements.push(req.trim());
95 |
96 | // If there are nested items, add them as separate requirements
97 | if (details && (details.includes(', ') || details.includes(' and '))) {
98 | const nestedItems = details
99 | .split(/,\s*|\s+and\s+/)
100 | .map(item => item.trim())
101 | .filter(Boolean);
102 |
103 | for (const item of nestedItems) {
104 | requirements.push(`${req.trim()} - ${item}`);
105 | }
106 | }
107 | } else {
108 | requirements.push(cleanLine);
109 | }
110 | }
111 | }
112 | } else {
113 | // No clear bullet points, try to extract sentences
114 | const sentences = answer.split(/\.(?!\d)/g).map(s => s.trim()).filter(s => s.length > 10);
115 | requirements.push(...sentences);
116 | }
117 |
118 | return requirements;
119 | }
120 |
121 | /**
122 | * Extract technical specifications from clarification responses
123 | * @param responses Clarification responses
124 | * @returns Array of technical specifications
125 | */
126 | export function extractTechnicalSpecsFromClarifications(responses: ClarificationResponse[]): string[] {
127 | // Find the response to the technical constraints question
128 | const technicalResponse = responses.find(r =>
129 | r.question.toLowerCase().includes('technical constraints') ||
130 | r.question.toLowerCase().includes('technical considerations')
131 | );
132 |
133 | if (!technicalResponse) return [];
134 |
135 | // Extract technical specifications from the response
136 | const answer = technicalResponse.answer;
137 | const specs: string[] = [];
138 |
139 | // If the response has numbered points or bullet points
140 | if (answer.includes('\n- ') || answer.includes('\n* ') || /\n\d+\./.test(answer)) {
141 | const lines = answer.split('\n').map(line => line.trim());
142 |
143 | for (const line of lines) {
144 | // Look for bullet points or numbered lists
145 | if (line.startsWith('- ') || line.startsWith('* ') || /^\d+\.\s*/.test(line)) {
146 | // Clean up the line (remove the bullet or number)
147 | const cleanLine = line.replace(/^- |^\* |^\d+\.\s*/, '');
148 | specs.push(cleanLine);
149 | }
150 | }
151 | } else {
152 | // No clear bullet points, try to extract sentences
153 | const sentences = answer.split(/\.(?!\d)/g).map(s => s.trim()).filter(s => s.length > 10);
154 | specs.push(...sentences);
155 | }
156 |
157 | return specs;
158 | }
159 |
160 | /**
161 | * Generate a Product Requirements Document (PRD) for a feature
162 | * @param feature The feature to generate a PRD for
163 | * @returns The PRD document as a markdown string
164 | */
165 | export function generatePRD(feature: Feature): string {
166 | const { name, description, clarificationResponses } = feature;
167 |
168 | // Find specific clarification responses
169 | const problemResponse = clarificationResponses.find(r =>
170 | r.question.toLowerCase().includes('what specific problem') ||
171 | r.question.toLowerCase().includes('what problem')
172 | );
173 |
174 | const usersResponse = clarificationResponses.find(r =>
175 | r.question.toLowerCase().includes('target users')
176 | );
177 |
178 | const requirementsResponse = clarificationResponses.find(r =>
179 | r.question.toLowerCase().includes('key requirements')
180 | );
181 |
182 | const technicalResponse = clarificationResponses.find(r =>
183 | r.question.toLowerCase().includes('technical constraints') ||
184 | r.question.toLowerCase().includes('technical considerations')
185 | );
186 |
187 | const metricsResponse = clarificationResponses.find(r =>
188 | r.question.toLowerCase().includes('measure') &&
189 | r.question.toLowerCase().includes('success')
190 | );
191 |
192 | const dependenciesResponse = clarificationResponses.find(r =>
193 | r.question.toLowerCase().includes('dependencies')
194 | );
195 |
196 | const risksResponse = clarificationResponses.find(r =>
197 | r.question.toLowerCase().includes('risks') ||
198 | r.question.toLowerCase().includes('challenges')
199 | );
200 |
201 | // Extract structured information
202 | const objectives = extractObjectivesFromClarifications(clarificationResponses);
203 | const requirements = extractRequirementsFromClarifications(clarificationResponses);
204 | const technicalSpecs = extractTechnicalSpecsFromClarifications(clarificationResponses);
205 |
206 | // Format the PRD
207 | const prd = `# ${name} - Product Requirements Document
208 |
209 | ## 1. Introduction
210 |
211 | ### 1.1 Purpose
212 | ${problemResponse ? problemResponse.answer : description}
213 |
214 | ### 1.2 Scope
215 | This document outlines the requirements and specifications for the ${name} feature.
216 |
217 | ### 1.3 Background
218 | ${description}
219 |
220 | ## 2. Product Overview
221 |
222 | ### 2.1 Product Description
223 | ${description}
224 |
225 | ### 2.2 Target Users
226 | ${usersResponse ? usersResponse.answer : 'To be determined during development.'}
227 |
228 | ## 3. Functional Requirements
229 |
230 | ${requirements.map((req, index) => `### 3.${index + 1} ${req}`).join('\n\n')}
231 |
232 | ## 4. Non-Functional Requirements
233 |
234 | ### 4.1 Technical Constraints
235 | ${technicalResponse ? technicalResponse.answer : 'No specific technical constraints identified.'}
236 |
237 | ### 4.2 Performance Requirements
238 | ${technicalSpecs.filter(spec =>
239 | spec.toLowerCase().includes('performance') ||
240 | spec.toLowerCase().includes('speed') ||
241 | spec.toLowerCase().includes('time') ||
242 | spec.toLowerCase().includes('fast')
243 | ).map(spec => `- ${spec}`).join('\n') || 'No specific performance requirements identified.'}
244 |
245 | ## 5. Success Metrics
246 |
247 | ### 5.1 Key Performance Indicators
248 | ${metricsResponse ? metricsResponse.answer : 'Success metrics to be determined.'}
249 |
250 | ## 6. Dependencies
251 |
252 | ### 6.1 System Dependencies
253 | ${dependenciesResponse ? dependenciesResponse.answer : 'No specific dependencies identified.'}
254 |
255 | ## 7. Risks and Challenges
256 |
257 | ### 7.1 Identified Risks
258 | ${risksResponse ? risksResponse.answer : 'Risks to be assessed during development.'}
259 |
260 | ## 8. Milestones and Implementation Plan
261 |
262 | Refer to the Implementation Plan document for detailed phases and tasks.
263 |
264 | ---
265 |
266 | Generated on: ${formatDate(new Date())}
267 | `;
268 |
269 | // Store the generated PRD in the document storage system
270 | documentStorage.storeDocument(feature.id, DocumentType.PRD, prd)
271 | .catch(error => console.error(`Failed to store PRD document: ${error}`));
272 |
273 | return prd;
274 | }
275 |
276 | /**
277 | * Generate an implementation plan for a feature
278 | * @param feature The feature to generate an implementation plan for
279 | * @returns The implementation plan as a markdown string
280 | */
281 | export function generateImplementationPlan(feature: Feature): string {
282 | const { name, description, clarificationResponses } = feature;
283 |
284 | // Find specific clarification responses
285 | const requirementsResponse = clarificationResponses.find(r =>
286 | r.question.toLowerCase().includes('key requirements')
287 | );
288 |
289 | const technicalResponse = clarificationResponses.find(r =>
290 | r.question.toLowerCase().includes('technical constraints') ||
291 | r.question.toLowerCase().includes('technical considerations')
292 | );
293 |
294 | const dependenciesResponse = clarificationResponses.find(r =>
295 | r.question.toLowerCase().includes('dependencies')
296 | );
297 |
298 | // Extract structured information
299 | const requirements = extractRequirementsFromClarifications(clarificationResponses);
300 | const technicalSpecs = extractTechnicalSpecsFromClarifications(clarificationResponses);
301 |
302 | // Format the implementation plan
303 | const implementationPlan = `# ${name} - Implementation Plan
304 |
305 | ## 1. Overview
306 |
307 | ${description}
308 |
309 | ## 2. Requirements Summary
310 |
311 | ${requirementsResponse ? requirementsResponse.answer : 'Requirements to be determined.'}
312 |
313 | ## 3. Technical Approach
314 |
315 | ${technicalResponse ? technicalResponse.answer : 'Technical approach to be determined during development.'}
316 |
317 | ## 4. Implementation Phases
318 |
319 | ### Phase 1: Setup and Initial Development
320 |
321 | **Description**: Set up the development environment and implement core functionality.
322 |
323 | **Tasks**:
324 | ${requirements.slice(0, Math.ceil(requirements.length / 3)).map(req => `- Implement ${req}`).join('\n')}
325 |
326 | ### Phase 2: Core Feature Implementation
327 |
328 | **Description**: Implement the main feature components and functionality.
329 |
330 | **Tasks**:
331 | ${requirements.slice(Math.ceil(requirements.length / 3), Math.ceil(requirements.length * 2 / 3)).map(req => `- Implement ${req}`).join('\n')}
332 |
333 | ### Phase 3: Testing and Refinement
334 |
335 | **Description**: Test the feature thoroughly and refine based on feedback.
336 |
337 | **Tasks**:
338 | ${requirements.slice(Math.ceil(requirements.length * 2 / 3)).map(req => `- Test and refine ${req}`).join('\n')}
339 | - Write automated tests for all functionality
340 | - Conduct code review
341 | - Performance optimization
342 |
343 | ## 5. Dependencies and Prerequisites
344 |
345 | ${dependenciesResponse ? dependenciesResponse.answer : 'No specific dependencies identified.'}
346 |
347 | ## 6. Timeline Estimate
348 |
349 | - Phase 1: 1-2 weeks
350 | - Phase 2: 2-3 weeks
351 | - Phase 3: 1-2 weeks
352 |
353 | Total estimated time: 4-7 weeks, depending on complexity and available resources.
354 |
355 | ## 7. Resources Required
356 |
357 | - Developer time: 1-2 developers
358 | - Testing resources
359 | - Technical documentation
360 | - Any specific tools or libraries mentioned in dependencies
361 |
362 | ---
363 |
364 | Generated on: ${formatDate(new Date())}
365 | `;
366 |
367 | // Store the generated implementation plan in the document storage system
368 | documentStorage.storeDocument(feature.id, DocumentType.IMPLEMENTATION_PLAN, implementationPlan)
369 | .catch(error => console.error(`Failed to store implementation plan document: ${error}`));
370 |
371 | return implementationPlan;
372 | }
```