UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

394 lines (393 loc) 17.8 kB
import { z } from 'zod'; import { registerTool } from '../../services/routing/toolRegistry.js'; import { jobManager, JobStatus } from '../../services/job-manager/index.js'; import { validateContextCuratorInput } from './types/context-curator.js'; import { ContextCuratorService } from './services/context-curator-service.js'; import { validateContextPackage } from './types/output-package.js'; import logger from '../../logger.js'; import fs from 'fs-extra'; import path from 'path'; function getPackageId(pkg) { if (validateContextPackage(pkg)) { return pkg.metadata.targetDirectory; } if (typeof pkg === 'object' && pkg !== null && 'id' in pkg) { return typeof pkg.id === 'string' ? pkg.id : undefined; } return undefined; } function getTaskType(pkg) { if (validateContextPackage(pkg)) { return pkg.metadata.taskType; } if (typeof pkg === 'object' && pkg !== null && 'metadata' in pkg) { const metadata = pkg.metadata; if (typeof metadata === 'object' && metadata !== null && 'taskType' in metadata) { return typeof metadata.taskType === 'string' ? metadata.taskType : 'general'; } } return 'general'; } function getTotalFiles(pkg) { if (validateContextPackage(pkg)) { return pkg.highPriorityFiles.length + pkg.mediumPriorityFiles.length + pkg.lowPriorityFiles.length; } if (typeof pkg === 'object' && pkg !== null) { if ('files' in pkg && Array.isArray(pkg.files)) { return pkg.files.length; } let total = 0; if ('highPriorityFiles' in pkg && Array.isArray(pkg.highPriorityFiles)) total += pkg.highPriorityFiles.length; if ('mediumPriorityFiles' in pkg && Array.isArray(pkg.mediumPriorityFiles)) total += pkg.mediumPriorityFiles.length; if ('lowPriorityFiles' in pkg && Array.isArray(pkg.lowPriorityFiles)) total += pkg.lowPriorityFiles.length; return total; } return 0; } function getTotalTokens(pkg) { if (validateContextPackage(pkg)) { return pkg.metadata.totalTokenEstimate; } if (typeof pkg === 'object' && pkg !== null) { if ('statistics' in pkg && typeof pkg.statistics === 'object' && pkg.statistics !== null && 'totalTokens' in pkg.statistics) { return typeof pkg.statistics.totalTokens === 'number' ? pkg.statistics.totalTokens : 0; } if ('metadata' in pkg && typeof pkg.metadata === 'object' && pkg.metadata !== null && 'totalTokenEstimate' in pkg.metadata) { return typeof pkg.metadata.totalTokenEstimate === 'number' ? pkg.metadata.totalTokenEstimate : 0; } } return 0; } function getAverageRelevanceScore(pkg) { if (typeof pkg === 'object' && pkg !== null && 'statistics' in pkg) { const stats = pkg.statistics; if (typeof stats === 'object' && stats !== null && 'averageRelevanceScore' in stats) { return typeof stats.averageRelevanceScore === 'number' ? stats.averageRelevanceScore : 0; } } return 0; } function getCacheHitRate(pkg) { if (typeof pkg === 'object' && pkg !== null && 'statistics' in pkg) { const stats = pkg.statistics; if (typeof stats === 'object' && stats !== null && 'cacheHitRate' in stats) { return typeof stats.cacheHitRate === 'number' ? stats.cacheHitRate : 0; } } return 0; } function getProcessingTimeMs(pkg) { if (validateContextPackage(pkg)) { return pkg.metadata.processingTimeMs; } if (typeof pkg === 'object' && pkg !== null) { if ('statistics' in pkg && typeof pkg.statistics === 'object' && pkg.statistics !== null && 'processingTimeMs' in pkg.statistics) { return typeof pkg.statistics.processingTimeMs === 'number' ? pkg.statistics.processingTimeMs : 0; } if ('metadata' in pkg && typeof pkg.metadata === 'object' && pkg.metadata !== null && 'processingTimeMs' in pkg.metadata) { return typeof pkg.metadata.processingTimeMs === 'number' ? pkg.metadata.processingTimeMs : 0; } } return 0; } function getPackageFiles(pkg) { if (validateContextPackage(pkg)) { return [...pkg.highPriorityFiles, ...pkg.mediumPriorityFiles, ...pkg.lowPriorityFiles]; } if (typeof pkg === 'object' && pkg !== null && 'files' in pkg && Array.isArray(pkg.files)) { return pkg.files; } return []; } function getFilePath(file) { if (typeof file === 'object' && file !== null) { if ('path' in file && typeof file.path === 'string') return file.path; if ('file' in file && typeof file.file === 'object' && file.file !== null && 'path' in file.file && typeof file.file.path === 'string') { return file.file.path; } } return 'unknown'; } function getFileRelevanceScore(file) { if (typeof file === 'object' && file !== null) { if ('relevanceScore' in file) { if (typeof file.relevanceScore === 'number') return file.relevanceScore; if (typeof file.relevanceScore === 'object' && file.relevanceScore !== null && 'score' in file.relevanceScore) { return typeof file.relevanceScore.score === 'number' ? file.relevanceScore.score : 0; } if (typeof file.relevanceScore === 'object' && file.relevanceScore !== null && 'overall' in file.relevanceScore) { return typeof file.relevanceScore.overall === 'number' ? file.relevanceScore.overall : 0; } } } return 0; } function getFileCategories(file) { if (typeof file === 'object' && file !== null && 'categories' in file && Array.isArray(file.categories)) { return file.categories.filter((cat) => typeof cat === 'string'); } return []; } function getMetaPromptSystemPrompt(pkg) { if (validateContextPackage(pkg) && pkg.metaPrompt) { return pkg.metaPrompt.substring(0, 200) + '...'; } if (typeof pkg === 'object' && pkg !== null && 'metaPrompt' in pkg) { const metaPrompt = pkg.metaPrompt; if (typeof metaPrompt === 'string') { return metaPrompt.substring(0, 200) + '...'; } if (typeof metaPrompt === 'object' && metaPrompt !== null && 'systemPrompt' in metaPrompt && typeof metaPrompt.systemPrompt === 'string') { return metaPrompt.systemPrompt.substring(0, 200) + '...'; } } return 'No system prompt available'; } function getMetaPromptUserPrompt(pkg) { if (typeof pkg === 'object' && pkg !== null && 'metaPrompt' in pkg) { const metaPrompt = pkg.metaPrompt; if (typeof metaPrompt === 'object' && metaPrompt !== null && 'userPrompt' in metaPrompt && typeof metaPrompt.userPrompt === 'string') { return metaPrompt.userPrompt.substring(0, 200) + '...'; } } return 'No user prompt available'; } function getMetaPromptComplexity(pkg) { if (typeof pkg === 'object' && pkg !== null && 'metaPrompt' in pkg) { const metaPrompt = pkg.metaPrompt; if (typeof metaPrompt === 'object' && metaPrompt !== null && 'estimatedComplexity' in metaPrompt && typeof metaPrompt.estimatedComplexity === 'string') { return metaPrompt.estimatedComplexity; } } return 'medium'; } function getMetaPromptEpicsCount(pkg) { if (typeof pkg === 'object' && pkg !== null && 'metaPrompt' in pkg) { const metaPrompt = pkg.metaPrompt; if (typeof metaPrompt === 'object' && metaPrompt !== null && 'taskDecomposition' in metaPrompt) { const taskDecomp = metaPrompt.taskDecomposition; if (typeof taskDecomp === 'object' && taskDecomp !== null && 'epics' in taskDecomp && Array.isArray(taskDecomp.epics)) { return taskDecomp.epics.length; } } } return 0; } function getMetaPromptGuidelinesCount(pkg) { if (typeof pkg === 'object' && pkg !== null && 'metaPrompt' in pkg) { const metaPrompt = pkg.metaPrompt; if (typeof metaPrompt === 'object' && metaPrompt !== null && 'guidelines' in metaPrompt && Array.isArray(metaPrompt.guidelines)) { return metaPrompt.guidelines.length; } } return 0; } function getBaseOutputDir() { return process.env.VIBE_CODER_OUTPUT_DIR ? path.resolve(process.env.VIBE_CODER_OUTPUT_DIR) : path.join(process.cwd(), 'VibeCoderOutput'); } const contextCuratorInputSchemaShape = { prompt: z.string() .min(3, { message: "Prompt must be at least 3 characters long." }) .describe("The user's development task or request that needs context curation"), target_directory: z.string() .optional() .describe("Target directory to analyze (defaults to current working directory)"), max_token_budget: z.number() .min(1000, { message: "Token budget must be at least 1000 tokens." }) .max(500000, { message: "Token budget cannot exceed 500000 tokens." }) .optional() .describe("Maximum token budget for the context package (default: 250000)"), task_type: z.enum(['feature_addition', 'refactoring', 'bug_fix', 'performance_optimization', 'auto_detect']) .optional() .default('auto_detect') .describe("Type of development task (auto_detect will analyze the prompt to determine the task type)"), include_meta_prompt: z.boolean() .optional() .default(true) .describe("Whether to include meta-prompt generation for downstream AI agents"), output_format: z.enum(['package', 'structured']) .optional() .default('package') .describe("Output format: 'package' for XML context package, 'structured' for JSON analysis") }; export const contextCuratorExecutor = async (params, config, context) => { try { logger.info({ sessionId: context?.sessionId, conversationId: context?.conversationId, prompt: params.prompt }, 'Context Curator tool execution initiated'); let taskType = params.task_type || 'general'; if (taskType === 'auto_detect') { taskType = 'general'; } const validatedParams = validateContextCuratorInput({ userPrompt: params.prompt, projectPath: (params.target_directory === '/' || !params.target_directory) ? process.cwd() : params.target_directory, taskType: taskType, maxFiles: 100, includePatterns: ['**/*'], excludePatterns: ['node_modules/**', '.git/**', 'dist/**', 'build/**'], focusAreas: [], useCodeMapCache: true, codeMapCacheMaxAgeMinutes: 1440 }); logger.debug({ validatedParams: { ...validatedParams, userPrompt: validatedParams.userPrompt.substring(0, 100) + '...' } }, 'Input parameters validated successfully'); const jobParams = { ...params, validatedParams }; const jobId = jobManager.createJob('curate-context', jobParams); logger.info({ jobId, sessionId: context?.sessionId, taskType: validatedParams.taskType, targetDirectory: validatedParams.projectPath }, 'Context curation job created successfully'); jobManager.updateJobStatus(jobId, JobStatus.PENDING, 'Context curation job created and queued for processing', 0); processContextCurationJob(jobId, validatedParams, config).catch(error => { logger.error({ jobId, error: error.message }, 'Background context curation job failed'); jobManager.updateJobStatus(jobId, JobStatus.FAILED, `Background processing failed: ${error.message}`, 0); }); return { content: [ { type: "text", text: JSON.stringify({ jobId, status: 'initiated', message: 'Context curation job has been created. Use get-job-result to check progress and retrieve results.', estimatedProcessingTime: '2-5 minutes', pollingRecommendation: 'Poll every 10-15 seconds for optimal user experience' }, null, 2) } ], isError: false }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; logger.error({ error: errorMessage, sessionId: context?.sessionId, params: { ...params, prompt: typeof params.prompt === 'string' ? params.prompt.substring(0, 100) + '...' : params.prompt } }, 'Context Curator tool execution failed'); return { content: [ { type: "text", text: JSON.stringify({ error: 'Context curation failed', message: errorMessage, details: 'Please check your input parameters and try again' }, null, 2) } ], isError: true }; } }; async function processContextCurationJob(jobId, input, config) { try { logger.info({ jobId }, 'Starting background Context Curator job processing'); const contextCuratorService = ContextCuratorService.getInstance(); const contextPackage = await contextCuratorService.executeWorkflow(jobId, input, config); const result = { content: [ { type: "text", text: JSON.stringify({ success: true, jobId, contextPackage: { id: getPackageId(contextPackage) || jobId, taskType: getTaskType(contextPackage), totalFiles: getTotalFiles(contextPackage), totalTokens: getTotalTokens(contextPackage), averageRelevanceScore: getAverageRelevanceScore(contextPackage), cacheHitRate: getCacheHitRate(contextPackage), processingTimeMs: getProcessingTimeMs(contextPackage), outputPath: `VibeCoderOutput/context-curator/context-package-${jobId}.xml` }, message: 'Context curation completed successfully', files: getPackageFiles(contextPackage).map((file) => ({ path: getFilePath(file), relevanceScore: getFileRelevanceScore(file), categories: getFileCategories(file) })), metaPrompt: { systemPrompt: getMetaPromptSystemPrompt(contextPackage), userPrompt: getMetaPromptUserPrompt(contextPackage), estimatedComplexity: getMetaPromptComplexity(contextPackage), epicsCount: getMetaPromptEpicsCount(contextPackage), guidelinesCount: getMetaPromptGuidelinesCount(contextPackage) } }, null, 2) } ], isError: false }; jobManager.setJobResult(jobId, result); logger.info({ jobId, totalFiles: getTotalFiles(contextPackage), processingTime: getProcessingTimeMs(contextPackage) }, 'Context Curator job completed successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error({ jobId, error: errorMessage }, 'Context Curator job failed'); const errorResult = { content: [ { type: "text", text: JSON.stringify({ success: false, jobId, error: 'Context curation failed', message: errorMessage, details: 'The context curation workflow encountered an error during processing' }, null, 2) } ], isError: true }; jobManager.setJobResult(jobId, errorResult); } } const contextCuratorToolDefinition = { name: "curate-context", description: "Intelligently analyzes codebases and curates comprehensive context packages for AI-driven development tasks. Generates refined prompts, relevance-ranked files, and meta-prompts for downstream AI agents. Supports automatic task type detection, file relevance scoring, content optimization, and XML output formatting for seamless integration with AI development workflows.", inputSchema: contextCuratorInputSchemaShape, executor: contextCuratorExecutor, }; export async function initDirectories() { const baseOutputDir = getBaseOutputDir(); try { await fs.ensureDir(baseOutputDir); const toolDir = path.join(baseOutputDir, 'context-curator'); await fs.ensureDir(toolDir); logger.debug(`Ensured context-curator directory exists: ${toolDir}`); } catch (error) { logger.error({ err: error, path: baseOutputDir }, `Failed to ensure base output directory exists for context-curator.`); } } registerTool(contextCuratorToolDefinition); logger.info('Context Curator tool registered successfully'); export { contextCuratorToolDefinition, contextCuratorInputSchemaShape };