/** * @fileoverview Configuration Manager for Laravel Healthcare MCP Server * Handles environment variables, validation, and configuration loading */ import dotenv from "dotenv"; import { logger } from "../utils/logger.js"; import { ConfigurationError } from "../utils/errors.js"; /** * Configuration Manager class * Manages application configuration and environment variables */ export class ConfigManager { /** * Create ConfigManager instance * @param {string} envPath - Path to .env file (optional) */ constructor(envPath = null) { this.config = {}; this.validationRules = this._defineValidationRules(); // Load environment variables this._loadEnvironment(envPath); // Load and validate configuration this._loadConfiguration(); this._validateConfiguration(); logger.info("Configuration loaded and validated successfully"); } /** * Load environment variables from .env file * @private * @param {string} envPath - Path to .env file */ _loadEnvironment(envPath) { try { const result = dotenv.config({ path: envPath }); if (result.error && envPath) { logger.warn( `Failed to load .env file from ${envPath}:`, result.error.message ); } logger.debug("Environment variables loaded"); } catch (error) { logger.warn("Error loading environment variables:", error.message); } } /** * Load configuration from environment variables * @private */ _loadConfiguration() { // Laravel API Configuration this.config.LARAVEL_API_BASE_URL = process.env.LARAVEL_API_BASE_URL; this.config.LARAVEL_API_TIMEOUT = parseInt(process.env.LARAVEL_API_TIMEOUT) || 30000; this.config.LARAVEL_API_RETRY_ATTEMPTS = parseInt(process.env.LARAVEL_API_RETRY_ATTEMPTS) || 3; this.config.LARAVEL_API_RETRY_DELAY = parseInt(process.env.LARAVEL_API_RETRY_DELAY) || 1000; // MCP Server Configuration this.config.MCP_SERVER_NAME = process.env.MCP_SERVER_NAME || "laravel-healthcare-mcp-server"; this.config.MCP_SERVER_VERSION = process.env.MCP_SERVER_VERSION || "1.0.0"; this.config.MCP_SERVER_PORT = parseInt(process.env.MCP_SERVER_PORT) || 3000; // Authentication Configuration this.config.ADMIN_USERNAME = process.env.ADMIN_USERNAME; this.config.ADMIN_PASSWORD = process.env.ADMIN_PASSWORD; this.config.ADMIN_LOGIN_ENDPOINT = process.env.ADMIN_LOGIN_ENDPOINT || "/api/admin/login"; this.config.ADMIN_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.ADMIN_TOKEN_REFRESH_THRESHOLD) || 300; this.config.AGENT_USERNAME = process.env.AGENT_USERNAME; this.config.AGENT_PASSWORD = process.env.AGENT_PASSWORD; this.config.AGENT_LOGIN_ENDPOINT = process.env.AGENT_LOGIN_ENDPOINT || "/agent/login/post"; this.config.AGENT_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.AGENT_TOKEN_REFRESH_THRESHOLD) || 300; this.config.PATIENT_USERNAME = process.env.PATIENT_USERNAME; this.config.PATIENT_PASSWORD = process.env.PATIENT_PASSWORD; this.config.PATIENT_LOGIN_ENDPOINT = process.env.PATIENT_LOGIN_ENDPOINT || "/api/frontend/login"; this.config.PATIENT_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.PATIENT_TOKEN_REFRESH_THRESHOLD) || 300; this.config.PRACTITIONER_USERNAME = process.env.PRACTITIONER_USERNAME; this.config.PRACTITIONER_PASSWORD = process.env.PRACTITIONER_PASSWORD; this.config.PRACTITIONER_LOGIN_ENDPOINT = process.env.PRACTITIONER_LOGIN_ENDPOINT || "/api/practitioner/login"; this.config.PRACTITIONER_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.PRACTITIONER_TOKEN_REFRESH_THRESHOLD) || 300; this.config.AFFILIATE_USERNAME = process.env.AFFILIATE_USERNAME; this.config.AFFILIATE_PASSWORD = process.env.AFFILIATE_PASSWORD; this.config.AFFILIATE_LOGIN_ENDPOINT = process.env.AFFILIATE_LOGIN_ENDPOINT || "/api/affiliate/login"; this.config.AFFILIATE_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.AFFILIATE_TOKEN_REFRESH_THRESHOLD) || 300; this.config.PARTNER_USERNAME = process.env.PARTNER_USERNAME; this.config.PARTNER_PASSWORD = process.env.PARTNER_PASSWORD; this.config.PARTNER_LOGIN_ENDPOINT = process.env.PARTNER_LOGIN_ENDPOINT || "/api/partner/login"; this.config.PARTNER_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.PARTNER_TOKEN_REFRESH_THRESHOLD) || 300; this.config.NETWORK_USERNAME = process.env.NETWORK_USERNAME; this.config.NETWORK_PASSWORD = process.env.NETWORK_PASSWORD; this.config.NETWORK_LOGIN_ENDPOINT = process.env.NETWORK_LOGIN_ENDPOINT || "/api/network/login"; this.config.NETWORK_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.NETWORK_TOKEN_REFRESH_THRESHOLD) || 300; this.config.DOCTOR_USERNAME = process.env.DOCTOR_USERNAME; this.config.DOCTOR_PASSWORD = process.env.DOCTOR_PASSWORD; this.config.DOCTOR_LOGIN_ENDPOINT = process.env.DOCTOR_LOGIN_ENDPOINT || "/api/doctor/login"; this.config.DOCTOR_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.DOCTOR_TOKEN_REFRESH_THRESHOLD) || 300; this.config.PROVIDER_USERNAME = process.env.PROVIDER_USERNAME; this.config.PROVIDER_PASSWORD = process.env.PROVIDER_PASSWORD; this.config.PROVIDER_LOGIN_ENDPOINT = process.env.PROVIDER_LOGIN_ENDPOINT || "/api/provider/login"; this.config.PROVIDER_TOKEN_REFRESH_THRESHOLD = parseInt(process.env.PROVIDER_TOKEN_REFRESH_THRESHOLD) || 300; // Token Management this.config.TOKEN_CACHE_DURATION = parseInt(process.env.TOKEN_CACHE_DURATION) || 3600; this.config.TOKEN_REFRESH_BUFFER = parseInt(process.env.TOKEN_REFRESH_BUFFER) || 300; this.config.MAX_CONCURRENT_REQUESTS = parseInt(process.env.MAX_CONCURRENT_REQUESTS) || 10; // Logging Configuration this.config.LOG_LEVEL = process.env.LOG_LEVEL || "info"; this.config.LOG_FILE_PATH = process.env.LOG_FILE_PATH || "./logs/mcp-server.log"; this.config.LOG_MAX_SIZE = process.env.LOG_MAX_SIZE || "10m"; this.config.LOG_MAX_FILES = process.env.LOG_MAX_FILES || "5"; this.config.LOG_DATE_PATTERN = process.env.LOG_DATE_PATTERN || "YYYY-MM-DD"; this.config.ENABLE_REQUEST_LOGGING = process.env.ENABLE_REQUEST_LOGGING || "true"; this.config.MASK_SENSITIVE_DATA = process.env.MASK_SENSITIVE_DATA || "true"; // Error Handling this.config.ENABLE_DETAILED_ERRORS = process.env.ENABLE_DETAILED_ERRORS === "true"; this.config.HIPAA_COMPLIANCE_MODE = process.env.HIPAA_COMPLIANCE_MODE !== "false"; this.config.ERROR_REPORTING_LEVEL = process.env.ERROR_REPORTING_LEVEL || "production"; // Rate Limiting this.config.RATE_LIMIT_ENABLED = process.env.RATE_LIMIT_ENABLED !== "false"; this.config.RATE_LIMIT_WINDOW = parseInt(process.env.RATE_LIMIT_WINDOW) || 60000; this.config.RATE_LIMIT_MAX_REQUESTS = parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100; // Health Check this.config.HEALTH_CHECK_ENABLED = process.env.HEALTH_CHECK_ENABLED !== "false"; this.config.HEALTH_CHECK_INTERVAL = parseInt(process.env.HEALTH_CHECK_INTERVAL) || 30000; this.config.HEALTH_CHECK_ENDPOINT = process.env.HEALTH_CHECK_ENDPOINT || "/health"; // Development Settings this.config.NODE_ENV = process.env.NODE_ENV || "production"; this.config.DEBUG_MODE = process.env.DEBUG_MODE === "true"; this.config.ENABLE_CORS = process.env.ENABLE_CORS !== "false"; this.config.CORS_ORIGINS = process.env.CORS_ORIGINS || "*"; } /** * Define validation rules for configuration * @private * @returns {Object} Validation rules */ _defineValidationRules() { return { required: ["LARAVEL_API_BASE_URL"], optional: ["MCP_SERVER_NAME", "MCP_SERVER_VERSION"], authCredentials: [ "ADMIN_USERNAME", "ADMIN_PASSWORD", "AGENT_USERNAME", "AGENT_PASSWORD", "PATIENT_USERNAME", "PATIENT_PASSWORD", "PRACTITIONER_USERNAME", "PRACTITIONER_PASSWORD", "AFFILIATE_USERNAME", "AFFILIATE_PASSWORD", "PARTNER_USERNAME", "PARTNER_PASSWORD", "NETWORK_USERNAME", "NETWORK_PASSWORD", "DOCTOR_USERNAME", "DOCTOR_PASSWORD", "PROVIDER_USERNAME", "PROVIDER_PASSWORD", ], numeric: [ "LARAVEL_API_TIMEOUT", "LARAVEL_API_RETRY_ATTEMPTS", "LARAVEL_API_RETRY_DELAY", "MCP_SERVER_PORT", "TOKEN_CACHE_DURATION", "TOKEN_REFRESH_BUFFER", "MAX_CONCURRENT_REQUESTS", ], urls: ["LARAVEL_API_BASE_URL"], }; } /** * Validate configuration * @private * @throws {ConfigurationError} If validation fails */ _validateConfiguration() { const errors = []; const warnings = []; // Check required fields this.validationRules.required.forEach((key) => { if (!this.config[key]) { errors.push(`Required configuration missing: ${key}`); } }); // Validate URLs this.validationRules.urls.forEach((key) => { if (this.config[key]) { try { new URL(this.config[key]); } catch (error) { errors.push(`Invalid URL format for ${key}: ${this.config[key]}`); } } }); // Validate numeric values this.validationRules.numeric.forEach((key) => { if ( this.config[key] !== undefined && (isNaN(this.config[key]) || this.config[key] < 0) ) { errors.push(`Invalid numeric value for ${key}: ${this.config[key]}`); } }); // Check authentication credentials (warnings only) const authTypes = [ "ADMIN", "AGENT", "PATIENT", "PRACTITIONER", "AFFILIATE", "PARTNER", "NETWORK", "DOCTOR", "PROVIDER", ]; authTypes.forEach((authType) => { const usernameKey = `${authType}_USERNAME`; const passwordKey = `${authType}_PASSWORD`; if (!this.config[usernameKey] || !this.config[passwordKey]) { warnings.push(`${authType} authentication credentials not configured`); } }); // Security warnings if ( this.config.ENABLE_REQUEST_LOGGING === "true" && this.config.MASK_SENSITIVE_DATA !== "true" ) { warnings.push( "Request logging enabled without sensitive data masking - potential security risk" ); } if (this.config.HIPAA_COMPLIANCE_MODE !== true) { warnings.push( "HIPAA compliance mode disabled - ensure this is intentional for healthcare data" ); } if ( this.config.DEBUG_MODE === true && this.config.NODE_ENV === "production" ) { warnings.push("Debug mode enabled in production environment"); } // Log warnings warnings.forEach((warning) => logger.warn(`Configuration warning: ${warning}`) ); // Throw error if validation fails if (errors.length > 0) { const errorMessage = `Configuration validation failed:\n${errors.join( "\n" )}`; logger.error(errorMessage); throw new ConfigurationError(errorMessage, "VALIDATION_FAILED", { errors, warnings, }); } if (warnings.length > 0) { logger.info(`Configuration loaded with ${warnings.length} warnings`); } } /** * Get configuration value * @param {string} key - Configuration key * @param {*} defaultValue - Default value if key not found * @returns {*} Configuration value */ get(key, defaultValue = undefined) { return this.config[key] !== undefined ? this.config[key] : defaultValue; } /** * Set configuration value * @param {string} key - Configuration key * @param {*} value - Configuration value */ set(key, value) { this.config[key] = value; } /** * Get all configuration * @param {boolean} includeSensitive - Include sensitive values * @returns {Object} Configuration object */ getAll(includeSensitive = false) { if (includeSensitive) { return { ...this.config }; } // Mask sensitive values const masked = { ...this.config }; const sensitiveKeys = this.validationRules.authCredentials.filter((key) => key.includes("PASSWORD") ); sensitiveKeys.forEach((key) => { if (masked[key]) { masked[key] = "[MASKED]"; } }); return masked; } /** * Check if configuration is valid * @returns {boolean} True if configuration is valid */ isValid() { try { this._validateConfiguration(); return true; } catch (error) { return false; } } /** * Get configuration summary * @returns {Object} Configuration summary */ getSummary() { const authTypesConfigured = []; const authTypes = [ "ADMIN", "AGENT", "PATIENT", "PRACTITIONER", "AFFILIATE", "PARTNER", "NETWORK", "DOCTOR", "PROVIDER", ]; authTypes.forEach((authType) => { const usernameKey = `${authType}_USERNAME`; const passwordKey = `${authType}_PASSWORD`; if (this.config[usernameKey] && this.config[passwordKey]) { authTypesConfigured.push(authType.toLowerCase()); } }); return { serverName: this.config.MCP_SERVER_NAME, serverVersion: this.config.MCP_SERVER_VERSION, apiBaseUrl: this.config.LARAVEL_API_BASE_URL, authTypesConfigured, hipaaCompliance: this.config.HIPAA_COMPLIANCE_MODE, logLevel: this.config.LOG_LEVEL, environment: this.config.NODE_ENV, debugMode: this.config.DEBUG_MODE, }; } /** * Reload configuration from environment */ reload() { logger.info("Reloading configuration..."); this._loadConfiguration(); this._validateConfiguration(); logger.info("Configuration reloaded successfully"); } }