Skip to main content
Glama

Vibe-Coder MCP Server

resource-handlers.ts13.5 kB
/** * @file resource-handlers.ts * @version 1.0.0 * * Provides handlers for resources exposed by the Vibe-Coder MCP Server. * These handlers are registered with the resource registry for consistent handling. */ import { ResourceTemplate } from '@modelcontextprotocol/sdk/types.js'; import { resourceRegistry, ResourceHandler } from './registry.js'; import { Feature } from './types.js'; import { listFeatures, getFeature } from './storage.js'; import { formatClarificationResponses } from './clarification.js'; import { generatePRD, generateImplementationPlan } from './documentation.js'; import { generateFeatureProgressSummary } from './utils.js'; import { ErrorCode, createErrorResponse } from './errors.js'; /** * Define resource templates for all resources */ const RESOURCE_TEMPLATES = { featuresList: new ResourceTemplate('features://list', { list: undefined }), featuresStatus: new ResourceTemplate('features://status', { list: undefined }), featureDetail: new ResourceTemplate('feature://{featureId}', { list: undefined }), featureProgress: new ResourceTemplate('feature://{featureId}/progress', { list: undefined }), featurePrd: new ResourceTemplate('feature://{featureId}/prd', { list: undefined }), featureImplementation: new ResourceTemplate('feature://{featureId}/implementation', { list: undefined }), featurePhases: new ResourceTemplate('feature://{featureId}/phases', { list: undefined }), featureTasks: new ResourceTemplate('feature://{featureId}/tasks', { list: undefined }), phaseDetail: new ResourceTemplate('feature://{featureId}/phase/{phaseId}', { list: undefined }), phaseTasks: new ResourceTemplate('feature://{featureId}/phase/{phaseId}/tasks', { list: undefined }), taskDetail: new ResourceTemplate('feature://{featureId}/phase/{phaseId}/task/{taskId}', { list: undefined }), }; /** * Handler for the features list resource */ const featuresListHandler: ResourceHandler = async (uri, params) => { return { contents: [{ uri: uri.href, mimeType: "text/plain", text: listFeatures().map(f => `${f.id}: ${f.name}`).join("\n") }] }; }; /** * Handler for the features status resource */ const featuresStatusHandler: ResourceHandler = async (uri, params) => { const features = listFeatures(); if (features.length === 0) { return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: "# Project Status\n\nNo features have been created yet." }] }; } const featuresStatus = features.map(feature => { const totalPhases = feature.phases.length; const completedPhases = feature.phases.filter(p => p.status === 'completed' || p.status === 'reviewed').length; const totalTasks = feature.phases.reduce((acc, phase) => acc + phase.tasks.length, 0); const completedTasks = feature.phases.reduce( (acc, phase) => acc + phase.tasks.filter(t => t.completed).length, 0 ); return `## ${feature.name} - ID: ${feature.id} - Status: ${completedPhases === totalPhases && totalPhases > 0 ? 'Completed' : 'In Progress'} - Phases: ${completedPhases}/${totalPhases} completed - Tasks: ${completedTasks}/${totalTasks} completed - [View Details](feature://${feature.id}/progress) `; }).join('\n'); return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: `# Project Status\n\n${featuresStatus}` }] }; }; /** * Handler for the feature detail resource */ const featureDetailHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const timestamp = feature.updatedAt.toISOString(); const clarifications = formatClarificationResponses(feature.clarificationResponses); const phasesText = feature.phases.map(p => `- ${p.name} (${p.status}): ${p.tasks.filter(t => t.completed).length}/${p.tasks.length} tasks completed` ).join('\n'); const featureDetails = ` Feature: ${feature.name} ID: ${feature.id} Description: ${feature.description} Last Updated: ${timestamp} Clarification Responses: ${clarifications} Phases (${feature.phases.length}): ${phasesText} `; return { contents: [{ uri: uri.href, mimeType: "text/plain", text: featureDetails }] }; }; /** * Handler for the feature progress resource */ const featureProgressHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const progressReport = generateFeatureProgressSummary(feature); return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: progressReport }] }; }; /** * Handler for the feature PRD resource */ const featurePrdHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const prdContent = feature.prdDoc || generatePRD(feature); return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: prdContent }] }; }; /** * Handler for the feature implementation plan resource */ const featureImplementationHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const implContent = feature.implDoc || generateImplementationPlan(feature); return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: implContent }] }; }; /** * Handler for the feature phases resource */ const featurePhasesHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } if (feature.phases.length === 0) { return { contents: [{ uri: uri.href, mimeType: "text/plain", text: `No phases defined for feature: ${feature.name}` }] }; } const phasesContent = feature.phases.map(phase => { const completedTasks = phase.tasks.filter(t => t.completed).length; const totalTasks = phase.tasks.length; return `Phase: ${phase.name} (ID: ${phase.id}) Status: ${phase.status} Description: ${phase.description} Progress: ${completedTasks}/${totalTasks} tasks completed Last Updated: ${phase.updatedAt.toISOString()} `; }).join('\n---\n\n'); return { contents: [{ uri: uri.href, mimeType: "text/plain", text: `# Phases for Feature: ${feature.name}\n\n${phasesContent}` }] }; }; /** * Handler for the feature tasks resource */ const featureTasksHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const allTasks = feature.phases.flatMap(phase => phase.tasks.map(task => ({ ...task, phaseName: phase.name, phaseId: phase.id, phaseStatus: phase.status })) ); if (allTasks.length === 0) { return { contents: [{ uri: uri.href, mimeType: "text/plain", text: `No tasks defined for feature: ${feature.name}` }] }; } const pendingTasks = allTasks.filter(t => !t.completed); const completedTasks = allTasks.filter(t => t.completed); const pendingTasksText = pendingTasks.length > 0 ? pendingTasks.map(task => `- [ ] ${task.description} (ID: ${task.id}, Phase: ${task.phaseName})`).join('\n') : "No pending tasks."; const completedTasksText = completedTasks.length > 0 ? completedTasks.map(task => `- [x] ${task.description} (ID: ${task.id}, Phase: ${task.phaseName})`).join('\n') : "No completed tasks."; const tasksContent = `# All Tasks for Feature: ${feature.name} ## Pending Tasks (${pendingTasks.length}) ${pendingTasksText} ## Completed Tasks (${completedTasks.length}) ${completedTasksText} `; return { contents: [{ uri: uri.href, mimeType: "text/plain", text: tasksContent }] }; }; /** * Handler for the phase detail resource */ const phaseDetailHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const phaseId = params.phaseId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { return createErrorResponse(ErrorCode.PHASE_NOT_FOUND, `Phase ${phaseId} not found in feature ${feature.name}`); } const completedTasks = phase.tasks.filter(t => t.completed).length; const totalTasks = phase.tasks.length; const taskList = phase.tasks.map(task => `- [${task.completed ? 'x' : ' '}] ${task.description} (ID: ${task.id})` ).join('\n'); const phaseDetails = ` Phase: ${phase.name} ID: ${phase.id} Status: ${phase.status} Description: ${phase.description} Created: ${phase.createdAt.toISOString()} Last Updated: ${phase.updatedAt.toISOString()} Progress: ${completedTasks}/${totalTasks} tasks completed Tasks: ${taskList} `; return { contents: [{ uri: uri.href, mimeType: "text/plain", text: phaseDetails }] }; }; /** * Handler for the phase tasks resource */ const phaseTasksHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const phaseId = params.phaseId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { return createErrorResponse(ErrorCode.PHASE_NOT_FOUND, `Phase ${phaseId} not found in feature ${feature.name}`); } if (phase.tasks.length === 0) { return { contents: [{ uri: uri.href, mimeType: "text/plain", text: `No tasks defined for phase: ${phase.name}` }] }; } const tasksContent = phase.tasks.map(task => ` Task: ${task.description} ID: ${task.id} Status: ${task.completed ? 'Completed' : 'Pending'} Created: ${task.createdAt.toISOString()} Last Updated: ${task.updatedAt.toISOString()} `).join('\n---\n'); return { contents: [{ uri: uri.href, mimeType: "text/plain", text: `# Tasks for Phase: ${phase.name}\n\n${tasksContent}` }] }; }; /** * Handler for the task detail resource */ const taskDetailHandler: ResourceHandler = async (uri, params) => { const featureId = params.featureId; const phaseId = params.phaseId; const taskId = params.taskId; const feature = getFeature(featureId); if (!feature) { return createErrorResponse(ErrorCode.FEATURE_NOT_FOUND, `Feature ${featureId} not found`); } const phase = feature.phases.find(p => p.id === phaseId); if (!phase) { return createErrorResponse(ErrorCode.PHASE_NOT_FOUND, `Phase ${phaseId} not found in feature ${feature.name}`); } const task = phase.tasks.find(t => t.id === taskId); if (!task) { return createErrorResponse(ErrorCode.TASK_NOT_FOUND, `Task ${taskId} not found in phase ${phase.name}`); } const taskDetails = ` Task: ${task.description} ID: ${task.id} Status: ${task.completed ? 'Completed' : 'Pending'} Created: ${task.createdAt.toISOString()} Last Updated: ${task.updatedAt.toISOString()} `; return { contents: [{ uri: uri.href, mimeType: "text/plain", text: taskDetails }] }; }; /** * Register all resource handlers */ export function registerResourceHandlers() { resourceRegistry.register(RESOURCE_TEMPLATES.featuresList, featuresListHandler); resourceRegistry.register(RESOURCE_TEMPLATES.featuresStatus, featuresStatusHandler); resourceRegistry.register(RESOURCE_TEMPLATES.featureDetail, featureDetailHandler); resourceRegistry.register(RESOURCE_TEMPLATES.featureProgress, featureProgressHandler); resourceRegistry.register(RESOURCE_TEMPLATES.featurePrd, featurePrdHandler); resourceRegistry.register(RESOURCE_TEMPLATES.featureImplementation, featureImplementationHandler); resourceRegistry.register(RESOURCE_TEMPLATES.featurePhases, featurePhasesHandler); resourceRegistry.register(RESOURCE_TEMPLATES.featureTasks, featureTasksHandler); resourceRegistry.register(RESOURCE_TEMPLATES.phaseDetail, phaseDetailHandler); resourceRegistry.register(RESOURCE_TEMPLATES.phaseTasks, phaseTasksHandler); resourceRegistry.register(RESOURCE_TEMPLATES.taskDetail, taskDetailHandler); } // Export individual handlers for testing or direct usage export { featuresListHandler, featuresStatusHandler, featureDetailHandler, featureProgressHandler, featurePrdHandler, featureImplementationHandler, featurePhasesHandler, featureTasksHandler, phaseDetailHandler, phaseTasksHandler, taskDetailHandler };

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