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