""" Centralized Configuration Manager """ import os import json from typing import Dict, Any, Optional from pathlib import Path try: from dotenv import load_dotenv DOTENV_AVAILABLE = True except ImportError: DOTENV_AVAILABLE = False class ConfigManager: """Centralized configuration manager for MCP components""" def __init__(self, config_file: Optional[str] = None, env_file: Optional[str] = None): self._config_file = config_file or "config.json" self._env_file = env_file or ".env" self._config: Dict[str, Any] = {} self._loaded = False async def load_config(self) -> Dict[str, Any]: """Load configuration from all sources""" if self._loaded: return self._config # Load environment variables await self._load_env_vars() # Load JSON configuration file await self._load_json_config() # Merge configurations self._config = await self._merge_configs() self._loaded = True return self._config async def _load_env_vars(self) -> None: """Load environment variables""" if DOTENV_AVAILABLE and Path(self._env_file).exists(): load_dotenv(self._env_file) async def _load_json_config(self) -> Dict[str, Any]: """Load JSON configuration file""" config_path = Path(self._config_file) if config_path.exists(): with open(config_path, 'r') as f: return json.load(f) return {} async def _merge_configs(self) -> Dict[str, Any]: """Merge all configuration sources""" merged = {} # Start with JSON config as base json_config = await self._load_json_config() merged.update(json_config) # Override with environment variables env_config = await self._get_env_config() self._deep_update(merged, env_config) return merged async def _get_env_config(self) -> Dict[str, Any]: """Get configuration from environment variables""" env_config = {} # Server configuration if os.getenv("MCP_SERVER_NAME"): env_config.setdefault("server", {})["name"] = os.getenv("MCP_SERVER_NAME") if os.getenv("MCP_SERVER_HOST"): env_config.setdefault("server", {})["host"] = os.getenv("MCP_SERVER_HOST") if os.getenv("MCP_SERVER_PORT"): env_config.setdefault("server", {})["port"] = int(os.getenv("MCP_SERVER_PORT")) if os.getenv("MCP_TRANSPORT"): env_config.setdefault("server", {})["transport"] = os.getenv("MCP_TRANSPORT") # Client configuration if os.getenv("MCP_AI_PROVIDER"): env_config.setdefault("client", {})["provider"] = os.getenv("MCP_AI_PROVIDER") if os.getenv("MCP_AI_MODEL"): env_config.setdefault("client", {})["model"] = os.getenv("MCP_AI_MODEL") # API keys for provider in ["OPENAI", "CLAUDE", "GROK"]: api_key = os.getenv(f"{provider}_API_KEY") if api_key: env_config.setdefault("api_keys", {})[provider.lower()] = api_key return env_config def _deep_update(self, base_dict: Dict[str, Any], update_dict: Dict[str, Any]) -> None: """Deep update dictionary""" for key, value in update_dict.items(): if isinstance(value, dict) and key in base_dict and isinstance(base_dict[key], dict): self._deep_update(base_dict[key], value) else: base_dict[key] = value async def get_server_config(self) -> Dict[str, Any]: """Get server-specific configuration""" config = await self.load_config() return config.get("server", {}) async def get_client_config(self) -> Dict[str, Any]: """Get client-specific configuration""" config = await self.load_config() return config.get("client", {}) async def get_api_key(self, provider: str) -> Optional[str]: """Get API key for a specific provider""" config = await self.load_config() api_keys = config.get("api_keys", {}) return api_keys.get(provider.lower()) async def save_config(self, config: Dict[str, Any]) -> None: """Save configuration to file""" with open(self._config_file, 'w') as f: json.dump(config, f, indent=2) async def update_config(self, updates: Dict[str, Any]) -> None: """Update configuration and save""" config = await self.load_config() self._deep_update(config, updates) await self.save_config(config) self._loaded = False # Force reload on next access