UNPKG

@mseep/vibe-check-mcp

Version:

Vibe Check MCP for preventing cascading errors in AI-assisted coding through metacognitive pattern interrupts

323 lines (322 loc) 13.4 kB
#!/usr/bin/env node // Import dotenv and configure it import dotenv from 'dotenv'; dotenv.config(); import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; // Import tool implementations import { vibeCheckTool } from './tools/vibeCheck.js'; import { vibeDistillTool } from './tools/vibeDistill.js'; import { vibeLearnTool } from './tools/vibeLearn.js'; // Import Gemini integration import { initializeGemini } from './utils/gemini.js'; import { STANDARD_CATEGORIES } from './utils/storage.js'; // Validate API key at startup const apiKey = process.env.GEMINI_API_KEY; if (!apiKey) { console.error("ERROR: GEMINI_API_KEY environment variable is missing. Server cannot start without a valid API key."); process.exit(1); } else { console.error("GEMINI_API_KEY found. Initializing API..."); try { initializeGemini(apiKey); console.error('Gemini API initialized successfully'); } catch (error) { console.error('Failed to initialize Gemini API:', error); process.exit(1); } } /** * Create the MCP server with appropriate capabilities */ const server = new Server({ name: "vibe-check", version: "0.2.0", }, { capabilities: { tools: {}, }, }); /** * Handler for listing available tools */ server.setRequestHandler(ListToolsRequestSchema, async () => { console.error("Received tools/list request"); const response = { tools: [ { name: "vibe_check", description: "Metacognitive questioning tool that identifies assumptions and breaks tunnel vision to prevent cascading errors", inputSchema: { type: "object", properties: { plan: { type: "string", description: "Current plan or thinking" }, userRequest: { type: "string", description: "Original user request - critical for alignment checking" }, thinkingLog: { type: "string", description: "Raw sequential thinking transcript" }, availableTools: { type: "array", items: { type: "string" }, description: "List of available MCP tools" }, focusAreas: { type: "array", items: { type: "string" }, description: "Optional specific focus areas" }, sessionId: { type: "string", description: "Optional session ID for state management" }, previousAdvice: { type: "string", description: "Previous feedback to avoid repetition and ensure progression" }, phase: { type: "string", enum: ["planning", "implementation", "review"], description: "Current project phase for context-appropriate feedback" }, confidence: { type: "number", minimum: 0, maximum: 1, description: "Agent's confidence level (0-1)" } }, required: ["plan", "userRequest"] // userRequest now required } }, { name: "vibe_distill", description: "Plan simplification tool that reduces complexity and extracts essential elements to prevent over-engineering", inputSchema: { type: "object", properties: { plan: { type: "string", description: "The plan to distill" }, userRequest: { type: "string", description: "Original user request" }, sessionId: { type: "string", description: "Optional session ID for state management" } }, required: ["plan", "userRequest"] // userRequest now required } }, { name: "vibe_learn", description: "Pattern recognition system that tracks common errors and solutions to prevent recurring issues", inputSchema: { type: "object", properties: { mistake: { type: "string", description: "One-sentence description of the mistake" }, category: { type: "string", description: `Category of mistake (standard categories: ${STANDARD_CATEGORIES.join(', ')})`, enum: STANDARD_CATEGORIES }, solution: { type: "string", description: "How it was corrected (one sentence)" }, sessionId: { type: "string", description: "Optional session ID for state management" } }, required: ["mistake", "category", "solution"] } } ] }; console.error(`Responding with ${response.tools.length} tools`); return response; }); /** * Handler for tool invocation */ server.setRequestHandler(CallToolRequestSchema, async (request) => { console.error(`Received tools/call request for tool: ${request.params.name}`); const { name, arguments: args } = request.params; switch (name) { case "vibe_check": { // Validate required userRequest if (!args || !args.userRequest || typeof args.userRequest !== 'string' || args.userRequest.trim() === '') { console.error("Invalid vibe_check request: missing userRequest"); throw new McpError(ErrorCode.InvalidParams, 'FULL user request is required for alignment checking and to prevent bias'); } // Fix type casting error - convert args to the correct interface const input = { plan: typeof args.plan === 'string' ? args.plan : '', userRequest: args.userRequest, thinkingLog: typeof args.thinkingLog === 'string' ? args.thinkingLog : undefined, availableTools: Array.isArray(args.availableTools) ? args.availableTools : undefined, focusAreas: Array.isArray(args.focusAreas) ? args.focusAreas : undefined, sessionId: typeof args.sessionId === 'string' ? args.sessionId : undefined, previousAdvice: typeof args.previousAdvice === 'string' ? args.previousAdvice : undefined, phase: ['planning', 'implementation', 'review'].includes(args.phase) ? args.phase : undefined, confidence: typeof args.confidence === 'number' ? args.confidence : undefined }; console.error("Executing vibe_check tool..."); const result = await vibeCheckTool(input); console.error("vibe_check execution complete"); return { content: [ { type: "text", text: formatVibeCheckOutput(result) } ] }; } case "vibe_distill": { // Validate required parameters if (!args || typeof args.plan !== 'string') { console.error("Invalid vibe_distill request: missing plan"); throw new McpError(ErrorCode.InvalidParams, 'Invalid input: plan is required and must be a string'); } // Validate required userRequest if (!args.userRequest || typeof args.userRequest !== 'string' || args.userRequest.trim() === '') { console.error("Invalid vibe_distill request: missing userRequest"); throw new McpError(ErrorCode.InvalidParams, 'FULL user request is required for proper distillation and alignment'); } // Create a properly typed input const input = { plan: args.plan, userRequest: args.userRequest, sessionId: typeof args.sessionId === 'string' ? args.sessionId : undefined }; console.error("Executing vibe_distill tool..."); const result = await vibeDistillTool(input); console.error("vibe_distill execution complete"); return { content: [ { type: "text", text: result.distilledPlan } ] }; } case "vibe_learn": { if (!args || typeof args.mistake !== 'string' || typeof args.category !== 'string' || typeof args.solution !== 'string') { console.error("Invalid vibe_learn request: missing required parameters"); throw new McpError(ErrorCode.InvalidParams, 'Invalid input: mistake, category, and solution are required and must be strings'); } // Create a properly typed input const input = { mistake: args.mistake, category: args.category, solution: args.solution, sessionId: typeof args.sessionId === 'string' ? args.sessionId : undefined }; console.error("Executing vibe_learn tool..."); const result = await vibeLearnTool(input); console.error("vibe_learn execution complete"); return { content: [ { type: "text", text: formatVibeLearnOutput(result) } ] }; } default: console.error(`Unknown tool requested: ${name}`); throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } }); /** * Format vibe check output as markdown */ function formatVibeCheckOutput(result) { let output = result.questions; // Add pattern alert section if present if (result.patternAlert) { // Check if the pattern alert is already in the response if (!output.includes("Pattern Alert:") && !output.includes("pattern emerging:")) { output += `\n\n**I notice a pattern emerging:** ${result.patternAlert}`; } } return output; } /** * Format vibe learn output as markdown */ function formatVibeLearnOutput(result) { let output = ''; if (result.added) { output += `✅ Pattern logged successfully (category tally: ${result.currentTally})`; } else { output += '❌ Failed to log pattern'; } // Add top categories section if (result.topCategories && result.topCategories.length > 0) { output += '\n\n## Top Pattern Categories\n'; for (const category of result.topCategories) { output += `\n### ${category.category} (${category.count} occurrences)\n`; // Show the most recent example if (category.recentExample) { output += `Most recent: "${category.recentExample.mistake}"\n`; output += `Solution: "${category.recentExample.solution}"\n`; } } } return output; } /** * Start the server */ async function main() { console.error('Starting Vibe Check MCP server...'); // Set up error handler server.onerror = (error) => { console.error("[Vibe Check Error]", error); }; try { // Connect to transport console.error('Connecting to transport...'); const transport = new StdioServerTransport(); await server.connect(transport); console.error('Vibe Check MCP server running'); } catch (error) { console.error("Error connecting to transport:", error); process.exit(1); } } // Start the server main().catch((error) => { console.error("Server startup error:", error); process.exit(1); });