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
JavaScript
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 };