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