UNPKG

vineguard-mcp

Version:

VineGuard MCP Server v2.1 - Intelligent QA Workflow System with advanced test generation for Jest/RTL, Cypress, and Playwright. Features smart project analysis, progressive testing strategies, and comprehensive quality patterns for React/Vue/Angular proje

549 lines 21.7 kB
#!/usr/bin/env node /** * VineGuard MCP Server v2.1 - Intelligent QA Workflow System * Aggregates all VineGuard feature packages for comprehensive testing workflows */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; // Import VineGuard feature packages import { WorkflowOrchestrator, ProjectScanner } from 'vineguard-core'; import { JestGenerator, CypressGenerator, PlaywrightGenerator } from 'vineguard-test-cultivator'; import { SecurityAuditor } from 'vineguard-pest-detector'; import { PRDGenerator } from 'vineguard-vineyard-scanner'; import { FixGenerator } from 'vineguard-vine-healer'; import { ConfigAnalyzer, WorkflowDetector } from 'vineguard-vine-inspector'; import { TestPlanner } from 'vineguard-harvest-planner'; const PROJECT_ROOT = process.env.VINEGUARD_VINEYARD_ROOT || process.cwd(); const VINEGUARD_MODE = process.env.VINEGUARD_MODE || 'orchestrator'; const MAX_RESPONSE_LENGTH = parseInt(process.env.VINEGUARD_MAX_RESPONSE_LENGTH || '100000'); // 100KB default console.error(`[VineGuard MCP v2.1] Starting server for project: ${PROJECT_ROOT}`); console.error(`[VineGuard MCP v2.1] Mode: ${VINEGUARD_MODE}`); const server = new Server({ name: 'vineguard', version: '2.1.0', }, { capabilities: { tools: {}, resources: {} } }); // Initialize orchestrator const orchestrator = new WorkflowOrchestrator(); const projectScanner = new ProjectScanner(); // Tool Handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Core workflow tools { name: 'scan_project', description: 'Analyze project structure, detect frameworks, and assess testing setup', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Project root path to scan', default: PROJECT_ROOT }, deep: { type: 'boolean', description: 'Perform deep analysis including dependencies', default: false } } } }, { name: 'analyze_prd', description: 'Analyze Product Requirements Document and extract user stories, test scenarios', inputSchema: { type: 'object', properties: { prdPath: { type: 'string', description: 'Path to PRD file (markdown, text, or README)', default: './README.md' }, includeTestingImplications: { type: 'boolean', description: 'Include testing implications analysis', default: true } } } }, { name: 'create_test_plan', description: 'Generate comprehensive test plan based on project analysis', inputSchema: { type: 'object', properties: { projectAnalysis: { type: 'object', description: 'Project analysis results from scan_project' }, prdAnalysis: { type: 'object', description: 'PRD analysis results (optional)' }, coverageTarget: { type: 'number', description: 'Target test coverage percentage', default: 85 }, includeAccessibility: { type: 'boolean', description: 'Include accessibility tests', default: true }, includeSecurity: { type: 'boolean', description: 'Include security tests', default: true } } } }, { name: 'generate_test', description: 'Generate comprehensive test file for a component or function', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the source file to generate tests for' }, testType: { type: 'string', enum: ['unit', 'integration', 'e2e'], description: 'Type of test to generate', default: 'unit' }, framework: { type: 'string', enum: ['jest', 'vitest', 'playwright', 'cypress'], description: 'Testing framework to use', default: 'jest' }, template: { type: 'string', enum: ['basic', 'comprehensive', 'component'], description: 'Test template style', default: 'comprehensive' } }, required: ['filePath'] } }, { name: 'run_tests', description: 'Execute tests using detected or specified test framework', inputSchema: { type: 'object', properties: { framework: { type: 'string', enum: ['npm', 'jest', 'vitest', 'playwright', 'cypress'], description: 'Test framework to use', default: 'npm' }, coverage: { type: 'boolean', description: 'Generate coverage report', default: false }, pattern: { type: 'string', description: 'Test file pattern to run' } } } }, { name: 'security_audit', description: 'Perform comprehensive security audit of the codebase', inputSchema: { type: 'object', properties: { projectPath: { type: 'string', description: 'Path to project root', default: PROJECT_ROOT }, includeNodeModules: { type: 'boolean', description: 'Include node_modules in scan', default: false }, includeDependencyAudit: { type: 'boolean', description: 'Include npm dependency audit', default: true } } } }, { name: 'analyze_config', description: 'Analyze testing framework configurations for optimization', inputSchema: { type: 'object', properties: { projectRoot: { type: 'string', description: 'Project root path', default: PROJECT_ROOT } } } }, { name: 'detect_workflow', description: 'Detect project workflow and provide testing recommendations', inputSchema: { type: 'object', properties: { projectRoot: { type: 'string', description: 'Project root path', default: PROJECT_ROOT } } } }, { name: 'generate_fixes', description: 'Generate automated fixes for detected issues', inputSchema: { type: 'object', properties: { bugReport: { type: 'object', description: 'Bug detection results with issues' }, autoApply: { type: 'boolean', description: 'Automatically apply safe fixes', default: false }, safeOnly: { type: 'boolean', description: 'Only generate safe fixes', default: true } } } }, { name: 'orchestrate_workflow', description: 'Execute complete VineGuard testing workflow', inputSchema: { type: 'object', properties: { workflowType: { type: 'string', enum: ['complete-qa', 'quick-test', 'security-audit', 'greenfield-setup'], description: 'Type of workflow to execute', default: 'complete-qa' }, projectPath: { type: 'string', description: 'Project path to analyze', default: PROJECT_ROOT }, options: { type: 'object', description: 'Workflow-specific options', default: {} } } } } ] }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'scan_project': return await handleScanProject(args); case 'analyze_prd': return await handleAnalyzePRD(args); case 'create_test_plan': return await handleCreateTestPlan(args); case 'generate_test': return await handleGenerateTest(args); case 'run_tests': return await handleRunTests(args); case 'security_audit': return await handleSecurityAudit(args); case 'analyze_config': return await handleAnalyzeConfig(args); case 'detect_workflow': return await handleDetectWorkflow(args); case 'generate_fixes': return await handleGenerateFixes(args); case 'orchestrate_workflow': return await handleOrchestrateWorkflow(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { console.error(`[VineGuard MCP] Error in ${name}:`, error); return { content: [ { type: 'text', text: `Error executing ${name}: ${error instanceof Error ? error.message : 'Unknown error'}` } ] }; } }); // Utility function to handle large responses function formatResponse(data, maxLength = MAX_RESPONSE_LENGTH) { const jsonString = JSON.stringify(data, null, 2); if (jsonString.length <= maxLength) { return jsonString; } // Response is too large, provide truncated version with metadata const truncated = jsonString.substring(0, maxLength - 200); const lastCompleteLineIndex = truncated.lastIndexOf('\n'); const safelyTruncated = lastCompleteLineIndex > 0 ? truncated.substring(0, lastCompleteLineIndex) : truncated; return `${safelyTruncated}\n\n... [RESPONSE TRUNCATED] ...\n\n` + `Original response was ${jsonString.length} characters (${(jsonString.length / 1024).toFixed(1)}KB).\n` + `Showing first ${safelyTruncated.length} characters.\n` + `To see full response, increase VINEGUARD_MAX_RESPONSE_LENGTH environment variable.`; } // Tool implementations using feature packages async function handleScanProject(args) { const projectPath = args.path || PROJECT_ROOT; const deep = args.deep || false; console.error(`[VineGuard] Scanning project: ${projectPath}`); const scanResult = await projectScanner.scanProject(projectPath, { deep }); return { content: [ { type: 'text', text: formatResponse(scanResult) } ] }; } async function handleAnalyzePRD(args) { const prdPath = args.prdPath || './README.md'; const includeTestingImplications = args.includeTestingImplications !== false; console.error(`[VineGuard] Analyzing PRD: ${prdPath}`); const prdGenerator = new PRDGenerator(PROJECT_ROOT); const prdDocument = await prdGenerator.generatePRD({ identifyTestingImplications: includeTestingImplications }); return { content: [ { type: 'text', text: formatResponse(prdDocument) } ] }; } async function handleCreateTestPlan(args) { const { projectAnalysis, prdAnalysis, coverageTarget = 85, includeAccessibility = true, includeSecurity = true } = args; console.error('[VineGuard] Creating test plan'); const testPlanner = new TestPlanner(); let testPlan; if (prdAnalysis) { // Generate from PRD testPlan = await testPlanner.generateTestPlan(prdAnalysis, { coverageTarget, includeAccessibility, includeSecurity }); } else if (projectAnalysis) { // Generate from project analysis testPlan = await testPlanner.generateTestPlanFromProject(projectAnalysis.name || 'Project', { type: projectAnalysis.type || 'web', frameworks: projectAnalysis.frameworks || [], complexity: projectAnalysis.complexity || 'medium', hasTests: projectAnalysis.hasTests || false, testCoverage: projectAnalysis.testCoverage || 0 }, { coverageTarget, includeAccessibility, includeSecurity }); } else { throw new Error('Either projectAnalysis or prdAnalysis is required'); } return { content: [ { type: 'text', text: formatResponse(testPlan) } ] }; } async function handleGenerateTest(args) { const { filePath, testType = 'unit', framework = 'jest', template = 'comprehensive' } = args; console.error(`[VineGuard] Generating ${testType} test for: ${filePath}`); let testContent = ''; switch (framework) { case 'jest': case 'vitest': testContent = JestGenerator.generateTest({ componentName: filePath.split('/').pop()?.replace(/\.(ts|tsx|js|jsx)$/, '') || 'Component', filePath, testType: (testType === 'unit' || testType === 'integration' || testType === 'accessibility') ? testType : 'unit', isComponent: filePath.includes('.tsx') || filePath.includes('.jsx'), isReact: filePath.includes('.tsx') || filePath.includes('.jsx') }); break; case 'playwright': testContent = PlaywrightGenerator.generateTest({ featureName: filePath.split('/').pop()?.replace(/\.(ts|tsx|js|jsx)$/, '') || 'Feature', testType: (testType === 'e2e' || testType === 'api' || testType === 'visual' || testType === 'mobile' || testType === 'performance' || testType === 'smoke') ? testType : 'e2e', baseUrl: 'http://localhost:3000' }); break; case 'cypress': testContent = CypressGenerator.generateTest({ featureName: filePath.split('/').pop()?.replace(/\.(ts|tsx|js|jsx)$/, '') || 'Feature', testType: (testType === 'user-flow' || testType === 'api' || testType === 'smoke' || testType === 'visual' || testType === 'accessibility') ? testType : 'user-flow', baseUrl: 'http://localhost:3000' }); break; default: throw new Error(`Unsupported framework: ${framework}`); } return { content: [ { type: 'text', text: testContent } ] }; } async function handleRunTests(args) { const { framework = 'npm', coverage = false, pattern } = args; console.error(`[VineGuard] Running tests with ${framework}`); // This would typically integrate with the test execution system // For now, return a placeholder implementation return { content: [ { type: 'text', text: `Running tests with ${framework}${coverage ? ' (with coverage)' : ''}${pattern ? ` (pattern: ${pattern})` : ''}` } ] }; } async function handleSecurityAudit(args) { const { projectPath = PROJECT_ROOT, includeNodeModules = false, includeDependencyAudit = true } = args; console.error(`[VineGuard] Running security audit on: ${projectPath}`); const auditResult = await SecurityAuditor.auditProject(projectPath, { includeNodeModules, includeDependencyAudit }); return { content: [ { type: 'text', text: formatResponse(auditResult) } ] }; } async function handleAnalyzeConfig(args) { const projectRoot = args.projectRoot || PROJECT_ROOT; console.error(`[VineGuard] Analyzing configurations in: ${projectRoot}`); const configAnalyzer = new ConfigAnalyzer(projectRoot); const analyses = await configAnalyzer.analyzeAllConfigs(); const report = await configAnalyzer.generateOptimizationReport(analyses); return { content: [ { type: 'text', text: formatResponse({ analyses, report }) } ] }; } async function handleDetectWorkflow(args) { const projectRoot = args.projectRoot || PROJECT_ROOT; console.error(`[VineGuard] Detecting workflow for: ${projectRoot}`); const workflowDetector = new WorkflowDetector(projectRoot); const analysis = await workflowDetector.analyzeProject(); return { content: [ { type: 'text', text: formatResponse(analysis) } ] }; } async function handleGenerateFixes(args) { const { bugReport, autoApply = false, safeOnly = true } = args; console.error('[VineGuard] Generating fixes for detected issues'); const fixResult = await FixGenerator.generateFixes(bugReport, { autoApply, safeOnly }); return { content: [ { type: 'text', text: formatResponse(fixResult) } ] }; } async function handleOrchestrateWorkflow(args) { const { workflowType = 'complete-qa', projectPath = PROJECT_ROOT, options = {} } = args; console.error(`[VineGuard] Orchestrating ${workflowType} workflow for: ${projectPath}`); const context = { projectPath, ...options }; const result = await orchestrator.executeWorkflow(workflowType, context); return { content: [ { type: 'text', text: formatResponse(result) } ] }; } // Resource handlers (optional) server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [] }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; throw new Error(`Resource not found: ${uri}`); }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('[VineGuard MCP v2.1] Server started successfully'); } main().catch((error) => { console.error('[VineGuard MCP] Fatal error:', error); process.exit(1); }); //# sourceMappingURL=index.js.map