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