Skip to main content
Glama

Vibe-Coder MCP Server

mcp-server.ts20.7 kB
#!/usr/bin/env node /** * @file Vibe-Coder MCP Server * @version 0.3.0 * @status STABLE - DO NOT MODIFY WITHOUT TESTS * @lastModified 2023-03-23 * * This MCP server implements a structured development workflow that helps * LLM-based coders build features in an organized, clean, and safe manner. * * IMPORTANT: * - Test any modifications thoroughly * - Maintain backward compatibility * * Functionality: * - Feature request clarification through iterative questioning * - PRD and implementation plan generation * - Phased development with tasks and status tracking */ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // Import core modules import { Feature, Phase, Task, ClarificationResponse, PhaseStatus } from './types.js'; import { getFeature, updateFeature, listFeatures } from './storage.js'; import { getNextClarificationQuestion, addClarificationResponse, formatClarificationResponses, isClarificationComplete } from './clarification.js'; import { generatePRD, generateImplementationPlan } from './documentation.js'; import { createFeatureObject, generateFeatureProgressSummary, createPhaseObject, createTaskObject, now } from './utils.js'; import { documentStorage, DocumentType } from './document-storage.js'; /** * Create an MCP server */ const server = new McpServer({ name: "Vibe-Coder", version: "0.3.0" }); // --------- Helper Functions --------- /** * Create a new phase for a feature directly */ function createPhaseDirectly(feature: Feature, name: string, description: string): Phase { const newPhase = createPhaseObject(name, description); feature.phases.push(newPhase); feature.updatedAt = new Date(); return newPhase; } /** * Update phase status directly */ function updatePhaseStatusDirectly(feature: Feature, phaseId: string, status: PhaseStatus): void { const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { throw new Error(`Phase ${phaseId} not found`); } phase.status = status; phase.updatedAt = now(); feature.updatedAt = now(); } /** * Add task directly */ function addTaskDirectly(feature: Feature, phaseId: string, description: string): Task { const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { throw new Error(`Phase ${phaseId} not found`); } const newTask = createTaskObject(description); // Convert task ID to string to ensure it's not an object if (typeof newTask.id !== 'string') { newTask.id = String(newTask.id); } phase.tasks.push(newTask); phase.updatedAt = now(); feature.updatedAt = now(); return newTask; } /** * Update task status directly */ function updateTaskStatusDirectly(feature: Feature, phaseId: string, taskId: string, completed: boolean): void { const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { throw new Error(`Phase ${phaseId} not found`); } const task = phase.tasks.find(t => t.id === taskId); if (!task) { throw new Error(`Task ${taskId} not found`); } task.completed = completed; task.updatedAt = now(); phase.updatedAt = now(); feature.updatedAt = now(); } // --------- Register Resources --------- // Features list resource server.resource( "features-list", "features://list", async (uri) => ({ contents: [{ uri: uri.href, text: listFeatures().map(f => `${f.id}: ${f.name}`).join("\n") }] }) ); // Features status resource server.resource( "features-status", "features://status", async (uri) => { const features = listFeatures(); if (features.length === 0) { return { contents: [{ uri: uri.href, text: "# Project Status\n\nNo features have been created yet." }] }; } const featuresStatus = features.map(feature => { const totalPhases = feature.phases.length; const completedPhases = feature.phases.filter(p => p.status === 'completed' || p.status === 'reviewed').length; const totalTasks = feature.phases.reduce((acc, phase) => acc + phase.tasks.length, 0); const completedTasks = feature.phases.reduce( (acc, phase) => acc + phase.tasks.filter(t => t.completed).length, 0 ); return `## ${feature.name} - ID: ${feature.id} - Status: ${completedPhases === totalPhases && totalPhases > 0 ? 'Completed' : 'In Progress'} - Phases: ${completedPhases}/${totalPhases} completed - Tasks: ${completedTasks}/${totalTasks} completed - [View Details](feature://${feature.id}/progress) `; }).join('\n'); return { contents: [{ uri: uri.href, text: `# Project Status\n\n${featuresStatus}` }] }; } ); // Feature detail resource with parameter server.resource( "feature-detail", new ResourceTemplate("feature://{featureId}", { list: undefined }), async (uri, { featureId }) => { const feature = getFeature(featureId as string); if (!feature) { throw new Error(`Feature ${featureId} not found`); } const timestamp = feature.updatedAt.toISOString(); const clarifications = formatClarificationResponses(feature.clarificationResponses); const phasesText = feature.phases.map(p => `- ${p.name} (${p.status}): ${p.tasks.filter(t => t.completed).length}/${p.tasks.length} tasks completed` ).join('\n'); const featureDetails = ` Feature: ${feature.name} ID: ${feature.id} Description: ${feature.description} Last Updated: ${timestamp} Clarification Responses: ${clarifications} Phases (${feature.phases.length}): ${phasesText} `; return { contents: [{ uri: uri.href, text: featureDetails }] }; } ); // Feature progress resource server.resource( "feature-progress", new ResourceTemplate("feature://{featureId}/progress", { list: undefined }), async (uri, { featureId }) => { const feature = getFeature(featureId as string); if (!feature) { throw new Error(`Feature ${featureId} not found`); } const progressReport = generateFeatureProgressSummary(feature); return { contents: [{ uri: uri.href, text: progressReport }] }; } ); // Feature PRD resource server.resource( "feature-prd", new ResourceTemplate("feature://{featureId}/prd", { list: undefined }), async (uri, { featureId }) => { const feature = getFeature(featureId as string); if (!feature) { throw new Error(`Feature ${featureId} not found`); } if (!isClarificationComplete(feature)) { return { contents: [{ uri: uri.href, text: "# PRD Not Available\n\nThe clarification process is not complete. Please answer all clarification questions first." }] }; } const prd = generatePRD(feature); return { contents: [{ uri: uri.href, text: prd }] }; } ); // Feature implementation plan resource server.resource( "feature-implementation-plan", new ResourceTemplate("feature://{featureId}/implementation", { list: undefined }), async (uri, { featureId }) => { const feature = getFeature(featureId as string); if (!feature) { throw new Error(`Feature ${featureId} not found`); } if (!isClarificationComplete(feature)) { return { contents: [{ uri: uri.href, text: "# Implementation Plan Not Available\n\nThe clarification process is not complete. Please answer all clarification questions first." }] }; } const implementationPlan = generateImplementationPlan(feature); return { contents: [{ uri: uri.href, text: implementationPlan }] }; } ); // --------- Register Tools --------- // Start feature clarification tool server.tool( "start_feature_clarification", { featureName: z.string().min(2).max(100), initialDescription: z.string().optional().default("") }, async ({ featureName, initialDescription }) => { // Create a new feature const feature = createFeatureObject(featureName, initialDescription); updateFeature(feature.id, feature); // Get the first clarification question const firstQuestion = getNextClarificationQuestion(feature); return { content: [{ type: "text", text: `Feature ID: ${feature.id}\n\nLet's clarify your feature request. ${firstQuestion}` }] }; } ); // Provide clarification tool server.tool( "provide_clarification", { featureId: z.string().min(1), question: z.string().min(1), answer: z.string().min(1) }, async ({ featureId, question, answer }) => { // Get the feature const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } // Add the clarification response to the feature feature.clarificationResponses.push({ question, answer, timestamp: new Date() }); // Save the feature with the updated clarification response updateFeature(featureId, feature); // Get the next question or indicate all questions are answered const nextQuestion = getNextClarificationQuestion(feature); if (nextQuestion) { return { content: [{ type: "text", text: `Response recorded. ${nextQuestion}` }] }; } else { // All questions answered return { content: [{ type: "text", text: "All clarification questions have been answered. You can now generate a PRD for this feature." }] }; } } ); // Generate PRD tool server.tool( "generate_prd", { featureId: z.string().min(1) }, async ({ featureId }) => { const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } if (!isClarificationComplete(feature)) { throw new Error("Clarification process not complete. Please answer all clarification questions."); } const prd = generatePRD(feature); feature.prd = prd; updateFeature(featureId, feature); return { content: [{ type: "text", text: `PRD generated successfully for "${feature.name}". You can view it at feature://${featureId}/prd` }] }; } ); // Create phase tool server.tool( "create_phase", { featureId: z.string().min(1), name: z.string().min(1), description: z.string().min(1) }, async ({ featureId, name, description }) => { const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } const phase = createPhaseDirectly(feature, name, description); updateFeature(featureId, feature); return { content: [{ type: "text", text: `Created phase "${name}" with ID ${phase.id} for feature "${feature.name}"` }] }; } ); // Update phase status tool server.tool( "update_phase_status", { featureId: z.string().min(1), phaseId: z.string().min(1), status: z.enum(["pending", "in_progress", "completed", "reviewed"]) }, async ({ featureId, phaseId, status }) => { const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { throw new Error(`Phase ${phaseId} not found in feature ${featureId}`); } const oldStatus = phase.status; updatePhaseStatusDirectly(feature, phaseId, status); updateFeature(featureId, feature); return { content: [{ type: "text", text: `Updated phase "${phase.name}" status from "${oldStatus}" to "${status}"` }] }; } ); // Add task tool server.tool( "add_task", { featureId: z.string().min(1), phaseId: z.string().min(1), description: z.string().min(1) }, async ({ featureId, phaseId, description }) => { const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { throw new Error(`Phase ${phaseId} not found in feature ${featureId}`); } const task = addTaskDirectly(feature, phaseId, description); updateFeature(featureId, feature); // Debug log console.error(`DEBUG: Task created with ID: ${task.id}, type: ${typeof task.id}`); console.error(`DEBUG: Task object: ${JSON.stringify(task)}`); // Ensure task.id is explicitly converted to string and check if it's an object let taskId: string; if (typeof task.id === 'object') { taskId = JSON.stringify(task.id); } else { taskId = String(task.id); } return { content: [{ type: "text", text: `Added task "${description.substring(0, 30)}${description.length > 30 ? '...' : ''}" with ID: ${taskId} to phase "${phase.name}"` }] }; } ); // Update task status tool server.tool( "update_task_status", { featureId: z.string().min(1), phaseId: z.string().min(1), taskId: z.string().min(1), completed: z.boolean() }, async ({ featureId, phaseId, taskId, completed }) => { const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { throw new Error(`Phase ${phaseId} not found in feature ${featureId}`); } const task = phase.tasks.find(t => t.id === taskId); if (!task) { throw new Error(`Task ${taskId} not found in phase ${phaseId}`); } updateTaskStatusDirectly(feature, phaseId, taskId, completed); updateFeature(featureId, feature); return { content: [{ type: "text", text: `Updated task "${task.description.substring(0, 30)}${task.description.length > 30 ? '...' : ''}" status to ${completed ? 'completed' : 'not completed'}` }] }; } ); // Get next phase action tool server.tool( "get_next_phase_action", { featureId: z.string().min(1) }, async ({ featureId }) => { const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } // Check if we need implementation plan first if (!feature.implementationPlan) { return { content: [{ type: "text", text: "You should generate an implementation plan before creating phases." }] }; } // If no phases, suggest creating first phase if (feature.phases.length === 0) { return { content: [{ type: "text", text: "You should create the first development phase based on the implementation plan." }] }; } // Find the current active phase (the first non-completed phase) const currentPhase = feature.phases.find(p => p.status !== 'completed' && p.status !== 'reviewed'); if (!currentPhase) { return { content: [{ type: "text", text: "All phases are complete. You should mark the final phase as reviewed." }] }; } // Check if all tasks in the phase are completed const allTasksCompleted = currentPhase.tasks.every(t => t.completed); if (currentPhase.tasks.length === 0) { return { content: [{ type: "text", text: `Current phase "${currentPhase.name}" has no tasks. You should add tasks based on the implementation plan.` }] }; } else if (!allTasksCompleted) { const pendingTasks = currentPhase.tasks.filter(t => !t.completed); return { content: [{ type: "text", text: `Current phase "${currentPhase.name}" has ${pendingTasks.length} incomplete tasks. Complete these tasks before moving to the next phase.` }] }; } else { return { content: [{ type: "text", text: `All tasks in the current phase "${currentPhase.name}" are complete. You can now mark this phase as completed and proceed to the next phase.` }] }; } } ); // Define prompt for feature planning server.prompt( "feature-planning", { featureId: z.string() }, ({ featureId }) => ({ messages: [{ role: "user", content: { type: "text", text: `I need to plan the development of feature with ID ${featureId}. Please help me: 1. Understand what the feature is about 2. Break down the feature into development phases 3. Create tasks for each phase 4. Track progress through completion` } }] }) ); // Document path tool server.tool( "get_document_path", { featureId: z.string().min(1), documentType: z.string().min(1) }, async ({ featureId, documentType }) => { try { // Check if the feature exists const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } // Map the string to DocumentType enum let docType: DocumentType; if (documentType === 'prd') { docType = DocumentType.PRD; } else if (documentType === 'implementation-plan') { docType = DocumentType.IMPLEMENTATION_PLAN; } else { throw new Error(`Invalid document type: ${documentType}. Expected 'prd' or 'implementation-plan'`); } // Check if the document exists if (!documentStorage.hasDocument(feature.id, docType)) { throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`); } // Get the default file path for the document const filePath = documentStorage.getDefaultFilePath(feature.id, docType); // Get the document to check if it's been saved const document = documentStorage.getDocument(feature.id, docType); return { content: [{ type: "text", text: `Document path: ${filePath}\nSaved to disk: ${document?.metadata.isSaved ? 'Yes' : 'No'}` }] }; } catch (error) { return { content: [{ type: "text", text: `Error retrieving document path: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // Document save tool server.tool( "save_document", { featureId: z.string().min(1), documentType: z.string().min(1), filePath: z.string().min(1).optional() }, async ({ featureId, documentType, filePath }) => { try { // Check if the feature exists const feature = getFeature(featureId); if (!feature) { throw new Error(`Feature ${featureId} not found`); } // Map the string to DocumentType enum let docType: DocumentType; if (documentType === 'prd') { docType = DocumentType.PRD; } else if (documentType === 'implementation-plan') { docType = DocumentType.IMPLEMENTATION_PLAN; } else { throw new Error(`Invalid document type: ${documentType}. Expected 'prd' or 'implementation-plan'`); } // Check if the document exists if (!documentStorage.hasDocument(feature.id, docType)) { throw new Error(`Document of type ${documentType} not found for feature ${feature.id}`); } let savedPath: string; // If a custom path was provided, use it; otherwise, save to the default path if (filePath) { savedPath = await documentStorage.saveDocumentToCustomPath(feature.id, docType, filePath); } else { savedPath = await documentStorage.saveDocumentToFile(feature.id, docType); } return { content: [{ type: "text", text: `Document saved successfully to: ${savedPath}` }] }; } catch (error) { return { content: [{ type: "text", text: `Error saving document: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // Start the server async function main() { console.error("Starting Vibe-Coder MCP Server..."); const transport = new StdioServerTransport(); await server.connect(transport); } main().catch(error => { console.error("Error in Vibe-Coder MCP Server:", error); process.exit(1); });

MCP directory API

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

curl -X GET 'https://glama.ai/api/mcp/v1/servers/crazyrabbitLTC/mcp-vibecoder'

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