UNPKG

mcp-orchestrator

Version:

MCP Orchestrator - Discover and install MCPs with automatic OAuth support. Uses Claude CLI for OAuth MCPs (Canva, Asana, etc). 34 trusted MCPs from Claude Partners.

280 lines (279 loc) 9.39 kB
/** * Config Manager * Manages Claude Code and Claude Desktop configuration files */ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { execSync } from 'child_process'; export class ConfigManager { /** * Detect which Claude product is being used and return config file path */ detectConfigPath() { const homeDir = os.homedir(); // Check for Claude Code config (preferred) const codeConfigPath = path.join(homeDir, '.claude.json'); if (fs.existsSync(codeConfigPath)) { return { path: codeConfigPath, product: 'code' }; } // Check for Claude Desktop config const platform = os.platform(); if (platform === 'darwin') { const desktopPath = path.join(homeDir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'); if (fs.existsSync(desktopPath)) { return { path: desktopPath, product: 'desktop' }; } } else if (platform === 'win32') { const appData = process.env.APPDATA; if (appData) { const desktopPath = path.join(appData, 'Claude', 'claude_desktop_config.json'); if (fs.existsSync(desktopPath)) { return { path: desktopPath, product: 'desktop' }; } } } // Default to Claude Code config (will be created if doesn't exist) return { path: codeConfigPath, product: 'code' }; } /** * Read Claude config file */ readConfig(configPath) { try { if (!fs.existsSync(configPath)) { return { mcpServers: {} }; } const content = fs.readFileSync(configPath, 'utf-8'); const config = JSON.parse(content); // Ensure mcpServers exists if (!config.mcpServers) { config.mcpServers = {}; } return config; } catch (error) { console.error(`⚠️ Error reading config file:`, error); return { mcpServers: {} }; } } /** * Write Claude config file */ writeConfig(configPath, config) { const dir = path.dirname(configPath); // Create directory if it doesn't exist if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // Write with pretty formatting fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8'); } /** * Convert MCP config to Claude config entry format */ toConfigEntry(mcp) { // Check if it's a remote MCP if (mcp.command?.startsWith('remote:')) { const parts = mcp.command.split(':'); const type = parts[1]; // 'sse' or 'streamable-http' const url = parts.slice(2).join(':'); return { type: type === 'sse' ? 'sse' : 'http', url, env: mcp.env }; } // Local stdio MCP return { command: mcp.command, args: mcp.args || [], env: mcp.env }; } /** * Add MCP to Claude config */ addMCPToConfig(mcp) { try { const { path: configPath, product } = this.detectConfigPath(); const config = this.readConfig(configPath); // Check if MCP already exists if (config.mcpServers[mcp.id]) { return { success: false, configPath, product, message: `MCP "${mcp.id}" is already in your config` }; } // Add MCP config.mcpServers[mcp.id] = this.toConfigEntry(mcp); // Write back this.writeConfig(configPath, config); return { success: true, configPath, product, message: `Added "${mcp.name}" to ${product === 'code' ? 'Claude Code' : 'Claude Desktop'} config` }; } catch (error) { return { success: false, configPath: '', product: 'code', message: error instanceof Error ? error.message : String(error) }; } } /** * Remove MCP from Claude config */ removeMCPFromConfig(mcpId) { try { const { path: configPath, product } = this.detectConfigPath(); const config = this.readConfig(configPath); // Check if MCP exists if (!config.mcpServers[mcpId]) { return { success: false, configPath, product, message: `MCP "${mcpId}" not found in config` }; } // Remove MCP delete config.mcpServers[mcpId]; // Write back this.writeConfig(configPath, config); return { success: true, configPath, product, message: `Removed "${mcpId}" from ${product === 'code' ? 'Claude Code' : 'Claude Desktop'} config` }; } catch (error) { return { success: false, configPath: '', product: 'code', message: error instanceof Error ? error.message : String(error) }; } } /** * List MCPs in Claude config */ listMCPsInConfig() { try { const { path: configPath, product } = this.detectConfigPath(); const config = this.readConfig(configPath); return { success: true, mcps: Object.keys(config.mcpServers), configPath, product }; } catch (error) { return { success: false, mcps: [], configPath: '', product: 'code' }; } } /** * Generate `claude mcp add` command */ generateAddCommand(mcp) { // Build command parts const parts = ['claude mcp add', mcp.id]; // Check if remote and determine transport type if (mcp.command?.startsWith('remote:')) { const commandParts = mcp.command.split(':'); const transportType = commandParts[1]; // 'sse' or 'http' const url = commandParts.slice(2).join(':'); // Add transport type flag parts.push('-t', transportType); // Add environment variables if present if (mcp.env) { for (const [key, value] of Object.entries(mcp.env)) { parts.push(`-e ${key}="${value}"`); } } // Add URL parts.push('--', url); } else { // Local stdio MCP // Add environment variables if present if (mcp.env) { for (const [key, value] of Object.entries(mcp.env)) { parts.push(`-e ${key}="${value}"`); } } parts.push('--'); // For local MCPs, use npx if it's a package if (mcp.packageName && !mcp.command?.includes('/')) { parts.push('npx', '-y', mcp.packageName); } else { parts.push(mcp.command || ''); } if (mcp.args && mcp.args.length > 0) { parts.push(...mcp.args); } } return parts.join(' '); } /** * Add MCP via Claude CLI (for OAuth MCPs) * This runs `claude mcp add` which handles OAuth flow automatically */ addMCPViaCLI(mcp) { try { const command = this.generateAddCommand(mcp); console.error(`🔐 Executing: ${command}`); console.error(`⏳ This will open a browser for OAuth authorization...`); // Run the command (this will be interactive if OAuth is needed) const output = execSync(command, { stdio: 'inherit', // Show output to user encoding: 'utf-8' }); return { success: true, message: `Added "${mcp.name}" via Claude CLI. OAuth flow completed.`, requiresRestart: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, message: `Failed to run claude mcp add: ${errorMessage}`, requiresRestart: false }; } } /** * Check if MCP requires OAuth */ requiresOAuth(mcp) { // Check if it's a remote MCP (often needs OAuth) if (mcp.command?.startsWith('remote:')) { return true; } // Check if env has authType if (mcp.env?.authType === 'oauth') { return true; } // Known OAuth MCPs const oauthMCPs = ['canva', 'asana', 'google-drive', 'github', 'gitlab']; return oauthMCPs.includes(mcp.id); } }