Skip to main content
Glama
YobieBen
by YobieBen
config-manager.ts14.9 kB
/** * Configuration Manager Module * * Author: Yobie Benjamin * Version: 0.2 * Date: July 28, 2025 * * Manages all configuration for the Llama Maverick Hub. * Handles environment variables, config files, and service definitions. */ import * as fs from 'fs/promises'; import * as path from 'path'; import { z } from 'zod'; import winston from 'winston'; import { MCPServiceConfig } from '../clients/mcp-client-manager.js'; const logger = winston.createLogger({ level: 'debug', format: winston.format.simple() }); /** * Hub configuration schema * Validates all configuration options */ const HubConfigSchema = z.object({ hub: z.object({ name: z.string().default('llama-maverick-hub'), version: z.string().default('0.2.0'), port: z.number().default(8080), logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info') }), llama: z.object({ model: z.string().default('llama3.2'), baseUrl: z.string().default('http://localhost:11434'), contextWindow: z.number().default(8192), defaultTemperature: z.number().default(0.7) }), services: z.array(z.object({ id: z.string(), name: z.string(), description: z.string(), transport: z.enum(['stdio', 'websocket', 'http']), endpoint: z.string(), enabled: z.boolean().default(true), command: z.string().optional(), args: z.array(z.string()).optional(), url: z.string().optional(), headers: z.record(z.string()).optional(), reconnectPolicy: z.object({ maxRetries: z.number().default(5), retryDelayMs: z.number().default(5000), backoffMultiplier: z.number().default(2) }).optional() })).default([]), orchestration: z.object({ maxConcurrentOperations: z.number().default(10), defaultTimeout: z.number().default(30000), retryPolicy: z.object({ maxRetries: z.number().default(3), retryDelayMs: z.number().default(1000) }) }), security: z.object({ enableAuth: z.boolean().default(false), apiKeys: z.array(z.string()).optional(), allowedOrigins: z.array(z.string()).default(['*']), rateLimiting: z.object({ enabled: z.boolean().default(true), requestsPerMinute: z.number().default(100) }) }) }); type HubConfig = z.infer<typeof HubConfigSchema>; /** * Manages all configuration for the hub * Loads from files, environment, and provides defaults */ export class ConfigManager { private config: HubConfig | null = null; private configPath: string; private envPrefix = 'LLAMA_HUB_'; constructor(configPath?: string) { /** * Set configuration file path * Default to config.json in current directory */ this.configPath = configPath || path.join(process.cwd(), 'config.json'); } /** * Load configuration from all sources * Merges file config with environment variables */ async loadConfig(): Promise<void> { logger.info('Loading configuration...'); try { /** * Start with default configuration * Provides sensible defaults for all options */ let config = this.getDefaultConfig(); /** * Load from configuration file if exists * File config overrides defaults */ const fileConfig = await this.loadFileConfig(); if (fileConfig) { config = this.mergeConfigs(config, fileConfig); } /** * Load from environment variables * Environment overrides file config */ const envConfig = this.loadEnvConfig(); config = this.mergeConfigs(config, envConfig); /** * Load service definitions * Can come from separate file or inline */ const services = await this.loadServiceDefinitions(); if (services.length > 0) { config.services = services; } /** * Validate final configuration * Ensure all required fields are present */ this.config = HubConfigSchema.parse(config); logger.info('Configuration loaded successfully', { hubName: this.config.hub.name, servicesCount: this.config.services.length }); } catch (error) { logger.error('Failed to load configuration:', error); throw error; } } /** * Get default configuration * Provides base configuration with example services */ private getDefaultConfig(): HubConfig { return { hub: { name: 'llama-maverick-hub', version: '0.2.0', port: 8080, logLevel: 'info' }, llama: { model: 'llama3.2', baseUrl: 'http://localhost:11434', contextWindow: 8192, defaultTemperature: 0.7 }, services: [ /** * Example Stripe MCP service configuration * Connects to Stripe's MCP server for payment operations */ { id: 'stripe', name: 'Stripe MCP', description: 'Stripe payment processing via MCP', transport: 'stdio' as const, endpoint: 'stripe-mcp', enabled: true, command: 'npx', args: ['-y', '@stripe/mcp-server'], reconnectPolicy: { maxRetries: 5, retryDelayMs: 5000, backoffMultiplier: 2 } }, /** * Example GitHub MCP service configuration * Connects to GitHub for repository operations */ { id: 'github', name: 'GitHub MCP', description: 'GitHub repository management via MCP', transport: 'stdio' as const, endpoint: 'github-mcp', enabled: false, // Disabled by default command: 'github-mcp-server', args: [] }, /** * Example database MCP service configuration * Connects to a database service via WebSocket */ { id: 'database', name: 'Database MCP', description: 'Database operations via MCP', transport: 'websocket' as const, endpoint: 'ws://localhost:8081/mcp', enabled: false, url: 'ws://localhost:8081/mcp' } ], orchestration: { maxConcurrentOperations: 10, defaultTimeout: 30000, retryPolicy: { maxRetries: 3, retryDelayMs: 1000 } }, security: { enableAuth: false, allowedOrigins: ['*'], rateLimiting: { enabled: true, requestsPerMinute: 100 } } }; } /** * Load configuration from file * Reads JSON configuration file */ private async loadFileConfig(): Promise<Partial<HubConfig> | null> { try { const configData = await fs.readFile(this.configPath, 'utf-8'); const config = JSON.parse(configData); logger.debug('Loaded configuration from file', { path: this.configPath }); return config; } catch (error: any) { if (error.code === 'ENOENT') { logger.debug('No configuration file found, using defaults'); return null; } logger.error('Failed to load configuration file:', error); throw error; } } /** * Load configuration from environment variables * Maps environment variables to config structure */ private loadEnvConfig(): Partial<HubConfig> { const config: any = {}; /** * Map environment variables to configuration * Uses LLAMA_HUB_ prefix for all variables */ // Hub configuration if (process.env[`${this.envPrefix}NAME`]) { config.hub = config.hub || {}; config.hub.name = process.env[`${this.envPrefix}NAME`]; } if (process.env[`${this.envPrefix}PORT`]) { config.hub = config.hub || {}; config.hub.port = parseInt(process.env[`${this.envPrefix}PORT`]); } if (process.env[`${this.envPrefix}LOG_LEVEL`]) { config.hub = config.hub || {}; config.hub.logLevel = process.env[`${this.envPrefix}LOG_LEVEL`]; } // Llama configuration if (process.env[`${this.envPrefix}LLAMA_MODEL`]) { config.llama = config.llama || {}; config.llama.model = process.env[`${this.envPrefix}LLAMA_MODEL`]; } if (process.env[`${this.envPrefix}LLAMA_BASE_URL`]) { config.llama = config.llama || {}; config.llama.baseUrl = process.env[`${this.envPrefix}LLAMA_BASE_URL`]; } // Security configuration if (process.env[`${this.envPrefix}ENABLE_AUTH`]) { config.security = config.security || {}; config.security.enableAuth = process.env[`${this.envPrefix}ENABLE_AUTH`] === 'true'; } if (process.env[`${this.envPrefix}API_KEYS`]) { config.security = config.security || {}; config.security.apiKeys = process.env[`${this.envPrefix}API_KEYS`].split(','); } return config; } /** * Load service definitions from file or environment * Supports dynamic service configuration */ private async loadServiceDefinitions(): Promise<MCPServiceConfig[]> { const services: MCPServiceConfig[] = []; /** * Check for services definition file * Allows external service configuration */ const servicesPath = process.env[`${this.envPrefix}SERVICES_FILE`] || path.join(process.cwd(), 'services.json'); try { const servicesData = await fs.readFile(servicesPath, 'utf-8'); const loadedServices = JSON.parse(servicesData); if (Array.isArray(loadedServices)) { services.push(...loadedServices); logger.debug(`Loaded ${loadedServices.length} services from file`); } } catch (error: any) { if (error.code !== 'ENOENT') { logger.warn('Failed to load services file:', error); } } /** * Check for individual service environment variables * Supports defining services via environment */ // Example: LLAMA_HUB_SERVICE_STRIPE_ENABLED=true const envServices = this.parseServiceEnvVars(); services.push(...envServices); return services; } /** * Parse service definitions from environment variables * Supports pattern: LLAMA_HUB_SERVICE_<ID>_<PROPERTY> */ private parseServiceEnvVars(): MCPServiceConfig[] { const services: Map<string, Partial<MCPServiceConfig>> = new Map(); const servicePrefix = `${this.envPrefix}SERVICE_`; for (const [key, value] of Object.entries(process.env)) { if (!key.startsWith(servicePrefix)) continue; // Parse service ID and property const parts = key.slice(servicePrefix.length).split('_'); if (parts.length < 2) continue; const serviceId = parts[0].toLowerCase(); const property = parts.slice(1).join('_').toLowerCase(); // Initialize service if needed if (!services.has(serviceId)) { services.set(serviceId, { id: serviceId }); } const service = services.get(serviceId)!; // Map property to service configuration switch (property) { case 'name': service.name = value; break; case 'description': service.description = value; break; case 'transport': service.transport = value as any; break; case 'endpoint': service.endpoint = value; break; case 'enabled': service.enabled = value === 'true'; break; case 'command': service.command = value; break; case 'url': service.url = value; break; } } return Array.from(services.values()) as MCPServiceConfig[]; } /** * Merge two configuration objects * Deep merge with override behavior */ private mergeConfigs(base: any, override: any): any { const merged = { ...base }; for (const key in override) { if (override[key] === undefined) continue; if (typeof override[key] === 'object' && !Array.isArray(override[key])) { merged[key] = this.mergeConfigs(merged[key] || {}, override[key]); } else { merged[key] = override[key]; } } return merged; } /** * Get the loaded configuration * Returns validated configuration object */ getConfig(): HubConfig { if (!this.config) { throw new Error('Configuration not loaded'); } return this.config; } /** * Get enabled services * Returns only services marked as enabled */ getEnabledServices(): MCPServiceConfig[] { if (!this.config) { throw new Error('Configuration not loaded'); } return this.config.services.filter(s => s.enabled); } /** * Get service configuration by ID * Returns specific service configuration */ getService(serviceId: string): MCPServiceConfig | undefined { if (!this.config) { throw new Error('Configuration not loaded'); } return this.config.services.find(s => s.id === serviceId); } /** * Update service configuration * Modifies service settings at runtime */ updateService(serviceId: string, updates: Partial<MCPServiceConfig>): void { if (!this.config) { throw new Error('Configuration not loaded'); } const serviceIndex = this.config.services.findIndex(s => s.id === serviceId); if (serviceIndex === -1) { throw new Error(`Service ${serviceId} not found`); } this.config.services[serviceIndex] = { ...this.config.services[serviceIndex], ...updates }; logger.info(`Updated service configuration: ${serviceId}`); } /** * Save configuration to file * Persists current configuration */ async saveConfig(): Promise<void> { if (!this.config) { throw new Error('Configuration not loaded'); } try { const configData = JSON.stringify(this.config, null, 2); await fs.writeFile(this.configPath, configData, 'utf-8'); logger.info('Configuration saved to file', { path: this.configPath }); } catch (error) { logger.error('Failed to save configuration:', error); throw error; } } /** * Reload configuration from sources * Refreshes configuration without restart */ async reloadConfig(): Promise<void> { logger.info('Reloading configuration...'); this.config = null; await this.loadConfig(); } /** * Validate service configuration * Checks if service config is valid */ validateServiceConfig(service: MCPServiceConfig): boolean { try { // Basic validation if (!service.id || !service.name || !service.transport) { return false; } // Transport-specific validation switch (service.transport) { case 'stdio': return !!service.command; case 'websocket': case 'http': return !!service.url || !!service.endpoint; default: return false; } } catch { return false; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/YobieBen/llama-maverick-hub-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server