Skip to main content
Glama
manager.ts7.84 kB
import fs from 'fs'; import path from 'path'; import { DEFAULT_CONFIG, REQUIRED_ENV_VARS, ZebrunnerDefaults } from './defaults.js'; /** * Configuration Manager for Zebrunner MCP Server * * Handles: * - Environment variable loading with defaults * - Auto-detection of rules engine based on rules file * - Validation of required configuration * - Graceful handling of missing/malformed files */ export class ConfigManager { private static instance: ConfigManager; private config: any = {}; private warnings: string[] = []; private constructor() { this.loadConfiguration(); } public static getInstance(): ConfigManager { if (!ConfigManager.instance) { ConfigManager.instance = new ConfigManager(); } return ConfigManager.instance; } /** * Get the complete configuration object */ public getConfig(): any { return { ...this.config }; } /** * Get configuration warnings (e.g., malformed rules file) */ public getWarnings(): string[] { return [...this.warnings]; } /** * Get a specific configuration value */ public get<K extends keyof ZebrunnerDefaults>(key: K): ZebrunnerDefaults[K] { return this.config[key] ?? DEFAULT_CONFIG[key]; } /** * Check if rules engine should be enabled */ public isRulesEngineEnabled(): boolean { // If explicitly set in environment, respect that setting const envValue = process.env.ENABLE_RULES_ENGINE; if (envValue !== undefined) { return envValue.toLowerCase() === 'true'; } // Otherwise, auto-detect based on rules file return this.detectRulesFile(); } /** * Load and process all configuration */ private loadConfiguration(): void { // Start with defaults this.config = { ...DEFAULT_CONFIG }; // Load environment variables this.loadEnvironmentVariables(); // Validate required variables this.validateRequiredVariables(); // Handle rules engine auto-detection this.handleRulesEngineDetection(); } /** * Load environment variables with type conversion */ private loadEnvironmentVariables(): void { // Load string values if (process.env.ZEBRUNNER_URL) this.config.baseUrl = process.env.ZEBRUNNER_URL; if (process.env.ZEBRUNNER_LOGIN) this.config.login = process.env.ZEBRUNNER_LOGIN; if (process.env.ZEBRUNNER_TOKEN) this.config.authToken = process.env.ZEBRUNNER_TOKEN; // Load numeric values with validation if (process.env.TIMEOUT) { const timeout = parseInt(process.env.TIMEOUT, 10); if (!isNaN(timeout) && timeout > 0) { this.config.timeout = timeout; } } if (process.env.RETRY_ATTEMPTS) { const retryAttempts = parseInt(process.env.RETRY_ATTEMPTS, 10); if (!isNaN(retryAttempts) && retryAttempts >= 0) { this.config.retryAttempts = retryAttempts; } } if (process.env.RETRY_DELAY) { const retryDelay = parseInt(process.env.RETRY_DELAY, 10); if (!isNaN(retryDelay) && retryDelay >= 0) { this.config.retryDelay = retryDelay; } } if (process.env.MAX_PAGE_SIZE) { const maxPageSize = parseInt(process.env.MAX_PAGE_SIZE, 10); if (!isNaN(maxPageSize) && maxPageSize > 0 && maxPageSize <= 1000) { this.config.maxPageSize = maxPageSize; } } if (process.env.DEFAULT_PAGE_SIZE) { const defaultPageSize = parseInt(process.env.DEFAULT_PAGE_SIZE, 10); if (!isNaN(defaultPageSize) && defaultPageSize > 0) { this.config.defaultPageSize = defaultPageSize; } } // Load boolean values if (process.env.DEBUG !== undefined) { this.config.debug = process.env.DEBUG.toLowerCase() === 'true'; } // ENABLE_RULES_ENGINE is handled separately in handleRulesEngineDetection } /** * Validate that required environment variables are set */ private validateRequiredVariables(): void { const missing = REQUIRED_ENV_VARS.filter(varName => !process.env[varName]); if (missing.length > 0) { throw new Error( `❌ Missing required environment variables: ${missing.join(', ')}\\n` + `Please set these variables in your .env file or environment.` ); } } /** * Handle rules engine detection logic */ private handleRulesEngineDetection(): void { // If explicitly set in environment, use that value if (process.env.ENABLE_RULES_ENGINE !== undefined) { this.config.enableRulesEngine = process.env.ENABLE_RULES_ENGINE.toLowerCase() === 'true'; return; } // Otherwise, auto-detect based on rules file this.config.enableRulesEngine = this.detectRulesFile(); } /** * Detect if rules file exists and has meaningful content */ private detectRulesFile(): boolean { try { const rulesFilePath = path.resolve(process.cwd(), DEFAULT_CONFIG.rulesFileName); // Check if file exists if (!fs.existsSync(rulesFilePath)) { return false; } // Read file content const content = fs.readFileSync(rulesFilePath, 'utf-8'); // Check if content is meaningful (not just whitespace/comments) const meaningfulContent = this.extractMeaningfulContent(content); if (meaningfulContent.length < DEFAULT_CONFIG.rulesFileMinContentLength) { this.warnings.push( `⚠️ Rules file '${DEFAULT_CONFIG.rulesFileName}' exists but appears to have insufficient content. ` + `Rules engine disabled. Minimum meaningful content: ${DEFAULT_CONFIG.rulesFileMinContentLength} characters.` ); return false; } // File exists and has meaningful content console.log(`✅ Auto-detected rules file '${DEFAULT_CONFIG.rulesFileName}' - Rules engine enabled`); return true; } catch (error: any) { this.warnings.push( `⚠️ Error reading rules file '${DEFAULT_CONFIG.rulesFileName}': ${error.message}. ` + `Rules engine disabled.` ); return false; } } /** * Extract meaningful content from rules file (excluding comments, whitespace) */ private extractMeaningfulContent(content: string): string { return content // Remove HTML/Markdown comments .replace(/<!--[\s\S]*?-->/g, '') // Remove empty lines and excessive whitespace .split('\n') .filter(line => { const trimmed = line.trim(); // Keep lines that have content, including markdown headers and list items return trimmed.length > 0 && // Don't exclude markdown headers (they are meaningful) !trimmed.startsWith('//') && !trimmed.startsWith('<!--') && // Keep meaningful markdown content (trimmed.includes(':') || trimmed.includes('-') || trimmed.length > 10); }) .join('\n') .trim(); } /** * Print configuration summary (for debugging) */ public printConfigSummary(): void { if (this.config.debug) { console.log('🔧 Zebrunner MCP Configuration:'); console.log(` - Base URL: ${this.config.baseUrl ? 'Set' : 'Not set'}`); console.log(` - Login: ${this.config.login ? 'Set' : 'Not set'}`); console.log(` - Token: ${this.config.authToken ? 'Set' : 'Not set'}`); console.log(` - Debug Mode: ${this.config.debug}`); console.log(` - Rules Engine: ${this.config.enableRulesEngine} ${process.env.ENABLE_RULES_ENGINE ? '(explicit)' : '(auto-detected)'}`); console.log(` - Max Page Size: ${this.config.maxPageSize}`); console.log(` - Default Page Size: ${this.config.defaultPageSize}`); if (this.warnings.length > 0) { console.log('⚠️ Configuration Warnings:'); this.warnings.forEach(warning => console.log(` ${warning}`)); } } } }

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/maksimsarychau/mcp-zebrunner'

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