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.

617 lines (616 loc) 22.5 kB
import { extractProjectFromContext } from '../../tools/vibe-task-manager/utils/context-extractor.js'; import logger from '../../logger.js'; export class ContextAwareParameterExtractor { toolSpecs = { 'research-manager': { required: ['topic'], optional: ['depth', 'focus', 'sources'], types: { topic: 'string', depth: 'string', focus: 'array', sources: 'array' }, defaults: { depth: 'comprehensive', sources: ['technical', 'documentation'] } }, 'prd-generator': { required: ['product', 'feature'], optional: ['stakeholders', 'timeline', 'scope'], types: { product: 'string', feature: 'string', stakeholders: 'array', timeline: 'string', scope: 'string' }, defaults: { scope: 'MVP' }, contextFields: { currentProject: 'product' } }, 'user-stories-generator': { required: ['feature'], optional: ['persona', 'acceptance_criteria', 'priority'], types: { feature: 'string', persona: 'string', acceptance_criteria: 'boolean', priority: 'string' }, defaults: { acceptance_criteria: true, priority: 'medium' } }, 'task-list-generator': { required: ['requirement'], optional: ['breakdown_level', 'include_dependencies', 'format'], types: { requirement: 'string', breakdown_level: 'string', include_dependencies: 'boolean', format: 'string' }, defaults: { breakdown_level: 'detailed', include_dependencies: true, format: 'markdown' } }, 'fullstack-starter-kit-generator': { required: ['project_name'], optional: ['frontend', 'backend', 'database', 'features'], types: { project_name: 'string', frontend: 'string', backend: 'string', database: 'string', features: 'array' }, defaults: { frontend: 'React', backend: 'Node.js', database: 'PostgreSQL' }, contextFields: { currentProject: 'project_name' } }, 'rules-generator': { required: ['project_type'], optional: ['language', 'framework', 'style_guide', 'linting'], types: { project_type: 'string', language: 'string', framework: 'string', style_guide: 'string', linting: 'boolean' }, defaults: { linting: true } }, 'map-codebase': { required: ['path'], optional: ['include_tests', 'max_depth', 'file_types', 'output_format'], types: { path: 'string', include_tests: 'boolean', max_depth: 'number', file_types: 'array', output_format: 'string' }, defaults: { path: '.', include_tests: false, max_depth: 5, output_format: 'mermaid' } }, 'curate-context': { required: ['task'], optional: ['codebase_path', 'relevance_threshold', 'max_files'], types: { task: 'string', codebase_path: 'string', relevance_threshold: 'number', max_files: 'number' }, defaults: { codebase_path: '.', relevance_threshold: 0.7, max_files: 20 } }, 'run-workflow': { required: ['workflowName'], optional: ['workflowInput', 'async_execution', 'notification'], types: { workflowName: 'string', workflowInput: 'object', async_execution: 'boolean', notification: 'boolean' }, defaults: { async_execution: false, notification: true } }, 'get-job-result': { required: ['jobId'], optional: ['wait_timeout', 'include_logs'], types: { jobId: 'string', wait_timeout: 'number', include_logs: 'boolean' }, defaults: { wait_timeout: 30000, include_logs: false } }, 'register-agent': { required: ['agentId'], optional: ['capabilities', 'transport', 'priority'], types: { agentId: 'string', capabilities: 'array', transport: 'string', priority: 'number' }, defaults: { transport: 'stdio', priority: 5 } }, 'get-agent-tasks': { required: ['agentId'], optional: ['capabilities', 'limit', 'timeout'], types: { agentId: 'string', capabilities: 'array', limit: 'number', timeout: 'number' }, defaults: { limit: 10, timeout: 5000 } }, 'submit-task-response': { required: ['taskId', 'status'], optional: ['result', 'error_message', 'completion_metadata'], types: { taskId: 'string', status: 'string', result: 'object', error_message: 'string', completion_metadata: 'object' }, defaults: {} }, 'vibe-task-manager': { required: ['command'], optional: ['projectName', 'taskId', 'description', 'options'], types: { command: 'string', projectName: 'string', taskId: 'string', description: 'string', options: 'object' }, defaults: {}, contextFields: { currentProject: 'projectName', currentTask: 'taskId' } }, 'process-request': { required: ['request'], optional: ['context', 'preferences', 'confirmation_threshold'], types: { request: 'string', context: 'object', preferences: 'object', confirmation_threshold: 'number' }, defaults: { confirmation_threshold: 0.8 } } }; async extractParameters(intent, input, context, toolCandidates) { try { const bestTool = toolCandidates[0]?.tool || 'process-request'; const spec = this.toolSpecs[bestTool]; if (!spec) { logger.debug({ tool: bestTool }, 'No parameter spec found for tool, using basic extraction'); return this.performBasicExtraction(intent, input, context); } const results = await Promise.all([ this.extractFromContext(spec, context), this.extractFromEntities(spec, intent.entities), this.extractFromPatterns(spec, input, bestTool), this.extractFromInference(spec, input, context) ]); const parameters = this.combineExtractionResults(results, spec); const enrichedParameters = await this.validateAndEnrichParameters(parameters, spec, context, bestTool); logger.debug({ tool: bestTool, extractedParams: Object.keys(enrichedParameters), sources: results.map(r => r.source) }, 'Parameter extraction completed'); return enrichedParameters; } catch (error) { logger.error({ err: error, intent: intent.intent }, 'Parameter extraction failed'); return this.performBasicExtraction(intent, input, context); } } async mapToToolParameters(toolName, intent, extractedParams, context) { const spec = this.toolSpecs[toolName]; if (!spec) { return { ...extractedParams, _intent: intent.intent }; } const mappedParams = {}; for (const param of spec.required) { if (extractedParams[param] !== undefined) { mappedParams[param] = this.castParameterType(extractedParams[param], spec.types[param]); } else { const alternative = this.findAlternativeParameter(param, extractedParams); if (alternative !== null) { mappedParams[param] = this.castParameterType(alternative, spec.types[param]); } } } for (const param of spec.optional) { if (extractedParams[param] !== undefined) { mappedParams[param] = this.castParameterType(extractedParams[param], spec.types[param]); } else { if (spec.defaults[param] !== undefined) { mappedParams[param] = spec.defaults[param]; } } } await this.addToolSpecificEnrichments(toolName, mappedParams, context, intent); return mappedParams; } async extractFromContext(spec, context) { const parameters = {}; const confidence = 0.9; try { if (spec.contextFields) { for (const [contextField, paramName] of Object.entries(spec.contextFields)) { const contextValue = context[contextField]; if (contextValue) { parameters[paramName] = contextValue; } } } if (spec.required.includes('path') || spec.optional.includes('path')) { try { const projectContext = await extractProjectFromContext({ sessionId: context.sessionId, currentProject: context.currentProject, config: {}, taskManagerConfig: {} }); if (projectContext.confidence > 0.7) { parameters.path = process.cwd(); parameters.projectName = projectContext.projectName; } } catch (error) { logger.debug({ err: error }, 'Project context extraction failed'); } } if (context.userPreferences && Object.keys(context.userPreferences).length > 0) { for (const [key, value] of Object.entries(context.userPreferences)) { if (spec.optional.includes(key) || spec.required.includes(key)) { parameters[key] = value; } } } return { parameters, confidence, source: 'context', suggestions: [] }; } catch (error) { logger.debug({ err: error }, 'Context extraction failed'); return { parameters: {}, confidence: 0, source: 'context', suggestions: [] }; } } async extractFromEntities(spec, entities) { const parameters = {}; const suggestions = []; let confidence = 0.8; for (const entity of entities) { const paramName = this.mapEntityTypeToParameter(entity.type, spec); if (paramName && (spec.required.includes(paramName) || spec.optional.includes(paramName))) { parameters[paramName] = entity.value; if (entity.confidence < 0.7) { confidence *= 0.9; } } } return { parameters, confidence, source: 'entities', suggestions }; } async extractFromPatterns(spec, input, toolName) { const parameters = {}; const suggestions = []; const confidence = 0.7; switch (toolName) { case 'research-manager': Object.assign(parameters, this.extractResearchPatterns(input)); break; case 'prd-generator': Object.assign(parameters, this.extractPRDPatterns(input)); break; case 'fullstack-starter-kit-generator': Object.assign(parameters, this.extractStarterKitPatterns(input)); break; case 'map-codebase': Object.assign(parameters, this.extractCodebasePatterns(input)); break; } return { parameters, confidence, source: 'patterns', suggestions }; } async extractFromInference(spec, input, context) { const parameters = {}; const suggestions = []; const confidence = 0.6; for (const param of spec.required) { if (!parameters[param]) { const inferred = await this.inferParameter(param, input, context, spec); if (inferred) { parameters[param] = inferred.value; suggestions.push(inferred.suggestion); } } } return { parameters, confidence, source: 'inference', suggestions }; } combineExtractionResults(results, spec) { const combined = {}; const priorityOrder = ['defaults', 'inference', 'patterns', 'entities', 'context']; Object.assign(combined, spec.defaults); for (const priority of priorityOrder) { const result = results.find(r => r.source === priority); if (result) { Object.assign(combined, result.parameters); } } return combined; } async validateAndEnrichParameters(parameters, spec, context, toolName) { const enriched = { ...parameters }; for (const required of spec.required) { if (enriched[required] === undefined || enriched[required] === null || enriched[required] === '') { const fallback = await this.provideFallbackValue(required, toolName, context); if (fallback) { enriched[required] = fallback; } } } for (const [param, type] of Object.entries(spec.types)) { if (enriched[param] !== undefined) { enriched[param] = this.castParameterType(enriched[param], type); } } return enriched; } performBasicExtraction(intent, input, context) { const parameters = { request: intent.originalInput, intent: intent.intent, sessionId: context.sessionId }; for (const entity of intent.entities) { parameters[entity.type] = entity.value; } if (context.currentProject) { parameters.currentProject = context.currentProject; } if (context.currentTask) { parameters.currentTask = context.currentTask; } return parameters; } mapEntityTypeToParameter(entityType, spec) { const mappings = { 'project_name': 'project', 'product_name': 'product', 'feature_name': 'feature', 'task_name': 'task', 'file_path': 'path', 'technology': 'language', 'topic': 'topic', 'requirement': 'requirement' }; const mapped = mappings[entityType] || entityType; if (spec.required.includes(mapped) || spec.optional.includes(mapped)) { return mapped; } return null; } castParameterType(value, type) { switch (type) { case 'string': return String(value); case 'number': return typeof value === 'number' ? value : parseFloat(String(value)) || 0; case 'boolean': return typeof value === 'boolean' ? value : ['true', '1', 'yes', 'on'].includes(String(value).toLowerCase()); case 'array': return Array.isArray(value) ? value : [value]; case 'object': return typeof value === 'object' ? value : { value }; default: return value; } } findAlternativeParameter(targetParam, parameters) { const alternatives = { 'topic': ['query', 'subject', 'research_topic'], 'product': ['project', 'application', 'system'], 'feature': ['functionality', 'capability', 'requirement'], 'path': ['directory', 'folder', 'location'], 'task': ['description', 'title', 'name'] }; const alternativeNames = alternatives[targetParam] || []; for (const alt of alternativeNames) { if (parameters[alt] !== undefined) { return parameters[alt]; } } return null; } async provideFallbackValue(param, toolName, context) { const fallbacks = { 'research-manager': { topic: 'general technical research' }, 'map-codebase': { path: '.' }, 'prd-generator': { product: context.currentProject || 'New Product' }, 'fullstack-starter-kit-generator': { project_name: context.currentProject || 'new-project' } }; return fallbacks[toolName]?.[param]; } async addToolSpecificEnrichments(toolName, parameters, context, intent) { parameters._sessionId = context.sessionId; parameters._intent = intent.intent; parameters._confidence = intent.confidence; parameters._timestamp = new Date().toISOString(); switch (toolName) { case 'vibe-task-manager': if (!parameters.projectName && context.currentProject) { parameters.projectName = context.currentProject; } break; case 'curate-context': if (!parameters.codebase_path) { parameters.codebase_path = process.cwd(); } break; } } async inferParameter(param, input, context, _spec) { switch (param) { case 'topic': { const topicMatch = input.match(/(?:research|about|regarding|on)\s+(.+?)(?:\s|$)/i); if (topicMatch) { return { value: topicMatch[1].trim(), suggestion: `Inferred research topic: ${topicMatch[1].trim()}` }; } break; } case 'feature': { const featureMatch = input.match(/(?:feature|functionality|capability)\s+(.+?)(?:\s|$)/i); if (featureMatch) { return { value: featureMatch[1].trim(), suggestion: `Inferred feature: ${featureMatch[1].trim()}` }; } break; } case 'product': if (context.currentProject) { return { value: context.currentProject, suggestion: `Using current project as product: ${context.currentProject}` }; } break; } return null; } extractResearchPatterns(input) { const params = {}; const topicMatch = input.match(/(?:research|study|investigate|analyze)\s+(.+?)(?:\s|$)/i); if (topicMatch) { params.topic = topicMatch[1].trim(); } if (input.includes('deep') || input.includes('comprehensive') || input.includes('detailed')) { params.depth = 'comprehensive'; } else if (input.includes('quick') || input.includes('brief') || input.includes('overview')) { params.depth = 'brief'; } return params; } extractPRDPatterns(input) { const params = {}; const productMatch = input.match(/(?:prd|requirements?)\s+for\s+(.+?)(?:\s|$)/i); if (productMatch) { params.product = productMatch[1].trim(); } return params; } extractStarterKitPatterns(input) { const params = {}; const projectMatch = input.match(/(?:create|generate|setup)\s+(?:project\s+)?(.+?)(?:\s|$)/i); if (projectMatch) { params.project_name = projectMatch[1].trim(); } const techPatterns = { frontend: /\b(react|vue|angular|svelte)\b/gi, backend: /\b(node|express|fastify|nestjs|python|django|flask)\b/gi, database: /\b(postgres|mysql|mongodb|sqlite|redis)\b/gi }; for (const [key, pattern] of Object.entries(techPatterns)) { const match = input.match(pattern); if (match) { params[key] = match[0].toLowerCase(); } } return params; } extractCodebasePatterns(input) { const params = {}; const pathMatch = input.match(/(?:map|analyze)\s+(.+?)(?:\s|$)/i); if (pathMatch && pathMatch[1].includes('/')) { params.path = pathMatch[1].trim(); } return params; } }