UNPKG

mcp-subagents

Version:

Multi-Agent AI Orchestration via Model Context Protocol - Access specialized CLI AI agents (Aider, Qwen, Gemini, Goose, etc.) with intelligent fallback and configuration

236 lines 9.91 kB
import { z } from 'zod'; import { readFileSync } from 'fs'; import { homedir } from 'os'; import { resolve } from 'path'; // Agent-specific configuration schema export const AgentConfigSchema = z.object({ enabled: z.boolean().default(true), priority: z.number().int().min(1).max(100).optional().describe('Priority for fallback order (1-100, lower = higher priority)'), maxConcurrent: z.number().int().min(1).max(10).default(1).describe('Maximum concurrent tasks'), flags: z.array(z.string()).optional(), env: z.record(z.string()).optional(), model: z.string().optional(), timeout: z.number().int().positive().optional().describe('Task timeout in milliseconds') }); // MCP Server global settings schema export const ServerConfigSchema = z.object({ enableFallback: z.boolean().default(true).describe('Enable automatic fallback to other agents when primary fails'), defaultTimeout: z.number().int().positive().default(300000).describe('Default timeout for all agents in milliseconds'), maxConcurrentTasks: z.number().int().min(1).max(50).default(10).describe('Maximum total concurrent tasks across all agents') }).optional(); // Complete configuration schema with global server settings export const GlobalConfigSchema = z.object({ global: ServerConfigSchema, qwen: AgentConfigSchema.optional(), gemini: AgentConfigSchema.optional(), aider: AgentConfigSchema.optional(), goose: AgentConfigSchema.optional(), codex: AgentConfigSchema.optional(), opencode: AgentConfigSchema.optional(), claude: AgentConfigSchema.optional() }); export class ConfigManager { config = {}; enabledAgents = null; constructor() { this.loadConfig(); } loadConfig() { try { // Option 1: JSON string in environment variable const configJson = process.env['AGENT_CONFIG']; if (configJson) { const parsed = JSON.parse(configJson); this.config = GlobalConfigSchema.parse(parsed); console.error('📋 Loaded agent configuration from AGENT_CONFIG environment variable'); return; } // Option 2: Configuration file path const configFile = process.env['AGENT_CONFIG_FILE']; if (configFile) { // Expand ~ to home directory with fallbacks let expandedPath; try { if (configFile.startsWith('~')) { const homeDir = homedir(); if (!homeDir) { throw new Error('Unable to determine home directory'); } // Handle ~/path (remove ~/) or just ~ const relativePath = configFile === '~' ? '.' : configFile.slice(2); expandedPath = resolve(homeDir, relativePath); } else { expandedPath = resolve(configFile); } } catch (pathError) { console.error(`⚠️ Failed to resolve config file path: ${configFile}`); throw pathError; } const fileContent = readFileSync(expandedPath, 'utf-8'); const parsed = JSON.parse(fileContent); this.config = GlobalConfigSchema.parse(parsed); console.error(`📋 Loaded agent configuration from ${expandedPath}`); return; } // Option 3: Try default config file locations const defaultLocations = this.getDefaultConfigLocations(); for (const location of defaultLocations) { try { const fileContent = readFileSync(location, 'utf-8'); const parsed = JSON.parse(fileContent); this.config = GlobalConfigSchema.parse(parsed); console.error(`📋 Found and loaded agent configuration from ${location}`); return; } catch { // Continue to next location } } // No configuration provided - use defaults console.error('📋 No agent configuration found, using defaults'); } catch (error) { if (error instanceof Error) { if (error.message.includes('ENOENT')) { console.error('⚠️ Agent configuration file not found. Check AGENT_CONFIG_FILE path.'); } else if (error.message.includes('JSON')) { console.error('⚠️ Invalid JSON in agent configuration file. Please check syntax.'); } else if (error.name === 'ZodError') { console.error('⚠️ Invalid agent configuration format:', error.message); } else { console.error('⚠️ Failed to load agent configuration:', error.message); } } else { console.error('⚠️ Unknown error loading agent configuration:', error); } console.error('📋 Using default agent configuration'); this.config = {}; } finally { // Parse ENABLED_AGENTS environment variable after all config loading this.parseEnabledAgents(); } } parseEnabledAgents() { const enabledAgentsEnv = process.env['ENABLED_AGENTS']; if (!enabledAgentsEnv) { this.enabledAgents = null; // Use config file settings return; } try { // Parse comma-separated list, trim whitespace, convert to lowercase const agentNames = enabledAgentsEnv .split(',') .map(name => name.trim().toLowerCase()) .filter(name => name.length > 0); // Validate agent names const validAgents = ['qwen', 'gemini', 'aider', 'goose', 'codex', 'opencode', 'claude']; const validEnabledAgents = []; for (const name of agentNames) { if (validAgents.includes(name)) { validEnabledAgents.push(name); } else { console.error(`⚠️ Unknown agent in ENABLED_AGENTS: ${name}`); } } this.enabledAgents = new Set(validEnabledAgents); console.error(`📋 ENABLED_AGENTS: ${Array.from(this.enabledAgents).join(', ')}`); } catch (error) { console.error('⚠️ Failed to parse ENABLED_AGENTS:', error); this.enabledAgents = null; } } getAgentConfig(agentName) { const baseConfig = this.config[agentName] || { enabled: true, maxConcurrent: 1 }; // If ENABLED_AGENTS is set, override the enabled status if (this.enabledAgents !== null) { return { ...baseConfig, enabled: this.enabledAgents.has(agentName) }; } return baseConfig; } getServerConfig() { const defaults = { enableFallback: true, defaultTimeout: 300000, maxConcurrentTasks: 10 }; if (!this.config.global) return defaults; return { enableFallback: this.config.global.enableFallback ?? defaults.enableFallback, defaultTimeout: this.config.global.defaultTimeout ?? defaults.defaultTimeout, maxConcurrentTasks: this.config.global.maxConcurrentTasks ?? defaults.maxConcurrentTasks }; } getAllConfigs() { return this.config; } isAgentEnabled(agentName) { const config = this.getAgentConfig(agentName); return config.enabled !== false; // Default to enabled } isFallbackEnabled() { return this.getServerConfig().enableFallback; } getDefaultTimeout() { return this.getServerConfig().defaultTimeout; } getMaxConcurrentTasks() { return this.getServerConfig().maxConcurrentTasks; } /** * Get the current configuration for validation purposes */ getCurrentConfig() { return this.config; } /** * Check if configuration has been loaded successfully */ hasConfiguration() { return Object.keys(this.config).length > 0; } getDefaultConfigLocations() { const isWindows = process.platform === 'win32'; const home = homedir(); if (isWindows) { // Windows-specific locations const locations = []; // Standard Windows AppData locations if (process.env['APPDATA']) { locations.push(resolve(process.env['APPDATA'], 'mcp-subagents', 'config.json')); } if (process.env['LOCALAPPDATA']) { locations.push(resolve(process.env['LOCALAPPDATA'], 'mcp-subagents', 'config.json')); } // User profile locations locations.push(resolve(home, '.mcp-subagents.json')); locations.push(resolve(home, 'mcp-subagents.json')); // Current directory locations.push(resolve(process.cwd(), 'mcp-subagents.json')); locations.push(resolve(process.cwd(), '.mcp-subagents.json')); return locations; } else { // Unix-like systems (macOS, Linux) return [ resolve(home, '.config', 'mcp-subagents.json'), resolve(home, '.mcp-subagents.json'), resolve(process.cwd(), 'mcp-subagents.json'), resolve(process.cwd(), '.mcp-subagents.json') ]; } } } //# sourceMappingURL=config.js.map