vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
634 lines (633 loc) • 27.3 kB
JavaScript
import path from 'path';
import { FileUtils } from './file-utils.js';
import { ConfigurationError, ValidationError, createErrorContext } from './enhanced-errors.js';
import { ENVIRONMENT_VARIABLES, DEFAULT_PERFORMANCE_CONFIG, getEnvironmentValue, validateAllEnvironmentVariables } from './config-defaults.js';
import logger from '../../../logger.js';
import { getProjectRootSafe, logWorkingDirectorySafety } from '../../../utils/safe-path-resolver.js';
import { getProjectRoot } from '../../code-map-generator/utils/pathUtils.enhanced.js';
export class ConfigLoader {
static instance;
config = null;
configCache = new Map();
llmConfigPath;
mcpConfigPath;
performanceConfig;
initializationPromise = null;
loadingStartTime = 0;
cacheHits = 0;
cacheRequests = 0;
constructor() {
const projectRoot = getProjectRoot();
this.llmConfigPath = path.join(projectRoot, 'llm_config.json');
this.mcpConfigPath = path.join(projectRoot, 'mcp-config.json');
this.performanceConfig = { ...DEFAULT_PERFORMANCE_CONFIG };
}
static getInstance() {
if (!ConfigLoader.instance) {
ConfigLoader.instance = new ConfigLoader();
}
return ConfigLoader.instance;
}
getVibeTaskManagerOutputDirectory() {
const baseOutputDir = process.env.VIBE_CODER_OUTPUT_DIR
? path.resolve(process.env.VIBE_CODER_OUTPUT_DIR)
: path.join(getProjectRoot(), 'VibeCoderOutput');
return path.join(baseOutputDir, 'vibe-task-manager');
}
isCacheValid(cacheKey) {
if (!this.performanceConfig.enableConfigCache) {
return false;
}
const cached = this.configCache.get(cacheKey);
if (!cached) {
return false;
}
const now = Date.now();
return (now - cached.timestamp) < cached.ttl;
}
getCachedConfig(cacheKey) {
this.cacheRequests++;
if (!this.isCacheValid(cacheKey)) {
this.configCache.delete(cacheKey);
return null;
}
const cached = this.configCache.get(cacheKey);
if (cached) {
this.cacheHits++;
return { ...cached.config };
}
return null;
}
cacheConfig(cacheKey, config) {
if (!this.performanceConfig.enableConfigCache) {
return;
}
this.configCache.set(cacheKey, {
config: { ...config },
timestamp: Date.now(),
ttl: this.performanceConfig.configCacheTTL
});
}
async batchLoadConfigs() {
const context = createErrorContext('ConfigLoader', 'batchLoadConfigs')
.metadata({
llmConfigPath: this.llmConfigPath,
mcpConfigPath: this.mcpConfigPath,
batchLoading: this.performanceConfig.batchConfigLoading
})
.build();
try {
if (this.performanceConfig.batchConfigLoading) {
const [llmResult, mcpResult] = await Promise.all([
FileUtils.readJsonFile(this.llmConfigPath),
FileUtils.readJsonFile(this.mcpConfigPath)
]);
if (!llmResult.success) {
throw new ConfigurationError(`Failed to load LLM configuration file: ${llmResult.error}`, context, {
configKey: 'llm_config',
expectedValue: 'Valid JSON file with LLM mappings',
actualValue: llmResult.error
});
}
if (!mcpResult.success) {
throw new ConfigurationError(`Failed to load MCP configuration file: ${mcpResult.error}`, context, {
configKey: 'mcp_config',
expectedValue: 'Valid JSON file with MCP tool definitions',
actualValue: mcpResult.error
});
}
return {
llm: llmResult.data,
mcp: mcpResult.data
};
}
else {
const llmResult = await FileUtils.readJsonFile(this.llmConfigPath);
if (!llmResult.success) {
throw new ConfigurationError(`Failed to load LLM configuration file: ${llmResult.error}`, context, {
configKey: 'llm_config',
expectedValue: 'Valid JSON file with LLM mappings',
actualValue: llmResult.error
});
}
const mcpResult = await FileUtils.readJsonFile(this.mcpConfigPath);
if (!mcpResult.success) {
throw new ConfigurationError(`Failed to load MCP configuration file: ${mcpResult.error}`, context, {
configKey: 'mcp_config',
expectedValue: 'Valid JSON file with MCP tool definitions',
actualValue: mcpResult.error
});
}
return {
llm: llmResult.data,
mcp: mcpResult.data
};
}
}
catch (error) {
if (error instanceof ConfigurationError) {
throw error;
}
throw new ConfigurationError(`Unexpected error during configuration loading: ${error instanceof Error ? error.message : String(error)}`, context, {
cause: error instanceof Error ? error : undefined
});
}
}
async loadConfig() {
this.loadingStartTime = performance.now();
try {
const cacheKey = 'main-config';
const cachedConfig = this.getCachedConfig(cacheKey);
if (cachedConfig) {
const loadTime = performance.now() - this.loadingStartTime;
logger.debug({ loadTime }, 'Configuration loaded from cache');
this.config = cachedConfig;
return {
success: true,
data: cachedConfig,
metadata: {
filePath: 'cached-config',
operation: 'load_config_cached',
timestamp: new Date(),
loadTime
}
};
}
logger.debug('Loading Vibe Task Manager configuration from files');
const { llm, mcp } = await this.batchLoadConfigs();
const envValidation = validateAllEnvironmentVariables();
if (!envValidation.valid) {
const errorContext = createErrorContext('ConfigLoader', 'loadConfig')
.metadata({ errors: envValidation.errors })
.build();
throw new ConfigurationError(`Environment variable validation failed: ${envValidation.errors.join(', ')}`, errorContext, {
configKey: 'environment_variables',
expectedValue: 'Valid environment configuration',
actualValue: envValidation.errors.join(', ')
});
}
if (envValidation.warnings.length > 0) {
logger.warn({ warnings: envValidation.warnings }, 'Environment variable warnings (using defaults)');
}
this.config = {
llm,
mcp,
taskManager: {
maxConcurrentTasks: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_CONCURRENT_TASKS),
defaultTaskTemplate: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DEFAULT_TASK_TEMPLATE),
dataDirectory: this.getVibeTaskManagerOutputDirectory(),
performanceTargets: {
maxResponseTime: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_RESPONSE_TIME),
maxMemoryUsage: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_MEMORY_USAGE),
minTestCoverage: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MIN_TEST_COVERAGE)
},
agentSettings: {
maxAgents: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_AGENTS),
defaultAgent: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DEFAULT_AGENT),
coordinationStrategy: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_COORDINATION_STRATEGY),
healthCheckInterval: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_HEALTH_CHECK_INTERVAL)
},
nlpSettings: {
primaryMethod: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_PRIMARY_NLP_METHOD),
fallbackMethod: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_FALLBACK_NLP_METHOD),
minConfidence: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MIN_CONFIDENCE),
maxProcessingTime: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_NLP_PROCESSING_TIME)
},
timeouts: {
taskExecution: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_TASK_EXECUTION_TIMEOUT),
taskDecomposition: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_TASK_DECOMPOSITION_TIMEOUT),
recursiveTaskDecomposition: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RECURSIVE_TASK_DECOMPOSITION_TIMEOUT),
taskRefinement: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_TASK_REFINEMENT_TIMEOUT),
agentCommunication: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_AGENT_COMMUNICATION_TIMEOUT),
llmRequest: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_LLM_REQUEST_TIMEOUT),
fileOperations: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_FILE_OPERATIONS_TIMEOUT),
databaseOperations: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DATABASE_OPERATIONS_TIMEOUT),
networkOperations: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_NETWORK_OPERATIONS_TIMEOUT)
},
retryPolicy: {
maxRetries: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_RETRIES),
backoffMultiplier: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_BACKOFF_MULTIPLIER),
initialDelayMs: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_INITIAL_DELAY_MS),
maxDelayMs: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_DELAY_MS),
enableExponentialBackoff: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_ENABLE_EXPONENTIAL_BACKOFF)
},
rddConfig: {
maxDepth: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_MAX_DEPTH),
maxSubTasks: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_MAX_SUB_TASKS),
minConfidence: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_MIN_CONFIDENCE),
enableParallelDecomposition: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_ENABLE_PARALLEL_DECOMPOSITION),
epicTimeLimit: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_EPIC_TIME_LIMIT)
},
performance: {
memoryManagement: {
enabled: true,
maxMemoryPercentage: 0.3,
monitorInterval: 5000,
autoManage: true,
pruneThreshold: 0.6,
prunePercentage: 0.4
},
fileSystem: {
enableLazyLoading: true,
batchSize: 50,
enableCompression: false,
indexingEnabled: true,
concurrentOperations: 10
},
caching: {
enabled: true,
strategy: 'memory',
maxCacheSize: 50 * 1024 * 1024,
defaultTTL: 60000,
enableWarmup: true
},
monitoring: {
enabled: true,
metricsInterval: 1000,
enableAlerts: true,
performanceThresholds: {
maxResponseTime: 50,
maxMemoryUsage: 300,
maxCpuUsage: 70
}
}
}
}
};
this.cacheConfig(cacheKey, this.config);
const loadTime = performance.now() - this.loadingStartTime;
if (loadTime > this.performanceConfig.maxStartupTime) {
logger.warn({
loadTime,
target: this.performanceConfig.maxStartupTime
}, 'Configuration loading exceeded performance target');
}
else {
logger.debug({ loadTime }, 'Configuration loaded within performance target');
}
logger.info({ loadTime }, 'Vibe Task Manager configuration loaded successfully');
return {
success: true,
data: this.config,
metadata: {
filePath: 'combined-config',
operation: 'load_config',
timestamp: new Date(),
loadTime,
fromCache: false
}
};
}
catch (error) {
const loadTime = performance.now() - this.loadingStartTime;
if (error instanceof ConfigurationError || error instanceof ValidationError) {
logger.error({
err: error,
loadTime,
category: error.category,
severity: error.severity,
retryable: error.retryable,
recoveryActions: error.recoveryActions.length
}, 'Configuration loading failed with enhanced error');
}
else {
logger.error({
err: error,
loadTime,
errorType: error instanceof Error ? error.constructor.name : 'Unknown'
}, 'Configuration loading failed with unexpected error');
}
return {
success: false,
error: error instanceof Error ? error.message : String(error),
metadata: {
filePath: 'combined-config',
operation: 'load_config',
timestamp: new Date(),
loadTime
}
};
}
}
getConfig() {
return this.config ? { ...this.config } : null;
}
getLLMModel(operation) {
const fallbackModel = getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DEFAULT_LLM_MODEL);
if (!this.config) {
return fallbackModel;
}
return this.config.llm.llm_mapping[operation] ||
this.config.llm.llm_mapping['default_generation'] ||
fallbackModel;
}
getMCPToolConfig(toolName) {
if (!this.config) {
return null;
}
return this.config.mcp.tools[toolName] || null;
}
getTaskManagerConfig() {
return this.config?.taskManager || null;
}
validateLLMMappings() {
if (!this.config) {
return { valid: false, missing: ['Configuration not loaded'] };
}
const requiredMappings = [
'task_decomposition',
'atomic_task_detection',
'intent_recognition',
'task_refinement',
'dependency_graph_analysis',
'agent_coordination'
];
const missing = requiredMappings.filter(mapping => !this.config.llm.llm_mapping[mapping]);
return {
valid: missing.length === 0,
missing
};
}
validateMCPRegistration() {
if (!this.config) {
return { valid: false, error: 'Configuration not loaded' };
}
const taskManagerConfig = this.config.mcp.tools['vibe-task-manager'];
if (!taskManagerConfig) {
return { valid: false, error: 'vibe-task-manager not found in MCP config' };
}
if (!taskManagerConfig.description ||
!taskManagerConfig.use_cases ||
!taskManagerConfig.input_patterns) {
return { valid: false, error: 'vibe-task-manager MCP config is incomplete' };
}
return { valid: true };
}
getConfigSummary() {
if (!this.config) {
return {
llmMappingsCount: 0,
mcpToolsCount: 0,
taskManagerConfigured: false,
requiredLLMMappingsPresent: false
};
}
const llmValidation = this.validateLLMMappings();
const mcpValidation = this.validateMCPRegistration();
return {
llmMappingsCount: Object.keys(this.config.llm.llm_mapping).length,
mcpToolsCount: Object.keys(this.config.mcp.tools).length,
taskManagerConfigured: mcpValidation.valid,
requiredLLMMappingsPresent: llmValidation.valid
};
}
async reloadConfig() {
this.config = null;
return await this.loadConfig();
}
isConfigValid() {
if (!this.config) {
return false;
}
const llmValidation = this.validateLLMMappings();
const mcpValidation = this.validateMCPRegistration();
return llmValidation.valid && mcpValidation.valid;
}
getPerformanceConfig() {
return { ...this.performanceConfig };
}
updatePerformanceConfig(updates) {
this.performanceConfig = { ...this.performanceConfig, ...updates };
logger.debug({ updates }, 'Performance configuration updated');
}
clearCache() {
this.configCache.clear();
this.cacheHits = 0;
this.cacheRequests = 0;
logger.debug('Configuration cache and statistics cleared');
}
resetCacheStats() {
this.cacheHits = 0;
this.cacheRequests = 0;
logger.debug('Cache statistics reset');
}
getCacheStats() {
const entries = Array.from(this.configCache.keys());
const hitRate = this.cacheRequests > 0 ? (this.cacheHits / this.cacheRequests) : 0;
return {
size: this.configCache.size,
entries,
hitRate: Math.round(hitRate * 100) / 100,
totalRequests: this.cacheRequests,
totalHits: this.cacheHits
};
}
async warmupCache() {
if (!this.performanceConfig.enableConfigCache) {
return;
}
const startTime = performance.now();
await this.loadConfig();
this.getLLMModel('task_decomposition');
this.getLLMModel('atomic_task_detection');
this.getLLMModel('intent_recognition');
this.getMCPToolConfig('vibe-task-manager');
this.getTaskManagerConfig();
const warmupTime = performance.now() - startTime;
logger.debug({ warmupTime }, 'Configuration cache warmed up');
}
}
export async function getVibeTaskManagerConfig() {
const loader = ConfigLoader.getInstance();
if (!loader.getConfig()) {
const result = await loader.loadConfig();
if (!result.success) {
logger.error({ error: result.error }, 'Failed to load Vibe Task Manager configuration');
return null;
}
}
return loader.getConfig();
}
export async function getLLMModelForOperation(operation) {
const loader = ConfigLoader.getInstance();
if (!loader.getConfig()) {
await loader.loadConfig();
}
return loader.getLLMModel(operation);
}
export function getBaseOutputDir() {
return process.env.VIBE_CODER_OUTPUT_DIR
? path.resolve(process.env.VIBE_CODER_OUTPUT_DIR)
: path.join(getProjectRoot(), 'VibeCoderOutput');
}
export function getVibeTaskManagerOutputDir() {
const baseOutputDir = getBaseOutputDir();
return path.join(baseOutputDir, 'vibe-task-manager');
}
function validateExtractedSecurityConfig(extractedConfig) {
const errors = [];
const warnings = [];
if (!extractedConfig.allowedReadDirectory) {
errors.push('allowedReadDirectory is required');
}
else if (typeof extractedConfig.allowedReadDirectory !== 'string') {
errors.push('allowedReadDirectory must be a string');
}
if (!extractedConfig.allowedWriteDirectory) {
errors.push('allowedWriteDirectory is required');
}
else if (typeof extractedConfig.allowedWriteDirectory !== 'string') {
errors.push('allowedWriteDirectory must be a string');
}
if (extractedConfig.securityMode &&
extractedConfig.securityMode !== 'strict' &&
extractedConfig.securityMode !== 'permissive') {
errors.push('securityMode must be either "strict" or "permissive"');
}
return {
isValid: errors.length === 0,
errors: errors,
warnings: warnings,
extractedConfig
};
}
export function extractVibeTaskManagerSecurityConfig(config) {
let securityConfig = {};
if (config) {
if (config.env) {
logger.debug({
configEnv: config.env,
hasReadDir: Boolean(config.env.VIBE_TASK_MANAGER_READ_DIR),
hasWriteDir: Boolean(config.env.VIBE_CODER_OUTPUT_DIR),
hasSecurityMode: Boolean(config.env.VIBE_TASK_MANAGER_SECURITY_MODE)
}, 'Extracting security config from MCP client environment variables');
securityConfig = {
allowedReadDirectory: config.env.VIBE_TASK_MANAGER_READ_DIR,
allowedWriteDirectory: config.env.VIBE_CODER_OUTPUT_DIR,
securityMode: (config.env.VIBE_TASK_MANAGER_SECURITY_MODE || 'strict')
};
}
if (!securityConfig.allowedReadDirectory || !securityConfig.allowedWriteDirectory) {
const toolConfig = config.tools?.['vibe-task-manager'];
const configSection = config.config?.['vibe-task-manager'];
if (toolConfig || configSection) {
logger.debug({
toolConfig,
configSection
}, 'Using legacy tool config extraction as fallback');
securityConfig = {
...configSection,
...toolConfig,
...securityConfig
};
}
}
}
logger.debug({
extractedSecurityConfig: securityConfig,
hasConfig: Boolean(config),
hasEnv: Boolean(config?.env),
configKeys: config ? Object.keys(config) : []
}, 'Extracted vibe-task-manager security config');
logWorkingDirectorySafety();
const allowedReadDirectory = securityConfig.allowedReadDirectory ||
process.env.VIBE_TASK_MANAGER_READ_DIR ||
getProjectRootSafe();
const allowedWriteDirectory = securityConfig.allowedWriteDirectory ||
process.env.VIBE_CODER_OUTPUT_DIR ||
path.join(getProjectRootSafe(), 'VibeCoderOutput');
const securityMode = (securityConfig.securityMode ||
process.env.VIBE_TASK_MANAGER_SECURITY_MODE ||
'strict');
const finalConfig = {
allowedReadDirectory,
allowedWriteDirectory,
securityMode
};
const validation = validateExtractedSecurityConfig(finalConfig);
if (!validation.isValid) {
const errorMessage = `Security configuration validation failed: ${validation.errors.join(', ')}`;
logger.error({
errors: validation.errors,
warnings: validation.warnings,
config: finalConfig
}, errorMessage);
throw new Error(errorMessage);
}
if (validation.warnings.length > 0) {
logger.warn({
warnings: validation.warnings
}, 'Security configuration has warnings');
}
let resolvedReadDir;
let resolvedWriteDir;
try {
resolvedReadDir = path.resolve(allowedReadDirectory);
resolvedWriteDir = path.resolve(allowedWriteDirectory);
}
catch (pathError) {
const errorMessage = `Failed to resolve security configuration paths: ${pathError instanceof Error ? pathError.message : String(pathError)}`;
logger.error({
allowedReadDirectory,
allowedWriteDirectory,
pathError
}, errorMessage);
throw new Error(errorMessage);
}
logger.info({
allowedReadDirectory: resolvedReadDir,
allowedWriteDirectory: resolvedWriteDir,
securityMode,
extractionSource: config?.env ? 'MCP client env' : 'process env fallback'
}, 'Vibe Task Manager security configuration extracted from MCP client config');
return {
allowedReadDirectory: resolvedReadDir,
allowedWriteDirectory: resolvedWriteDir,
securityMode
};
}
export async function getOrchestratorConfig() {
try {
const config = await getVibeTaskManagerConfig();
if (!config) {
logger.debug('No config found, using AgentOrchestrator defaults');
return null;
}
const agentSettings = config.taskManager?.agentSettings;
const timeouts = config.taskManager?.timeouts;
if (!agentSettings) {
logger.debug('No agent settings found in config, using AgentOrchestrator defaults');
return null;
}
const orchestratorConfig = {
heartbeatInterval: (agentSettings.healthCheckInterval || 30) * 1000,
taskTimeout: timeouts?.taskExecution || 300000,
maxRetries: 3,
loadBalancingStrategy: mapCoordinationStrategy(agentSettings.coordinationStrategy),
enableHealthChecks: true,
conflictResolutionStrategy: 'queue',
heartbeatTimeoutMultiplier: 3,
enableAdaptiveTimeouts: true,
maxHeartbeatMisses: 5
};
logger.debug({ orchestratorConfig }, 'Created OrchestratorConfig from centralized config');
return orchestratorConfig;
}
catch (error) {
logger.warn({ err: error }, 'Failed to load centralized config for AgentOrchestrator, using defaults');
return null;
}
}
function mapCoordinationStrategy(strategy) {
switch (strategy) {
case 'round_robin':
return 'round_robin';
case 'capability_based':
return 'capability_based';
case 'priority_based':
case 'least_loaded':
return 'performance_based';
default:
return 'capability_based';
}
}