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
JavaScript
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