UNPKG

@moikas/code-audit-mcp

Version:

AI-powered code auditing via MCP using local Ollama models for security, performance, and quality analysis

363 lines 13.1 kB
/** * MCP (Model Context Protocol) configuration management for Claude Desktop and Claude Code * Handles detection, reading, and writing of MCP server configurations across different environments */ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; import { join, dirname, resolve } from 'path'; import { homedir, platform } from 'os'; import { exec } from 'child_process'; import { promisify } from 'util'; import chalk from 'chalk'; import { fileURLToPath } from 'url'; const execAsync = promisify(exec); /** * Claude environment types */ export var ClaudeEnvironment; (function (ClaudeEnvironment) { ClaudeEnvironment["DESKTOP"] = "desktop"; ClaudeEnvironment["CODE_GLOBAL"] = "code-global"; ClaudeEnvironment["CODE_PROJECT"] = "code-project"; })(ClaudeEnvironment || (ClaudeEnvironment = {})); /** * MCP configuration manager class */ export class MCPConfigManager { configPaths; executablePath = null; constructor() { this.configPaths = this.detectConfigPaths(); } /** * Detect configuration file paths based on platform and environment */ detectConfigPaths() { const paths = {}; const home = homedir(); const cwd = process.cwd(); // Claude Desktop paths switch (platform()) { case 'darwin': // macOS paths.desktop = join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'); break; case 'win32': // Windows paths.desktop = join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json'); break; case 'linux': paths.desktop = join(home, '.config', 'Claude', 'claude_desktop_config.json'); break; } // Claude Code global config paths.codeGlobal = join(home, '.config', 'claude', 'mcp-settings.json'); // Claude Code project configs (check multiple possible locations) const projectConfigPaths = [ join(cwd, '.claude', 'mcp-settings.json'), join(cwd, '.mcp.json'), ]; for (const path of projectConfigPaths) { if (existsSync(path)) { paths.codeProject = path; break; } } // If no project config exists, use the preferred location if (!paths.codeProject) { paths.codeProject = join(cwd, '.claude', 'mcp-settings.json'); } return paths; } /** * Get available Claude environments */ getAvailableEnvironments() { const environments = []; if (this.configPaths.desktop) { const exists = existsSync(this.configPaths.desktop); const configured = exists && this.isServerConfigured(ClaudeEnvironment.DESKTOP); environments.push({ environment: ClaudeEnvironment.DESKTOP, path: this.configPaths.desktop, exists, configured, }); } if (this.configPaths.codeGlobal) { const exists = existsSync(this.configPaths.codeGlobal); const configured = exists && this.isServerConfigured(ClaudeEnvironment.CODE_GLOBAL); environments.push({ environment: ClaudeEnvironment.CODE_GLOBAL, path: this.configPaths.codeGlobal, exists, configured, }); } if (this.configPaths.codeProject) { const exists = existsSync(this.configPaths.codeProject); const configured = exists && this.isServerConfigured(ClaudeEnvironment.CODE_PROJECT); environments.push({ environment: ClaudeEnvironment.CODE_PROJECT, path: this.configPaths.codeProject, exists, configured, }); } return environments; } /** * Read configuration from a specific environment */ readConfig(environment) { const path = this.getConfigPath(environment); if (!path || !existsSync(path)) { return null; } try { const content = readFileSync(path, 'utf8'); return JSON.parse(content); } catch (error) { console.error(chalk.yellow(`Warning: Failed to parse ${environment} config:`, error)); return null; } } /** * Write configuration to a specific environment */ writeConfig(environment, config) { const path = this.getConfigPath(environment); if (!path) { throw new Error(`No configuration path available for ${environment}`); } // Ensure directory exists const dir = dirname(path); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true, mode: 0o755 }); } // Backup existing config if it exists if (existsSync(path)) { const backupPath = `${path}.backup-${Date.now()}`; writeFileSync(backupPath, readFileSync(path, 'utf8')); } // Write new config with proper formatting writeFileSync(path, JSON.stringify(config, null, 2) + '\n', { encoding: 'utf8', mode: 0o600, // Secure file permissions }); } /** * Get configuration path for an environment */ getConfigPath(environment) { switch (environment) { case ClaudeEnvironment.DESKTOP: return this.configPaths.desktop || null; case ClaudeEnvironment.CODE_GLOBAL: return this.configPaths.codeGlobal || null; case ClaudeEnvironment.CODE_PROJECT: return this.configPaths.codeProject || null; default: return null; } } /** * Resolve the executable path for code-audit */ async resolveExecutablePath() { if (this.executablePath) { return this.executablePath; } // Get __dirname equivalent for ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Check if running from development (local) const localPath = resolve(join(__dirname, '..', '..', '..', 'bin', 'code-audit.js')); if (existsSync(localPath)) { this.executablePath = localPath; return localPath; } // Check if globally installed try { const { stdout } = await execAsync('which code-audit'); const globalPath = stdout.trim(); if (globalPath && existsSync(globalPath)) { this.executablePath = globalPath; return globalPath; } } catch { // which command failed, try other methods } // Check common global install locations const commonPaths = [ join(homedir(), '.npm', 'bin', 'code-audit'), join(homedir(), '.yarn', 'bin', 'code-audit'), join(homedir(), '.pnpm', 'bin', 'code-audit'), join(homedir(), '.bun', 'bin', 'code-audit'), '/usr/local/bin/code-audit', '/usr/bin/code-audit', ]; for (const path of commonPaths) { if (existsSync(path)) { this.executablePath = path; return path; } } throw new Error('Could not find code-audit executable. Please ensure it is installed globally or run from the project directory.'); } /** * Generate MCP server configuration for code-audit */ async generateServerConfig() { const command = await this.resolveExecutablePath(); return { command, args: ['start', '--stdio'], env: {}, }; } /** * Check if code-audit server is already configured */ isServerConfigured(environment) { const config = this.readConfig(environment); return !!config?.mcpServers?.['code-audit']; } /** * Configure code-audit as an MCP server */ async configureServer(environment, options = {}) { const serverName = options.serverName || 'code-audit'; try { // Read existing config or create new one let config = this.readConfig(environment) || {}; // Initialize mcpServers if not present if (!config.mcpServers) { config.mcpServers = {}; } // Check if already configured if (config.mcpServers[serverName] && !options.force) { console.log(chalk.yellow(`${serverName} is already configured in ${environment}. Use --force to override.`)); return false; } // Generate and add server configuration const serverConfig = await this.generateServerConfig(); config.mcpServers[serverName] = serverConfig; // Write updated config this.writeConfig(environment, config); console.log(chalk.green(`✓ Configured ${serverName} in ${environment} environment`)); return true; } catch (error) { console.error(chalk.red(`Failed to configure ${environment}:`), error instanceof Error ? error.message : error); return false; } } /** * Remove code-audit from MCP servers */ removeServer(environment, serverName = 'code-audit') { try { const config = this.readConfig(environment); if (!config?.mcpServers?.[serverName]) { console.log(chalk.yellow(`${serverName} is not configured in ${environment}`)); return false; } delete config.mcpServers[serverName]; this.writeConfig(environment, config); console.log(chalk.green(`✓ Removed ${serverName} from ${environment} environment`)); return true; } catch (error) { console.error(chalk.red(`Failed to remove from ${environment}:`), error instanceof Error ? error.message : error); return false; } } /** * Get current MCP configuration status */ getStatus() { return { executable: this.executablePath, environments: this.getAvailableEnvironments(), }; } /** * Backup MCP configurations */ async backupConfigurations() { const backup = { timestamp: new Date().toISOString(), }; const environments = [ { key: 'desktop', env: ClaudeEnvironment.DESKTOP }, { key: 'codeGlobal', env: ClaudeEnvironment.CODE_GLOBAL }, { key: 'codeProject', env: ClaudeEnvironment.CODE_PROJECT }, ]; for (const { key, env } of environments) { const config = this.readConfig(env); if (config) { backup[key] = config; } } return backup; } /** * Restore MCP configurations from backup */ restoreConfigurations(backup, environments) { const envMap = [ { key: 'desktop', env: ClaudeEnvironment.DESKTOP }, { key: 'codeGlobal', env: ClaudeEnvironment.CODE_GLOBAL }, { key: 'codeProject', env: ClaudeEnvironment.CODE_PROJECT }, ]; for (const { key, env } of envMap) { if (backup[key] && (!environments || environments.includes(env))) { try { this.writeConfig(env, backup[key]); console.log(chalk.green(`✓ Restored ${env} configuration`)); } catch (error) { console.error(chalk.red(`Failed to restore ${env}:`), error instanceof Error ? error.message : error); } } } } /** * Auto-detect and configure all available environments */ async autoConfigureAll(options = {}) { const results = { configured: [], failed: [], skipped: [], }; const environments = this.getAvailableEnvironments(); for (const env of environments) { if (env.configured && !options.force) { results.skipped.push(env.environment); continue; } const success = await this.configureServer(env.environment, options); if (success) { results.configured.push(env.environment); } else { results.failed.push(env.environment); } } return results; } } // Singleton instance let mcpConfigManager = null; /** * Get MCP configuration manager instance */ export function getMCPConfigManager() { if (!mcpConfigManager) { mcpConfigManager = new MCPConfigManager(); } return mcpConfigManager; } //# sourceMappingURL=mcp-config.js.map