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
JavaScript
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();
}