UNPKG

claude-gemini-multimodal-bridge

Version:

Enterprise-grade AI integration bridge connecting Claude Code, Gemini CLI, and Google AI Studio with intelligent routing and advanced multimodal processing capabilities

417 lines (404 loc) 15.5 kB
import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'fs'; import { mkdir } from 'fs/promises'; import { dirname, join } from 'path'; import { homedir } from 'os'; import { logger } from './logger.js'; export class MCPConfigManager { CONFIG_PATHS = [ join(homedir(), '.claude-code', 'mcp_servers.json'), join(homedir(), '.config', 'claude-code', 'mcp_servers.json'), join(homedir(), '.claude', 'mcp_servers.json'), join(homedir(), 'Library', 'Application Support', 'Claude Code', 'mcp_servers.json'), join(homedir(), 'AppData', 'Roaming', 'Claude Code', 'mcp_servers.json'), join(homedir(), 'AppData', 'Local', 'Claude Code', 'mcp_servers.json'), ]; CGMB_SERVER_NAME = 'claude-gemini-multimodal-bridge'; findConfigPath() { for (const path of this.CONFIG_PATHS) { if (existsSync(path)) { logger.debug('Found MCP config file', { path }); return path; } } const defaultPath = this.CONFIG_PATHS[0]; if (!defaultPath) { return null; } logger.debug('No existing MCP config found, will use default', { path: defaultPath }); return defaultPath; } readExistingConfig(configPath) { if (!existsSync(configPath)) { return { mcpServers: {} }; } try { const content = readFileSync(configPath, 'utf8'); const parsed = JSON.parse(content); if (!parsed.mcpServers) { parsed.mcpServers = {}; } return parsed; } catch (error) { logger.warn('Failed to parse existing MCP config, starting with empty config', { error: error.message, configPath }); return { mcpServers: {} }; } } createBackup(configPath) { if (!existsSync(configPath)) { return null; } try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = `${configPath}.backup.${timestamp}`; copyFileSync(configPath, backupPath); logger.info('Created configuration backup', { original: configPath, backup: backupPath }); return backupPath; } catch (error) { logger.error('Failed to create backup', { error: error.message, configPath }); return null; } } generateCGMBConfig() { const detectedCgmbPath = process.env.CGMB_DETECTED_PATH; const detectedNodePath = process.env.CGMB_DETECTED_NODE_PATH; if (detectedCgmbPath && detectedNodePath) { logger.info('Using environment-specific MCP configuration', { nodePath: detectedNodePath, cgmbPath: detectedCgmbPath }); return { command: detectedNodePath, args: [detectedCgmbPath, 'serve'], env: { NODE_ENV: 'production', PATH: process.env.PATH || '' } }; } return { command: 'cgmb', args: ['serve'], env: { NODE_ENV: 'production' } }; } async checkCGMBConfiguration() { const configPath = this.findConfigPath(); if (!configPath || !existsSync(configPath)) { return { isConfigured: false, configPath }; } const existingConfig = this.readExistingConfig(configPath); const cgmbConfig = existingConfig.mcpServers[this.CGMB_SERVER_NAME]; const result = { isConfigured: !!cgmbConfig, configPath, }; if (cgmbConfig) { result.currentConfig = cgmbConfig; } return result; } async addCGMBConfiguration(options = {}) { const { force = false, skipBackup = false, dryRun = false, interactive = false } = options; try { const configPath = this.findConfigPath(); if (!configPath) { return { success: false, message: 'Could not determine Claude Code configuration path', action: 'error' }; } const existingConfig = this.readExistingConfig(configPath); if (existingConfig.mcpServers[this.CGMB_SERVER_NAME] && !force) { const currentConfig = existingConfig.mcpServers[this.CGMB_SERVER_NAME]; logger.info('CGMB MCP configuration already exists', { configPath, currentCommand: currentConfig?.command, currentArgs: currentConfig?.args }); return { success: true, message: 'CGMB is already configured in Claude Code MCP settings', configPath, action: 'skipped' }; } if (dryRun) { const cgmbConfig = this.generateCGMBConfig(); return { success: true, message: `Would add CGMB configuration to ${configPath}`, configPath, action: force ? 'updated' : 'added' }; } let backupPath = null; if (existsSync(configPath) && !skipBackup) { backupPath = this.createBackup(configPath); if (!backupPath) { return { success: false, message: 'Failed to create backup of existing configuration', action: 'error' }; } } const configDir = dirname(configPath); if (!existsSync(configDir)) { await mkdir(configDir, { recursive: true }); logger.info('Created configuration directory', { path: configDir }); } const cgmbConfig = this.generateCGMBConfig(); if (!cgmbConfig.command || !cgmbConfig.args || cgmbConfig.args.length === 0) { return { success: false, message: 'Failed to generate valid CGMB configuration. Please check your installation.', action: 'error' }; } logger.info('Generated CGMB configuration', { command: cgmbConfig.command, args: cgmbConfig.args, env: Object.keys(cgmbConfig.env || {}) }); existingConfig.mcpServers[this.CGMB_SERVER_NAME] = cgmbConfig; const configContent = JSON.stringify(existingConfig, null, 2); try { writeFileSync(configPath, configContent, 'utf8'); } catch (writeError) { logger.error('Failed to write MCP configuration file', { configPath, error: writeError.message }); return { success: false, message: `Failed to write configuration file: ${writeError.message}. Check file permissions.`, configPath, action: 'error' }; } const action = force && existingConfig.mcpServers[this.CGMB_SERVER_NAME] ? 'updated' : 'added'; logger.info('Successfully updated Claude Code MCP configuration', { configPath, backupPath, action, cgmbConfig }); return { success: true, message: `Successfully ${action} CGMB configuration in Claude Code`, configPath, backupPath: backupPath !== null ? backupPath : undefined, action }; } catch (error) { logger.error('Failed to add CGMB configuration', { error: error.message }); return { success: false, message: `Failed to update MCP configuration: ${error.message}`, action: 'error' }; } } async removeCGMBConfiguration(options = {}) { const { skipBackup = false, dryRun = false } = options; try { const configPath = this.findConfigPath(); if (!configPath || !existsSync(configPath)) { return { success: true, message: 'No MCP configuration file found', action: 'skipped' }; } const existingConfig = this.readExistingConfig(configPath); if (!existingConfig.mcpServers[this.CGMB_SERVER_NAME]) { return { success: true, message: 'CGMB is not configured in Claude Code MCP settings', configPath, action: 'skipped' }; } if (dryRun) { return { success: true, message: `Would remove CGMB configuration from ${configPath}`, configPath, action: 'updated' }; } let backupPath = null; if (!skipBackup) { backupPath = this.createBackup(configPath); } delete existingConfig.mcpServers[this.CGMB_SERVER_NAME]; const configContent = JSON.stringify(existingConfig, null, 2); writeFileSync(configPath, configContent, 'utf8'); logger.info('Successfully removed CGMB from Claude Code MCP configuration', { configPath, backupPath }); return { success: true, message: 'Successfully removed CGMB configuration from Claude Code', configPath, backupPath: backupPath !== null ? backupPath : undefined, action: 'updated' }; } catch (error) { logger.error('Failed to remove CGMB configuration', { error: error.message }); return { success: false, message: `Failed to remove MCP configuration: ${error.message}`, action: 'error' }; } } async getConfigurationStatus() { const status = await this.checkCGMBConfiguration(); const recommendations = []; const issues = []; if (!status.isConfigured) { recommendations.push('Run "cgmb setup-mcp" to configure Claude Code MCP integration'); issues.push('CGMB is not configured as an MCP server in Claude Code'); } else { recommendations.push('CGMB MCP integration is properly configured'); if (status.currentConfig) { const config = status.currentConfig; if (config.command === 'npx') { recommendations.push('Consider installing CGMB globally for better performance'); } if (!config.env?.NODE_ENV) { issues.push('Missing environment variables in MCP configuration'); } } } return { ...status, recommendations, issues }; } generateManualSetupInstructions() { const cgmbConfig = this.generateCGMBConfig(); return ` # Manual Claude Code MCP Setup Instructions ## Method A: Using Claude CLI (Recommended for v1.0.35+) If your Claude Code CLI supports the new MCP command: \`\`\`bash # Add CGMB to Claude Code claude mcp add claude-gemini-multimodal-bridge cgmb serve -e NODE_ENV=production # Verify the configuration claude mcp list claude mcp get claude-gemini-multimodal-bridge \`\`\` ## Method B: Manual Configuration File (Legacy Method) ### 1. Find your Claude Code configuration directory Common locations: - ~/.claude-code/ - ~/.config/claude-code/ - ~/Library/Application Support/Claude Code/ (macOS) - ~/AppData/Roaming/Claude Code/ (Windows) ### 2. Create or edit mcp_servers.json Add the following configuration to your mcp_servers.json file: \`\`\`json { "mcpServers": { "${this.CGMB_SERVER_NAME}": { "command": "${cgmbConfig.command}", "args": ${JSON.stringify(cgmbConfig.args, null, 2)}, "env": ${JSON.stringify(cgmbConfig.env, null, 2)} } } } \`\`\` ### 3. Restart Claude Code After saving the configuration, restart Claude Code to load the new MCP server. ### 4. Verify connection Run: cgmb verify Then check if CGMB tools are available in Claude Code. ⚠️ **Important Notes:** - If the file already contains other MCP servers, add the CGMB configuration to the existing "mcpServers" object - Make sure to keep valid JSON syntax (proper commas, brackets, etc.) - The new CLI method (Method A) is preferred as it handles configuration automatically ## 5. Troubleshooting If the MCP server doesn't load: - Check Claude Code logs for error messages - Verify the cgmb command is in your PATH: which cgmb - Ensure your API keys are properly set in environment variables - Try running the command manually: ${cgmbConfig.command} ${cgmbConfig.args.join(' ')} - For new CLI method issues, check: claude mcp list `; } async getDiagnosticInfo() { const configPath = this.findConfigPath(); const recommendations = []; let cgmbAvailable = false; try { const { execSync } = require('child_process'); execSync('cgmb --version', { stdio: 'ignore', timeout: 5000 }); cgmbAvailable = true; } catch { cgmbAvailable = false; recommendations.push('CGMB command not found. Run "npm link" for development or "npm install -g claude-gemini-multimodal-bridge" for production.'); } if (!configPath) { recommendations.push('Claude Code configuration directory not found. Ensure Claude Code is installed.'); } if (configPath && !existsSync(configPath)) { recommendations.push('Claude Code MCP configuration file does not exist yet. This is normal for first-time setup.'); } return { configPath, configExists: configPath ? existsSync(configPath) : false, cgmbInstallation: { command: 'cgmb', available: cgmbAvailable }, recommendations }; } } export async function setupCGMBMCP(options = {}) { const manager = new MCPConfigManager(); if (options.interactive) { } return manager.addCGMBConfiguration({ force: options.force || false, dryRun: options.dryRun || false }); } export async function getMCPStatus() { const manager = new MCPConfigManager(); return manager.getConfigurationStatus(); } export function getManualSetupInstructions() { const manager = new MCPConfigManager(); return manager.generateManualSetupInstructions(); }