UNPKG

claude-code-subagents-orchestrator

Version:

Claude Code Sub-agents Orchestrator - A powerful MCP server for orchestrating multiple AI sub-agents for complex task execution in Claude Code

394 lines (317 loc) 12.4 kB
#!/usr/bin/env node /** * MCP Server Auto-Registration Script * Handles automatic registration of the orchestrator with Claude Code */ import fs from 'fs-extra'; import path from 'path'; import os from 'os'; import chalk from 'chalk'; import inquirer from 'inquirer'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Platform-specific configuration paths const CLAUDE_CONFIG_PATHS = { win32: path.join(os.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'), darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'), linux: path.join(os.homedir(), '.config', 'claude', 'claude_desktop_config.json') }; // Server configuration template const SERVER_CONFIG_TEMPLATE = { type: 'stdio', command: 'node', args: [], env: { NODE_ENV: 'production' } }; class MCPInstaller { constructor(options = {}) { this.platform = os.platform(); this.serverName = 'claude-code-subagents-orchestrator'; this.interactive = options.interactive !== false; this.force = options.force || false; this.dryRun = options.dryRun || false; this.configPath = CLAUDE_CONFIG_PATHS[this.platform]; if (!this.configPath) { throw new Error(`Unsupported platform: ${this.platform}`); } } async install() { console.log(chalk.blue('🔧 MCP Server Auto-Registration')); console.log(chalk.gray(`Platform: ${this.platform}`)); console.log(chalk.gray(`Config path: ${this.configPath}`)); console.log(); try { // Check prerequisites await this.checkPrerequisites(); // Load existing configuration const config = await this.loadConfiguration(); // Check if already registered if (await this.isAlreadyRegistered(config)) { return await this.handleExistingRegistration(config); } // Generate server configuration const serverConfig = await this.generateServerConfig(); // Register the server await this.registerServer(config, serverConfig); // Verify registration await this.verifyRegistration(); console.log(); console.log(chalk.green('✅ MCP server registered successfully!')); console.log(); console.log(chalk.yellow('Next steps:')); console.log('1. Restart Claude Code if it\'s running'); console.log('2. The orchestrator should now be available in Claude Code'); console.log('3. Test with: claude-orchestrator health-check'); } catch (error) { console.error(chalk.red('❌ Registration failed:'), error.message); if (error.code === 'EACCES') { console.log(chalk.yellow('💡 Try running with elevated permissions')); } else if (error.code === 'ENOENT') { console.log(chalk.yellow('💡 Make sure Claude Code is installed')); } throw error; } } async checkPrerequisites() { console.log(chalk.blue('Checking prerequisites...')); // Check if Claude Code config directory exists const configDir = path.dirname(this.configPath); if (!await fs.pathExists(configDir)) { throw new Error(`Claude Code not found. Please install Claude Code first.\nExpected directory: ${configDir}`); } // Check if our package is installed const packageJsonPath = path.resolve(__dirname, '..', 'package.json'); if (!await fs.pathExists(packageJsonPath)) { throw new Error('Package installation not found'); } const packageJson = await fs.readJson(packageJsonPath); console.log(chalk.green(`✓ Package ${packageJson.name} v${packageJson.version} found`)); // Check server executable const serverPath = path.resolve(__dirname, '..', 'dist', 'server.js'); if (!await fs.pathExists(serverPath)) { throw new Error('Server executable not found. Make sure the package is built correctly.'); } console.log(chalk.green(`✓ Server executable found: ${serverPath}`)); } async loadConfiguration() { let config = {}; if (await fs.pathExists(this.configPath)) { try { config = await fs.readJson(this.configPath); console.log(chalk.green('✓ Existing Claude Code configuration loaded')); } catch (error) { console.log(chalk.yellow('⚠️ Invalid configuration file, creating new one')); config = {}; } } else { console.log(chalk.blue('Creating new Claude Code configuration')); } // Initialize mcpServers if it doesn't exist if (!config.mcpServers) { config.mcpServers = {}; } return config; } async isAlreadyRegistered(config) { return config.mcpServers && config.mcpServers[this.serverName]; } async handleExistingRegistration(config) { console.log(chalk.yellow(`⚠️ MCP server "${this.serverName}" is already registered`)); if (!this.interactive) { if (this.force) { console.log(chalk.blue('Force flag set, updating registration...')); return false; // Continue with registration } else { console.log(chalk.green('✓ Registration already exists')); return true; // Skip registration } } const { action } = await inquirer.prompt([ { type: 'list', name: 'action', message: 'What would you like to do?', choices: [ { name: 'Keep existing registration', value: 'keep' }, { name: 'Update registration', value: 'update' }, { name: 'View current configuration', value: 'view' } ] } ]); if (action === 'view') { console.log('\nCurrent configuration:'); console.log(JSON.stringify(config.mcpServers[this.serverName], null, 2)); return await this.handleExistingRegistration(config); } return action === 'keep'; } async generateServerConfig() { const serverPath = path.resolve(__dirname, '..', 'dist', 'server.js'); const config = { ...SERVER_CONFIG_TEMPLATE, args: [serverPath] }; // Platform-specific adjustments if (this.platform === 'win32') { // Use absolute path for Windows config.command = 'node.exe'; } console.log(chalk.blue('Generated server configuration:')); console.log(JSON.stringify(config, null, 2)); if (this.interactive) { const { proceed } = await inquirer.prompt([ { type: 'confirm', name: 'proceed', message: 'Proceed with this configuration?', default: true } ]); if (!proceed) { throw new Error('Registration cancelled by user'); } } return config; } async registerServer(config, serverConfig) { console.log(chalk.blue(`Registering MCP server: ${this.serverName}`)); config.mcpServers[this.serverName] = serverConfig; if (this.dryRun) { console.log(chalk.yellow('DRY RUN: Configuration would be written to:')); console.log(this.configPath); console.log(JSON.stringify(config, null, 2)); return; } // Backup existing configuration if (await fs.pathExists(this.configPath)) { const backupPath = `${this.configPath}.backup.${Date.now()}`; await fs.copy(this.configPath, backupPath); console.log(chalk.green(`✓ Configuration backed up to: ${backupPath}`)); } // Ensure directory exists await fs.ensureDir(path.dirname(this.configPath)); // Write configuration await fs.writeJson(this.configPath, config, { spaces: 2 }); console.log(chalk.green(`✓ Configuration written to: ${this.configPath}`)); } async verifyRegistration() { if (this.dryRun) { console.log(chalk.yellow('DRY RUN: Skipping verification')); return; } console.log(chalk.blue('Verifying registration...')); try { const config = await fs.readJson(this.configPath); if (!config.mcpServers || !config.mcpServers[this.serverName]) { throw new Error('Server not found in configuration'); } const serverConfig = config.mcpServers[this.serverName]; // Check if server executable exists const serverPath = serverConfig.args[0]; if (!await fs.pathExists(serverPath)) { throw new Error(`Server executable not found: ${serverPath}`); } console.log(chalk.green('✓ Registration verified')); } catch (error) { throw new Error(`Verification failed: ${error.message}`); } } async uninstall() { console.log(chalk.blue('🗑️ MCP Server Unregistration')); console.log(); try { if (!await fs.pathExists(this.configPath)) { console.log(chalk.yellow('⚠️ No Claude Code configuration found')); return; } const config = await fs.readJson(this.configPath); if (!config.mcpServers || !config.mcpServers[this.serverName]) { console.log(chalk.yellow(`⚠️ MCP server "${this.serverName}" not found`)); return; } if (this.interactive && !this.force) { const { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: `Remove MCP server "${this.serverName}" from Claude Code?`, default: false } ]); if (!confirm) { console.log(chalk.yellow('Unregistration cancelled')); return; } } // Backup configuration const backupPath = `${this.configPath}.backup.${Date.now()}`; await fs.copy(this.configPath, backupPath); console.log(chalk.green(`✓ Configuration backed up to: ${backupPath}`)); // Remove server delete config.mcpServers[this.serverName]; // Write updated configuration await fs.writeJson(this.configPath, config, { spaces: 2 }); console.log(chalk.green(`✓ MCP server "${this.serverName}" unregistered`)); } catch (error) { console.error(chalk.red('❌ Unregistration failed:'), error.message); throw error; } } } // CLI interface async function main() { const args = process.argv.slice(2); const command = args[0] || 'install'; const options = { interactive: !args.includes('--no-interactive'), force: args.includes('--force'), dryRun: args.includes('--dry-run') }; try { const installer = new MCPInstaller(options); switch (command) { case 'install': case 'register': await installer.install(); break; case 'uninstall': case 'unregister': await installer.uninstall(); break; case 'status': const config = await installer.loadConfiguration(); const isRegistered = await installer.isAlreadyRegistered(config); if (isRegistered) { console.log(chalk.green(`✓ MCP server "${installer.serverName}" is registered`)); console.log(JSON.stringify(config.mcpServers[installer.serverName], null, 2)); } else { console.log(chalk.yellow(`⚠️ MCP server "${installer.serverName}" is not registered`)); } break; default: console.log('Usage: node install-mcp.js [install|uninstall|status] [--force] [--no-interactive] [--dry-run]'); console.log(); console.log('Commands:'); console.log(' install Register MCP server with Claude Code (default)'); console.log(' uninstall Remove MCP server from Claude Code'); console.log(' status Show registration status'); console.log(); console.log('Options:'); console.log(' --force Force operation without prompts'); console.log(' --no-interactive Run in non-interactive mode'); console.log(' --dry-run Show what would be done without making changes'); process.exit(1); } } catch (error) { console.error(chalk.red('❌ Operation failed:'), error.message); process.exit(1); } } // Only run if called directly if (import.meta.url === `file://${process.argv[1]}`) { main(); } export { MCPInstaller };