Skip to main content
Glama

Vibe-Coder MCP Server

tool-handlers.ts23.8 kB
/** * @file tool-handlers.ts * @version 1.0.0 * * Provides handlers for the MCP tools exposed by the Vibe-Coder MCP Server. * These handlers are registered with the tool registry for consistent handling. */ import { z } from 'zod'; import { ToolHandler, toolRegistry } from './registry.js'; import { getFeature, updateFeature } from './storage.js'; import { getNextClarificationQuestion, isClarificationComplete, getClarificationStatus } from './clarification.js'; import { generatePRD, generateImplementationPlan } from './documentation.js'; import { createPhase, updatePhaseStatus, addTask, updateTaskStatus } from './phases.js'; import { createFeatureObject } from './utils.js'; import { validateFeatureId, validatePhaseId, validateTaskId, validateFeaturePhaseTask, validateRequiredText, validatePhaseStatusValue } from './validators.js'; import { createToolErrorResponse, featureNotFoundError, phaseNotFoundError, taskNotFoundError, invalidPhaseTransitionError, clarificationIncompleteError } from './errors.js'; import { Phase, Task } from './types.js'; // Schema for start_feature_clarification const StartFeatureClarificationSchema = z.object({ featureName: z.string().min(2).max(100), initialDescription: z.string().optional().default("") }); /** * Start feature clarification handler */ const startFeatureClarificationHandler: ToolHandler<z.infer<typeof StartFeatureClarificationSchema>> = async (params) => { try { const { featureName, initialDescription } = StartFeatureClarificationSchema.parse(params); // 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}` }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for provide_clarification const ProvideClarificationSchema = z.object({ featureId: z.string().min(1), question: z.string().min(1), answer: z.string().min(1) }); /** * Provide clarification handler */ const provideClarificationHandler: ToolHandler<z.infer<typeof ProvideClarificationSchema>> = async (params) => { try { const { featureId, question, answer } = ProvideClarificationSchema.parse(params); console.log(`\n[CLARIFICATION] Received request for feature ${featureId}\n Question: "${question.substring(0, 50)}..."\n Answer: "${answer.substring(0, 50)}..."`); // Get the feature const feature = getFeature(featureId); if (!feature) { console.log(`[CLARIFICATION] Feature ID ${featureId} not found`); return featureNotFoundError(featureId); } console.log(`[CLARIFICATION] Found feature: ${feature.name} with ${feature.clarificationResponses.length} existing responses`); // Add the clarification response to the feature feature.clarificationResponses.push({ question, answer, timestamp: new Date() }); console.log(`[CLARIFICATION] Added response, now has ${feature.clarificationResponses.length} responses`); // 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) { // Check if nextQuestion is a string before using substring const questionPreview = typeof nextQuestion === 'string' ? nextQuestion.substring(0, 50) + "..." : "All questions answered"; console.log(`[CLARIFICATION] Returning next question: "${questionPreview}"`); return { content: [{ type: "text", text: `Response recorded. ${nextQuestion}` }] }; } else { // All questions answered console.log(`[CLARIFICATION] All questions answered`); return { content: [{ type: "text", text: "All clarification questions have been answered. You can now generate a PRD for this feature." }] }; } } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for generate_prd const GeneratePrdSchema = z.object({ featureId: z.string().min(1) }); /** * Generate PRD handler */ const generatePrdHandler: ToolHandler<z.infer<typeof GeneratePrdSchema>> = async (params) => { try { const { featureId } = GeneratePrdSchema.parse(params); // Validate feature ID const featureResult = validateFeatureId(featureId); if (!featureResult.valid) { return createToolErrorResponse(featureResult.message); } const feature = featureResult.data; // Check if clarifications are complete if (!isClarificationComplete(feature)) { return clarificationIncompleteError(getClarificationStatus(feature)); } // Generate PRD const prdDoc = generatePRD(feature); // Store the document feature.prdDoc = prdDoc; updateFeature(featureId, feature); return { content: [{ type: "text", text: `PRD generated for feature ${feature.name}. You can view it using the resource URI: feature://${feature.id}/prd` }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for generate_implementation_plan const GenerateImplementationPlanSchema = z.object({ featureId: z.string().min(1) }); /** * Generate implementation plan handler */ const generateImplementationPlanHandler: ToolHandler<z.infer<typeof GenerateImplementationPlanSchema>> = async (params) => { try { const { featureId } = GenerateImplementationPlanSchema.parse(params); // Validate feature ID const featureResult = validateFeatureId(featureId); if (!featureResult.valid) { return createToolErrorResponse(featureResult.message); } const feature = featureResult.data; // Check if clarifications are complete if (!isClarificationComplete(feature)) { return clarificationIncompleteError(getClarificationStatus(feature)); } // Generate the implementation plan const implDoc = generateImplementationPlan(feature); // Store the document feature.implDoc = implDoc; updateFeature(featureId, feature); return { content: [{ type: "text", text: `Implementation plan generated for feature ${feature.name}. You can view it using the resource URI: feature://${feature.id}/implementation` }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for create_phase const CreatePhaseSchema = z.object({ featureId: z.string().min(1), name: z.string().min(2).max(100), description: z.string().min(1) }); /** * Create phase handler */ const createPhaseHandler: ToolHandler<z.infer<typeof CreatePhaseSchema>> = async (params) => { try { const { featureId, name, description } = CreatePhaseSchema.parse(params); // Validate feature ID const featureResult = validateFeatureId(featureId); if (!featureResult.valid) { return createToolErrorResponse(featureResult.message); } const feature = featureResult.data; // Create the phase const phase = createPhase(featureId, name, description); if (!phase) { return createToolErrorResponse(`Failed to create phase for feature ${feature.name}`); } return { content: [{ type: "text", text: `Phase "${name}" created with ID: ${phase.id}` }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for update_phase_status const UpdatePhaseStatusSchema = z.object({ featureId: z.string().min(1), phaseId: z.string().min(1), status: z.enum(["pending", "in_progress", "completed", "reviewed"]) }); /** * Update phase status handler */ const updatePhaseStatusHandler: ToolHandler<z.infer<typeof UpdatePhaseStatusSchema>> = async (params) => { try { const { featureId, phaseId, status } = UpdatePhaseStatusSchema.parse(params); // Validate feature and phase const validationResult = validateFeaturePhaseTask(featureId, phaseId); if (!validationResult.valid) { return createToolErrorResponse(validationResult.message); } const { feature, phase } = validationResult.data; // Validate the status transition const transitionResult = phase.status !== status; if (!transitionResult) { return invalidPhaseTransitionError(phase.status, status); } // Update the phase status const updatedPhase = updatePhaseStatus(featureId, phaseId, status); return { content: [{ type: "text", text: `Phase status updated to "${status}"` }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for add_task const AddTaskSchema = z.object({ featureId: z.string().min(1), phaseId: z.string().min(1), description: z.string().min(3).max(500) }); /** * Add task handler */ const addTaskHandler: ToolHandler<z.infer<typeof AddTaskSchema>> = async (params) => { try { const { featureId, phaseId, description } = AddTaskSchema.parse(params); // Validate feature and phase const validationResult = validateFeaturePhaseTask(featureId, phaseId); if (!validationResult.valid) { return createToolErrorResponse(validationResult.message); } const { feature, phase } = validationResult.data; // Add the task const taskId = addTask(featureId, phaseId, description); return { content: [{ type: "text", text: `Task added to phase "${phase.name}" with ID: ${taskId}` }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for update_task_status const UpdateTaskStatusSchema = z.object({ featureId: z.string().min(1), phaseId: z.string().min(1), taskId: z.string().min(1), completed: z.boolean() }); /** * Update task status handler */ const updateTaskStatusHandler: ToolHandler<z.infer<typeof UpdateTaskStatusSchema>> = async (params) => { try { const { featureId, phaseId, taskId, completed } = UpdateTaskStatusSchema.parse(params); // Validate feature, phase and task const validationResult = validateFeaturePhaseTask(featureId, phaseId, taskId); if (!validationResult.valid) { return createToolErrorResponse(validationResult.message); } const { feature, phase, task } = validationResult.data; // Update the task status const updatedTask = updateTaskStatus(featureId, phaseId, taskId, completed); // Check if all tasks in this phase are now completed if (completed && phase.tasks.every((t: Task) => t.id === taskId || t.completed)) { // Auto-advance the phase to 'completed' status return { content: [{ type: "text", text: `Task marked as ${completed ? 'completed' : 'incomplete'}. All tasks in this phase are now complete. The phase "${phase.name}" has been automatically marked as completed.` }] }; } // Check if all tasks are completed and suggest phase update if applicable let message = `Task status updated to ${completed ? 'completed' : 'not completed'}`; if (completed && phase.tasks.every((t: Task) => t.id === taskId || t.completed)) { // All tasks are now completed message += `. All tasks in phase ${phase.name} are now completed. Consider updating the phase status to 'completed'.`; } return { content: [{ type: "text", text: message }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; // Schema for get_next_phase_action const GetNextPhaseActionSchema = z.object({ featureId: z.string().min(1) }); /** * Get next phase action handler */ const getNextPhaseActionHandler: ToolHandler<z.infer<typeof GetNextPhaseActionSchema>> = async (params) => { try { const { featureId } = GetNextPhaseActionSchema.parse(params); // Validate feature ID const featureResult = validateFeatureId(featureId); if (!featureResult.valid) { return createToolErrorResponse(featureResult.message); } const feature = featureResult.data; // Find the current active phase (first non-completed/reviewed phase) const currentPhase = feature.phases.find((p: Phase) => p.status === 'pending' || p.status === 'in_progress'); if (!currentPhase) { // All phases are completed or reviewed return { content: [{ type: "text", text: 'All phases are completed or reviewed. The feature implementation is done!' }] }; } // Check task completion status const completedTasks = currentPhase.tasks.filter((t: Task) => t.completed); const pendingTasks = currentPhase.tasks.filter((t: Task) => !t.completed); // Determine next action based on phase and task status let message = ''; if (currentPhase.status === 'pending') { message = `Phase "${currentPhase.name}" is pending. Start working on this phase by setting its status to "in_progress".`; } else if (currentPhase.status === 'in_progress') { if (pendingTasks.length > 0) { message = `${completedTasks.length}/${currentPhase.tasks.length} tasks are completed in phase "${currentPhase.name}". Continue working on pending tasks.`; } else if (currentPhase.tasks.length === 0) { message = `Phase "${currentPhase.name}" has no tasks defined. Add tasks or mark the phase as completed if appropriate.`; } else { // All tasks are completed message = `All tasks in phase "${currentPhase.name}" are completed. Consider marking this phase as completed.`; } } return { content: [{ type: "text", text: message }] }; } catch (error) { if (error instanceof z.ZodError) { const errorMessage = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return createToolErrorResponse(`Validation error: ${errorMessage}`); } return createToolErrorResponse(error instanceof Error ? error.message : "Unknown error"); } }; /** * Register all tool handlers with the tool registry */ export function registerToolHandlers() { toolRegistry.register( 'start_feature_clarification', startFeatureClarificationHandler, 'Start the clarification process for a new feature', { type: "object", properties: { featureName: { type: "string", description: "Name of the feature" }, initialDescription: { type: "string", description: "Initial description of the feature" } }, required: ["featureName"] }, [ { featureName: "User Authentication", initialDescription: "Add login and registration functionality to the application" }, { featureName: "Data Export", initialDescription: "Allow users to export their data in CSV and JSON formats" } ] ); toolRegistry.register( 'provide_clarification', provideClarificationHandler, 'Provide answer to a clarification question', { type: "object", properties: { featureId: { type: "string", description: "ID of the feature" }, question: { type: "string", description: "Clarification question" }, answer: { type: "string", description: "Answer to the clarification question" } }, required: ["featureId", "question", "answer"] }, [ { featureId: "feature-123", question: "What problem does this feature solve?", answer: "This feature solves the problem of users forgetting their passwords by providing a secure password reset flow." }, { featureId: "feature-456", question: "Who are the target users?", answer: "The target users are administrators who need to manage user accounts and permissions." } ] ); toolRegistry.register( 'generate_prd', generatePrdHandler, 'Generate a PRD document based on clarification responses', { type: "object", properties: { featureId: { type: "string", description: "ID of the feature" } }, required: ["featureId"] }, [ { featureId: "feature-123" } ] ); toolRegistry.register( 'generate_implementation_plan', generateImplementationPlanHandler, 'Generate an implementation plan for a feature based on clarifications and PRD', { type: 'object', properties: { featureId: { type: 'string', description: 'The ID of the feature to generate an implementation plan for' } }, required: ['featureId'] } ); toolRegistry.register( 'create_phase', createPhaseHandler, 'Create a new development phase for a feature', { type: 'object', properties: { featureId: { type: 'string', description: 'ID of the feature to create a phase for' }, name: { type: 'string', description: 'Name of the phase' }, description: { type: 'string', description: 'Description of the phase' } }, required: ['featureId', 'name', 'description'] }, [ { featureId: "feature-123", name: "Requirements Analysis", description: "Gather and analyze requirements for the feature" }, { featureId: "feature-123", name: "Implementation", description: "Implement the core functionality of the feature" } ] ); toolRegistry.register( 'update_phase_status', updatePhaseStatusHandler, 'Update the status of a development phase', { type: 'object', properties: { featureId: { type: 'string', description: 'ID of the feature containing the phase' }, phaseId: { type: 'string', description: 'ID of the phase to update' }, status: { type: 'string', description: 'New status for the phase (pending, in_progress, completed, reviewed)' } }, required: ['featureId', 'phaseId', 'status'] }, [ { featureId: "feature-123", phaseId: "phase-456", status: "in_progress" }, { featureId: "feature-123", phaseId: "phase-456", status: "completed" } ] ); toolRegistry.register( 'add_task', addTaskHandler, 'Add a task to a development phase', { type: 'object', properties: { featureId: { type: 'string', description: 'ID of the feature containing the phase' }, phaseId: { type: 'string', description: 'ID of the phase to add the task to' }, description: { type: 'string', description: 'Description of the task' } }, required: ['featureId', 'phaseId', 'description'] }, [ { featureId: "feature-123", phaseId: "phase-456", description: "Create database migration scripts" }, { featureId: "feature-123", phaseId: "phase-456", description: "Implement user interface components" } ] ); toolRegistry.register( 'update_task_status', updateTaskStatusHandler, 'Update the completion status of a task', { type: 'object', properties: { featureId: { type: 'string', description: 'ID of the feature containing the phase' }, phaseId: { type: 'string', description: 'ID of the phase containing the task' }, taskId: { type: 'string', description: 'ID of the task to update' }, completed: { type: 'boolean', description: 'Whether the task is completed' } }, required: ['featureId', 'phaseId', 'taskId', 'completed'] }, [ { featureId: "feature-123", phaseId: "phase-456", taskId: "task-789", completed: true }, { featureId: "feature-123", phaseId: "phase-456", taskId: "task-789", completed: false } ] ); toolRegistry.register( 'get_next_phase_action', getNextPhaseActionHandler, 'Get guidance on what to do next in the current phase or whether to move to the next phase', { type: 'object', properties: { featureId: { type: 'string', description: 'ID of the feature' } }, required: ['featureId'] }, [ { featureId: "feature-123" } ] ); } // Export all handlers for testing or direct usage export { startFeatureClarificationHandler, provideClarificationHandler, generatePrdHandler, generateImplementationPlanHandler, createPhaseHandler, updatePhaseStatusHandler, addTaskHandler, updateTaskStatusHandler, getNextPhaseActionHandler };

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