UNPKG

code-auditor-mcp

Version:

Multi-language code quality auditor with MCP server - Analyze TypeScript, JavaScript, and Go code for SOLID principles, DRY violations, security patterns, and more

1,058 lines 49.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { createAuditRunner } from './auditRunner.js'; import { syncFileIndex, searchFunctions, findDefinition, clearIndex } from './codeIndexService.js'; import { CodeIndexDB } from './codeIndexDB.js'; import { ConfigGeneratorFactory } from './generators/ConfigGeneratorFactory.js'; import { IS_DEV_MODE } from './constants.js'; import { scanFunctionsInDirectory } from './functionScanner.js'; import path from 'node:path'; import chalk from 'chalk'; import { createWriteStream } from 'node:fs'; // Set up file logging (only in development mode) let logStream = null; if (IS_DEV_MODE) { const logFilePath = path.join(process.cwd(), 'mcp-server.log'); logStream = createWriteStream(logFilePath, { flags: 'a' }); console.error(chalk.blue('[INFO]'), `Development mode: Logging to file: ${logFilePath}`); } // Save original console methods const originalConsoleLog = console.log; const originalConsoleError = console.error; // Override console.log (with conditional file logging) console.log = (...args) => { if (logStream) { const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' '); const timestamp = new Date().toISOString(); logStream.write(`[${timestamp}] [LOG] ${message}\n`); } originalConsoleLog.apply(console, args); }; // Override console.error (with conditional file logging) console.error = (...args) => { if (logStream) { const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' '); const timestamp = new Date().toISOString(); logStream.write(`[${timestamp}] [ERROR] ${message}\n`); } originalConsoleError.apply(console, args); }; const tools = [ { name: 'audit', description: 'Run a comprehensive code audit on files or directories', parameters: [ { name: 'path', type: 'string', required: false, description: 'The file or directory path to audit (defaults to current directory)', default: process.cwd(), }, { name: 'analyzers', type: 'array', required: false, description: 'List of analyzers to run (solid, dry, security, component, data-access)', default: ['solid', 'dry'], }, { name: 'minSeverity', type: 'string', required: false, description: 'Minimum severity level to report', default: 'info', enum: ['info', 'warning', 'critical'], }, { name: 'indexFunctions', type: 'boolean', required: false, description: 'Automatically index functions during audit', default: true, }, { name: 'limit', type: 'number', required: false, description: 'Maximum number of violations to return (default: 50, max: 100)', default: 50, }, { name: 'offset', type: 'number', required: false, description: 'Number of violations to skip for pagination (default: 0)', default: 0, }, { name: 'auditId', type: 'string', required: false, description: 'Existing audit ID to retrieve cached results', }, { name: 'useCache', type: 'boolean', required: false, description: 'Store results in cache for pagination (default: true)', default: true, }, ], }, { name: 'audit_health', description: 'Quick health check of a codebase with key metrics', parameters: [ { name: 'path', type: 'string', required: false, description: 'The directory path to check', default: process.cwd(), }, { name: 'threshold', type: 'number', required: false, description: 'Health score threshold (0-100) for pass/fail', default: 70, }, { name: 'indexFunctions', type: 'boolean', required: false, description: 'Automatically index functions during health check', default: true, }, ], }, { name: 'search_code', description: 'Search indexed functions and React components with natural language queries. Supports operators: entity:component, component:functional|class|memo|forwardRef, hook:useState|useEffect|etc, prop:propName, dep:packageName, dependency:lodash, uses:express, calls:functionName, calledby:functionName, dependents-of:functionName, used-by:functionName, depends-on:module, imports-from:file, unused-imports, dead-imports, type:fileType, file:path, lang:language, complexity:1-10, jsdoc:true|false', parameters: [ { name: 'query', type: 'string', required: true, description: 'Search query with natural language and/or operators. Examples: "Button component:functional", "entity:component hook:useState", "render prop:onClick", "dep:lodash", "calls:validateUser", "unused-imports", "dependents-of:authenticate"', }, { name: 'filters', type: 'object', required: false, description: 'Optional filters (language, filePath, dependencies, componentType, entityType, searchMode). Set searchMode to "content" to search within function bodies, "metadata" for names/signatures only, or "both" for combined search', }, { name: 'limit', type: 'number', required: false, description: 'Maximum results to return', default: 50, }, { name: 'offset', type: 'number', required: false, description: 'Offset for pagination', default: 0, }, ], }, { name: 'find_definition', description: 'Find the exact definition of a specific function', parameters: [ { name: 'name', type: 'string', required: true, description: 'Function name to find', }, { name: 'filePath', type: 'string', required: false, description: 'Optional file path to narrow search', }, ], }, { name: 'sync_index', description: 'Synchronize, cleanup, or reset the code index', parameters: [ { name: 'mode', type: 'string', required: false, description: 'Operation mode: sync (update), cleanup (remove stale), or reset (clear all)', default: 'sync', enum: ['sync', 'cleanup', 'reset'], }, { name: 'path', type: 'string', required: false, description: 'Optional specific path to sync', }, ], }, { name: 'generate_ai_config', description: 'Generate configuration files for AI coding assistants', parameters: [ { name: 'tools', type: 'array', required: true, description: 'AI tools to configure (cursor, continue, copilot, claude, zed, windsurf, cody, aider, cline, pearai)', }, { name: 'outputDir', type: 'string', required: false, description: 'Output directory for configuration files', default: '.', }, ], }, { name: 'get_workflow_guide', description: 'Get recommended workflows and best practices for using code auditor tools effectively', parameters: [ { name: 'scenario', type: 'string', required: false, description: 'Specific scenario: initial-setup, react-development, code-review, find-patterns, maintenance. Leave empty to see all.', }, ], }, // Analyzer Configuration Tools { name: 'set_analyzer_config', description: 'Set or update analyzer configuration that persists across audit runs', parameters: [ { name: 'analyzerName', type: 'string', required: true, description: 'The analyzer to configure (solid, dry, security, etc.)', }, { name: 'config', type: 'object', required: true, description: 'Configuration object for the analyzer (e.g., thresholds, rules)', }, { name: 'projectPath', type: 'string', required: false, description: 'Optional project path for project-specific config (defaults to global)', }, ], }, { name: 'get_analyzer_config', description: 'Get current configuration for an analyzer', parameters: [ { name: 'analyzerName', type: 'string', required: false, description: 'Specific analyzer name, or omit to get all configs', }, { name: 'projectPath', type: 'string', required: false, description: 'Optional project path to get project-specific config', }, ], }, { name: 'reset_analyzer_config', description: 'Reset analyzer configuration to defaults', parameters: [ { name: 'analyzerName', type: 'string', required: false, description: 'Specific analyzer to reset, or omit to reset all', }, { name: 'projectPath', type: 'string', required: false, description: 'Optional project path to reset only project-specific config', }, ], }, { name: 'whitelist_get', description: 'Get current whitelist entries for dependency and class instantiation checks', parameters: [ { name: 'type', type: 'string', required: false, description: 'Filter by type: platform-api, framework-class, project-dep, shared-library, node-builtin', enum: ['platform-api', 'framework-class', 'project-dep', 'shared-library', 'node-builtin'], }, { name: 'status', type: 'string', required: false, description: 'Filter by status: active, pending, rejected, disabled', enum: ['active', 'pending', 'rejected', 'disabled'], }, ], }, { name: 'whitelist_add', description: 'Add a new entry to the whitelist', parameters: [ { name: 'name', type: 'string', required: true, description: 'Class name or import path to whitelist', }, { name: 'type', type: 'string', required: true, description: 'Type of whitelist entry', enum: ['platform-api', 'framework-class', 'project-dep', 'shared-library', 'node-builtin'], }, { name: 'description', type: 'string', required: false, description: 'Explanation of why this is whitelisted', }, { name: 'patterns', type: 'array', required: false, description: 'Additional patterns to match (e.g., ["fs/*", "node:fs"])', }, ], }, { name: 'whitelist_update_status', description: 'Update the status of a whitelist entry', parameters: [ { name: 'name', type: 'string', required: true, description: 'Name of the whitelist entry to update', }, { name: 'status', type: 'string', required: true, description: 'New status for the entry', enum: ['active', 'pending', 'rejected', 'disabled'], }, ], }, { name: 'whitelist_detect', description: 'Detect potential whitelist candidates from package.json and usage patterns', parameters: [ { name: 'path', type: 'string', required: false, description: 'Project path to analyze (defaults to current directory)', default: process.cwd(), }, { name: 'includePackageJson', type: 'boolean', required: false, description: 'Include dependencies from package.json', default: true, }, { name: 'autoPopulate', type: 'boolean', required: false, description: 'Automatically add high-confidence entries (>95% confidence)', default: false, }, ], }, ]; function getAllViolations(result) { const violations = []; for (const [analyzerName, analyzerResult] of Object.entries(result.analyzerResults)) { for (const violation of analyzerResult.violations) { violations.push({ ...violation, analyzer: analyzerName, }); } } return violations; } function calculateHealthScore(result) { const filesAnalyzed = result.metadata?.filesAnalyzed || 1; const critical = result.summary.criticalIssues || 0; const warnings = result.summary.warnings || 0; const suggestions = result.summary.suggestions || 0; // Weight factors for different severity levels const weights = { critical: 10, warning: 3, suggestion: 0.5 }; // Calculate weighted violations per file const weightedViolations = (critical * weights.critical) + (warnings * weights.warning) + (suggestions * weights.suggestion); const violationsPerFile = weightedViolations / filesAnalyzed; // Score calculation: 100 points minus deductions // Each weighted violation per file reduces score // Critical-heavy codebases drop faster than suggestion-heavy ones let score = 100 - (violationsPerFile * 2); return Math.max(0, Math.round(Math.min(100, score))); } async function startMcpServer() { console.error(chalk.blue('[INFO]'), 'Starting Code Auditor MCP Server (standalone)...'); console.error(chalk.blue('[DEBUG]'), `Process PID: ${process.pid}`); console.error(chalk.blue('[DEBUG]'), `Node version: ${process.version}`); console.error(chalk.blue('[DEBUG]'), `Working directory: ${process.cwd()}`); const server = new Server({ name: 'code-auditor', }, { capabilities: { tools: {}, }, }); console.error(chalk.blue('[INFO]'), 'Server instance created'); console.error(chalk.blue('[DEBUG]'), 'Server capabilities:', JSON.stringify({ tools: {} })); // Add error handler server.onerror = (error) => { console.error(chalk.red('[ERROR]'), 'Server error occurred:', error); console.error(chalk.red('[ERROR]'), 'Error stack:', error.stack); }; // Handle tool listing console.error(chalk.blue('[INFO]'), 'Setting up request handlers...'); server.setRequestHandler(ListToolsRequestSchema, async (request) => { console.error(chalk.blue('[DEBUG]'), 'Received ListTools request'); console.error(chalk.blue('[DEBUG]'), 'Request:', JSON.stringify(request, null, 2)); const response = { tools: tools.map(tool => ({ name: tool.name, description: tool.description, inputSchema: { type: 'object', properties: tool.parameters.reduce((acc, param) => { acc[param.name] = { type: param.type, description: param.description, ...(param.default !== undefined && { default: param.default }), ...(param.enum && { enum: param.enum }), }; return acc; }, {}), required: tool.parameters.filter(p => p.required).map(p => p.name), }, })), }; console.error(chalk.blue('[DEBUG]'), `Returning ${response.tools.length} tools`); console.error(chalk.blue('[DEBUG]'), 'ListTools response:', JSON.stringify(response, null, 2)); return response; }); console.error(chalk.blue('[INFO]'), `Registered ${tools.length} tools`); // Add handler for initialize request server.setRequestHandler(InitializeRequestSchema, async (request) => { console.error(chalk.blue('[DEBUG]'), 'Received initialize request'); console.error(chalk.blue('[DEBUG]'), 'Request:', JSON.stringify(request, null, 2)); const response = { protocolVersion: '2024-11-05', capabilities: { tools: {}, }, serverInfo: { name: 'code-auditor', version: '1.0.0', }, }; console.error(chalk.blue('[DEBUG]'), 'Initialize response:', JSON.stringify(response, null, 2)); return response; }); // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { console.error(chalk.blue('[DEBUG]'), 'Received CallTool request'); console.error(chalk.blue('[DEBUG]'), 'Request:', JSON.stringify(request, null, 2)); const { name, arguments: args } = request.params; console.error(chalk.blue('[DEBUG]'), `Tool name: ${name}`); console.error(chalk.blue('[DEBUG]'), `Tool arguments:`, JSON.stringify(args, null, 2)); try { console.error(chalk.blue('[DEBUG]'), `Starting tool execution for: ${name}`); let result; switch (name) { case 'audit': { const auditPath = path.resolve(args.path || process.cwd()); const indexFunctions = args.indexFunctions !== false; // Default true const analyzers = args.analyzers || ['solid', 'dry']; const minSeverity = args.minSeverity; const limit = Math.min(args.limit || 50, 100); // Max 100 const offset = args.offset || 0; const useCache = args.useCache !== false; // Default true const providedAuditId = args.auditId; let auditResult; let auditId = providedAuditId; // Check if we should use cached results if (providedAuditId) { const db = CodeIndexDB.getInstance(); const cachedResult = await db.getAuditResults(providedAuditId); if (cachedResult) { auditResult = cachedResult; console.error(chalk.blue('[INFO]'), `Using cached audit results: ${providedAuditId}`); } else { console.error(chalk.yellow('[WARN]'), `Cached audit not found: ${providedAuditId}, running new audit`); } } // Run new audit if no cached result if (!auditResult) { // Get stored analyzer configs from database const db = CodeIndexDB.getInstance(); await db.initialize(); const storedConfigs = await db.getAllAnalyzerConfigs(auditPath); // Merge stored configs with any provided configs const analyzerConfigs = { ...storedConfigs, ...(args.analyzerConfigs || {}) }; const options = { projectRoot: auditPath, enabledAnalyzers: analyzers, minSeverity, verbose: false, indexFunctions, ...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }), }; const runner = createAuditRunner(options); const runResult = await runner.run(); // Store in cache if requested if (useCache) { const db = CodeIndexDB.getInstance(); auditId = await db.storeAuditResults(runResult, auditPath); console.error(chalk.blue('[INFO]'), `Stored audit results with ID: ${auditId}`); } auditResult = runResult; } // Handle function indexing if enabled and functions were collected let indexingResult = null; if (indexFunctions && auditResult.metadata.fileToFunctionsMap) { try { const syncStats = { added: 0, updated: 0, removed: 0 }; // Sync each file's functions to handle additions, updates, and removals for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) { const fileStats = await syncFileIndex(filePath, functions); syncStats.added += fileStats.added; syncStats.updated += fileStats.updated; syncStats.removed += fileStats.removed; } indexingResult = { success: true, registered: syncStats.added + syncStats.updated, failed: 0, syncStats }; console.error(chalk.blue('[INFO]'), `Synced functions: ${syncStats.added} added, ${syncStats.updated} updated, ${syncStats.removed} removed`); // Auto-detect and populate whitelist entries after indexing try { const { WhitelistService } = await import('./services/whitelistService.js'); const whitelistService = WhitelistService.getInstance(); const whitelistResult = await whitelistService.whitelistAllDependencies(auditPath); if (whitelistResult.added > 0) { console.error(chalk.blue('[INFO]'), `Auto-added ${whitelistResult.added} whitelist entries`); } } catch (error) { // Don't fail audit if whitelist detection fails console.warn('Whitelist auto-detection failed:', error); } } catch (error) { console.error(chalk.yellow('[WARN]'), 'Failed to sync functions:', error); } } // Get all violations and apply pagination const allViolations = auditResult.violations || getAllViolations(auditResult); const paginatedViolations = allViolations.slice(offset, offset + limit); // Format for MCP result = { summary: { totalViolations: auditResult.summary.totalViolations, criticalIssues: auditResult.summary.criticalIssues, warnings: auditResult.summary.warnings, suggestions: auditResult.summary.suggestions, filesAnalyzed: auditResult.metadata?.filesAnalyzed || 0, executionTime: auditResult.metadata?.auditDuration || 0, healthScore: auditResult.summary.healthScore || calculateHealthScore(auditResult), }, violations: paginatedViolations, pagination: { total: allViolations.length, limit, offset, hasMore: offset + limit < allViolations.length, nextOffset: offset + limit < allViolations.length ? offset + limit : null, auditId: auditId, // Include audit ID for subsequent requests }, recommendations: auditResult.recommendations || [], ...(indexingResult && { functionIndexing: indexingResult }), }; break; } case 'audit_health': { const auditPath = path.resolve(args.path || process.cwd()); const indexFunctions = args.indexFunctions !== false; // Default true // Get stored analyzer configs from database const db = CodeIndexDB.getInstance(); await db.initialize(); const storedConfigs = await db.getAllAnalyzerConfigs(auditPath); // Merge stored configs with any provided configs const analyzerConfigs = { ...storedConfigs, ...(args.analyzerConfigs || {}) }; const runner = createAuditRunner({ projectRoot: auditPath, enabledAnalyzers: ['solid', 'dry'], minSeverity: 'warning', verbose: false, indexFunctions, // Pass the flag to the runner ...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }), }); const auditResult = await runner.run(); const healthScore = calculateHealthScore(auditResult); // Handle function indexing if enabled and functions were collected let indexingResult = null; if (indexFunctions && auditResult.metadata.fileToFunctionsMap) { try { const syncStats = { added: 0, updated: 0, removed: 0 }; // Sync each file's functions to handle additions, updates, and removals for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) { const fileStats = await syncFileIndex(filePath, functions); syncStats.added += fileStats.added; syncStats.updated += fileStats.updated; syncStats.removed += fileStats.removed; } indexingResult = { success: true, registered: syncStats.added + syncStats.updated, failed: 0, syncStats }; console.error(chalk.blue('[INFO]'), `Synced functions: ${syncStats.added} added, ${syncStats.updated} updated, ${syncStats.removed} removed`); } catch (error) { console.error(chalk.yellow('[WARN]'), 'Failed to index functions:', error); } } const threshold = args.threshold || 70; result = { healthScore, threshold, passed: healthScore >= threshold, status: healthScore >= threshold ? 'healthy' : 'needs-attention', metrics: { filesAnalyzed: auditResult.metadata.filesAnalyzed, totalViolations: auditResult.summary.totalViolations, criticalViolations: auditResult.summary.criticalIssues, warningViolations: auditResult.summary.warnings, }, recommendation: healthScore >= 90 ? 'Excellent code health!' : healthScore >= 70 ? 'Good code health with room for improvement' : 'Code health needs attention - run detailed audit', ...(indexingResult && { functionIndexing: indexingResult }), }; break; } case 'search_code': { const query = args.query; const filters = args.filters; const limit = args.limit || 50; const offset = args.offset || 0; // Extract searchMode from filters if present let searchMode; if (filters && filters.searchMode) { searchMode = filters.searchMode; // Remove searchMode from filters as it's a top-level option const { searchMode: _, ...cleanFilters } = filters; result = await searchFunctions({ query, filters: cleanFilters, limit, offset, searchMode }); } else { result = await searchFunctions({ query, filters, limit, offset }); } break; } case 'find_definition': { const name = args.name; const filePath = args.filePath; const definition = await findDefinition(name, filePath); result = definition || { error: 'Function not found' }; break; } case 'sync_index': { const mode = args.mode || 'sync'; const targetPath = args.path; const db = CodeIndexDB.getInstance(); await db.initialize(); switch (mode) { case 'cleanup': { const cleanupResult = await db.bulkCleanup(); result = { success: true, mode: 'cleanup', filesProcessed: cleanupResult.scannedCount, functionsRemoved: cleanupResult.removedCount, message: `Removed ${cleanupResult.removedCount} functions from ${cleanupResult.removedFiles.length} deleted files` }; break; } case 'sync': { let syncResult; if (targetPath) { const functions = await scanFunctionsInDirectory(targetPath); syncResult = await syncFileIndex(targetPath, functions); result = { success: true, mode: 'sync', path: targetPath, ...syncResult }; } else { syncResult = await db.deepSync(); result = { success: true, mode: 'sync', filesProcessed: syncResult.syncedFiles, functionsAdded: syncResult.addedFunctions, functionsUpdated: syncResult.updatedFunctions, functionsRemoved: syncResult.removedFunctions }; } // Auto-detect and populate whitelist entries try { const { WhitelistService } = await import('./services/whitelistService.js'); const whitelistService = WhitelistService.getInstance(); const whitelistResult = await whitelistService.whitelistAllDependencies(targetPath || process.cwd()); if (whitelistResult.added > 0) { result.whitelistAdded = whitelistResult.added; result.whitelistMessage = `Auto-added ${whitelistResult.added} whitelist entries`; } } catch (error) { // Don't fail sync if whitelist detection fails console.warn('Whitelist auto-detection failed:', error); } break; } case 'reset': { await clearIndex(); result = { success: true, mode: 'reset', message: 'Index cleared successfully' }; break; } default: throw new Error(`Unknown sync mode: ${mode}`); } break; } case 'generate_ai_config': { const tools = args.tools; const outputDir = args.outputDir || '.'; if (!Array.isArray(tools) || tools.length === 0) { throw new Error('tools parameter is required and must be a non-empty array'); } const factory = new ConfigGeneratorFactory(); const generatedFiles = []; const errors = []; for (const toolName of tools) { try { const generator = factory.createGenerator(toolName); if (!generator) { errors.push(`Unknown tool: ${toolName}`); continue; } const config = generator.generateConfig(); const outputPath = path.join(outputDir, config.filename); // Check if file exists const fs = await import('fs/promises'); try { await fs.access(outputPath); errors.push(`File already exists: ${config.filename} (use overwrite: true to replace)`); continue; } catch { // File doesn't exist, proceed } await fs.writeFile(outputPath, config.content, 'utf8'); generatedFiles.push(config.filename); } catch (error) { errors.push(`Failed to generate config for ${toolName}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } result = { success: errors.length === 0, generatedFiles, ...(errors.length > 0 && { errors }), totalRequested: tools.length, totalGenerated: generatedFiles.length }; break; } case 'get_workflow_guide': { const scenario = args.scenario; const { getWorkflowGuide, getWorkflowTips } = await import('./mcp-tools/workflowGuide.js'); try { const workflows = getWorkflowGuide(scenario); const tips = getWorkflowTips(); result = { success: true, ...(scenario ? { workflow: workflows } : { workflows }), tips }; } catch (error) { result = { success: false, error: error instanceof Error ? error.message : 'Unknown error', availableScenarios: ['initial-setup', 'react-development', 'code-review', 'find-patterns', 'maintenance', 'analyzer-configuration'] }; } break; } case 'set_analyzer_config': { const analyzerName = args.analyzerName; const config = args.config; const projectPath = args.projectPath; const db = CodeIndexDB.getInstance(); await db.initialize(); try { await db.storeAnalyzerConfig(analyzerName, config, { projectPath, isGlobal: !projectPath }); result = { success: true, message: `Configuration for ${analyzerName} analyzer has been saved${projectPath ? ` for project ${projectPath}` : ' globally'}`, analyzer: analyzerName, scope: projectPath ? 'project' : 'global', config }; } catch (error) { result = { success: false, error: error instanceof Error ? error.message : 'Failed to save configuration' }; } break; } case 'get_analyzer_config': { const analyzerName = args.analyzerName; const projectPath = args.projectPath; const db = CodeIndexDB.getInstance(); await db.initialize(); try { if (analyzerName) { const config = await db.getAnalyzerConfig(analyzerName, projectPath); result = { success: true, analyzer: analyzerName, config: config || null, scope: projectPath ? 'project' : 'global', message: config ? 'Configuration found' : 'No custom configuration found, using defaults' }; } else { const configs = await db.getAllAnalyzerConfigs(projectPath); result = { success: true, configs, scope: projectPath ? 'project' : 'global', message: `Found ${Object.keys(configs).length} analyzer configurations` }; } } catch (error) { result = { success: false, error: error instanceof Error ? error.message : 'Failed to get configuration' }; } break; } case 'reset_analyzer_config': { const analyzerName = args.analyzerName; const projectPath = args.projectPath; const db = CodeIndexDB.getInstance(); await db.initialize(); try { if (analyzerName) { const deleted = await db.deleteAnalyzerConfig(analyzerName, { projectPath, isGlobal: !projectPath }); result = { success: deleted, message: deleted ? `Configuration for ${analyzerName} analyzer has been reset${projectPath ? ` for project ${projectPath}` : ' globally'}` : `No configuration found for ${analyzerName} analyzer`, analyzer: analyzerName, scope: projectPath ? 'project' : 'global' }; } else { await db.resetAnalyzerConfigs(projectPath); result = { success: true, message: projectPath ? `All project-specific configurations for ${projectPath} have been reset` : 'All analyzer configurations have been reset to defaults', scope: projectPath ? 'project' : 'global' }; } } catch (error) { result = { success: false, error: error instanceof Error ? error.message : 'Failed to reset configuration' }; } break; } case 'whitelist_get': { const { handleWhitelistGet } = await import('./mcp-tools/whitelistTools.js'); result = await handleWhitelistGet(args); break; } case 'whitelist_add': { const { handleWhitelistAdd } = await import('./mcp-tools/whitelistTools.js'); result = await handleWhitelistAdd(args); break; } case 'whitelist_update_status': { const { handleWhitelistUpdateStatus } = await import('./mcp-tools/whitelistTools.js'); result = await handleWhitelistUpdateStatus(args); break; } case 'whitelist_detect': { const { handleWhitelistDetect } = await import('./mcp-tools/whitelistTools.js'); result = await handleWhitelistDetect(args); break; } default: throw new Error(`Unknown tool: ${name}`); } const response = { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; console.error(chalk.blue('[DEBUG]'), `Tool ${name} executed successfully`); console.error(chalk.blue('[DEBUG]'), 'CallTool response:', JSON.stringify(response, null, 2).substring(0, 500) + '...'); return response; } catch (error) { console.error(chalk.red('[ERROR]'), `Tool ${name} execution failed:`, error); console.error(chalk.red('[ERROR]'), 'Error stack:', error instanceof Error ? error.stack : 'No stack trace'); return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error', tool: name, }), }, ], isError: true, }; } }); console.error(chalk.blue('[INFO]'), 'Request handlers configured'); const transport = new StdioServerTransport(); console.error(chalk.blue('[INFO]'), 'Creating stdio transport...'); // Add transport event handlers if available if (transport.onclose) { transport.onclose = () => { console.error(chalk.yellow('[WARN]'), 'Transport closed'); }; } if (transport.onerror) { transport.onerror = (error) => { console.error(chalk.red('[ERROR]'), 'Transport error:', error); }; } console.error(chalk.blue('[DEBUG]'), 'Connecting server to transport...'); await server.connect(transport); console.error(chalk.blue('[DEBUG]'), 'Server connected to transport'); console.error(chalk.green('✓ Code Auditor MCP Server started (standalone mode)')); console.error(chalk.gray('Listening on stdio...')); console.error(chalk.blue('[DEBUG]'), 'Server state after initialization:', { name: 'code-auditor', transport: 'stdio', handlers: ['ListTools', 'CallTool', 'initialize'], toolCount: tools.length, }); } // Error handlers process.on('uncaughtException', (error) => { console.error(chalk.red('[ERROR]'), 'Uncaught exception:', error); console.error(chalk.red('[ERROR]'), 'Stack:', error.stack); console.error(chalk.red('[ERROR]'), 'Error details:', JSON.stringify(error, null, 2)); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { console.error(chalk.red('[ERROR]'), 'Unhandled rejection at:', promise); console.error(chalk.red('[ERROR]'), 'Reason:', reason); console.error(chalk.red('[ERROR]'), 'Rejection details:', JSON.stringify(reason, null, 2)); process.exit(1); }); // Add SIGTERM and SIGINT handlers process.on('SIGTERM', () => { console.error(chalk.yellow('[WARN]'), 'Received SIGTERM, shutting down gracefully...'); process.exit(0); }); process.on('SIGINT', () => { console.error(chalk.yellow('[WARN]'), 'Received SIGINT, shutting down gracefully...'); process.exit(0); }); // Log when stdin/stdout events occur process.stdin.on('error', (error) => { console.error(chalk.red('[ERROR]'), 'stdin error:', error); }); process.stdout.on('error', (error) => { console.error(chalk.red('[ERROR]'), 'stdout error:', error); }); // Start server console.error(chalk.blue('[INFO]'), 'Initializing server...'); startMcpServer().catch(error => { console.error(chalk.red('[ERROR]'), 'Failed to start MCP server:', error); console.error(chalk.red('[ERROR]'), 'Stack:', error.stack); process.exit(1); }); //# sourceMappingURL=mcp-standalone.js.map