Skip to main content
Glama
config_manager.py12.9 kB
#!/usr/bin/env python """ Configuration Manager Handles all configuration from MCP environment variables """ import os import json import subprocess import platform import socket import time from typing import Optional, Dict, Any, List from dataclasses import dataclass, field from loguru import logger @dataclass class SearchEngineConfig: """Search engine configuration""" api_key: Optional[str] = None secret_key: Optional[str] = None cse_id: Optional[str] = None enabled: bool = False @dataclass class ServerConfig: """Main server configuration""" # Logging configuration log_level: str = "INFO" log_file: str = "search_fusion.log" log_rotation: str = "100 MB" # Proxy configuration http_proxy: Optional[str] = None https_proxy: Optional[str] = None no_proxy: Optional[str] = None # Rate limiting configuration default_cooldown: int = 300 max_error_count: int = 3 # Search engine configurations google: SearchEngineConfig = field(default_factory=SearchEngineConfig) serper: SearchEngineConfig = field(default_factory=SearchEngineConfig) # Same priority as Google jina: SearchEngineConfig = field(default_factory=SearchEngineConfig) bing: SearchEngineConfig = field(default_factory=SearchEngineConfig) baidu: SearchEngineConfig = field(default_factory=SearchEngineConfig) exa: SearchEngineConfig = field(default_factory=SearchEngineConfig) duckduckgo: SearchEngineConfig = field(default_factory=lambda: SearchEngineConfig(enabled=True)) class ConfigManager: """Configuration manager - handles all configuration sources""" def __init__(self, config_path: Optional[str] = None): """Initialize configuration manager Args: config_path: Legacy parameter, now deprecated, using pure MCP environment variable configuration """ if config_path: logger.warning("⚠️ config_path parameter is deprecated, now using pure MCP environment variable configuration") self.config = ServerConfig() self._load_config() def _load_config(self): """Load configuration from MCP environment variables only""" # Load from environment variables and MCP configuration only self._load_from_env() # Auto-detect and setup proxy (inspired by concurrent-browser-mcp implementation) self._auto_detect_proxy() # Setup proxy environment variables self._setup_proxy() logger.info("✅ Configuration loaded successfully (MCP environment variable mode)") self._log_config_summary() def _load_from_env(self): """Load configuration from environment variables and MCP configuration""" # Basic configuration self.config.log_level = os.getenv('LOG_LEVEL', self.config.log_level) self.config.log_file = os.getenv('LOG_FILE', self.config.log_file) self.config.log_rotation = os.getenv('LOG_ROTATION', self.config.log_rotation) # Proxy configuration - prioritize environment variables self.config.http_proxy = os.getenv('HTTP_PROXY') or os.getenv('http_proxy') self.config.https_proxy = os.getenv('HTTPS_PROXY') or os.getenv('https_proxy') self.config.no_proxy = os.getenv('NO_PROXY') or os.getenv('no_proxy', self.config.no_proxy) # MCP format API key configuration # Supports format: SEARCH_ENGINE_API_KEY, SEARCH_ENGINE_SECRET_KEY etc # Google configuration google_api_key = os.getenv('GOOGLE_API_KEY') or os.getenv('GOOGLE_SEARCH_API_KEY') google_cse_id = os.getenv('GOOGLE_CSE_ID') or os.getenv('GOOGLE_SEARCH_CSE_ID') if google_api_key: self.config.google = SearchEngineConfig( api_key=google_api_key, cse_id=google_cse_id, enabled=True ) # Serper configuration serper_api_key = os.getenv('SERPER_API_KEY') or os.getenv('SERPER_SEARCH_API_KEY') if serper_api_key: self.config.serper = SearchEngineConfig( api_key=serper_api_key, enabled=True ) # Bing configuration bing_api_key = os.getenv('BING_API_KEY') or os.getenv('BING_SEARCH_API_KEY') if bing_api_key: self.config.bing = SearchEngineConfig( api_key=bing_api_key, enabled=True ) # Baidu configuration baidu_api_key = os.getenv('BAIDU_API_KEY') or os.getenv('BAIDU_SEARCH_API_KEY') baidu_secret_key = os.getenv('BAIDU_SECRET_KEY') or os.getenv('BAIDU_SEARCH_SECRET_KEY') if baidu_api_key and baidu_secret_key: self.config.baidu = SearchEngineConfig( api_key=baidu_api_key, secret_key=baidu_secret_key, enabled=True ) # Jina configuration jina_api_key = os.getenv('JINA_API_KEY') or os.getenv('JINA_SEARCH_API_KEY') if jina_api_key: self.config.jina = SearchEngineConfig( api_key=jina_api_key, enabled=True ) # Exa configuration exa_api_key = os.getenv('EXA_API_KEY') or os.getenv('EXA_SEARCH_API_KEY') if exa_api_key: self.config.exa = SearchEngineConfig( api_key=exa_api_key, enabled=True ) def _auto_detect_proxy(self): """Auto-detect system proxy settings - inspired by concurrent-browser-mcp implementation""" try: # If environment variables already have proxy settings, skip auto-detection if self.config.http_proxy or self.config.https_proxy: logger.info("🌐 Using proxy from environment variables") return # Follow concurrent-browser-mcp detection order detected_proxy = self._detect_local_proxy() if detected_proxy: self.config.http_proxy = detected_proxy self.config.https_proxy = detected_proxy logger.info(f"🔍 Auto-detected proxy: {detected_proxy}") else: logger.info("🌐 No system proxy detected, using direct connection") except Exception as e: logger.warning(f"⚠️ Failed to auto-detect proxy: {e}") def _detect_local_proxy(self) -> Optional[str]: """Detect local proxy - fully inspired by concurrent-browser-mcp implementation""" # 1. Check environment variables env_proxy = self._get_proxy_from_env() if env_proxy: logger.info(f"Proxy detected from environment variables: {env_proxy}") return env_proxy # 2. Check common proxy ports - this is the key feature! common_ports = [7890, 1087, 8080, 3128, 8888, 10809, 20171] for port in common_ports: proxy_url = f"http://127.0.0.1:{port}" if self._test_proxy_connection(proxy_url): logger.info(f"Local proxy port detected: {port}") return proxy_url # 3. Try to detect system proxy settings (macOS) if platform.system().lower() == 'darwin': system_proxy = self._get_macos_system_proxy() if system_proxy: logger.info(f"System proxy detected: {system_proxy}") return system_proxy return None def _get_proxy_from_env(self) -> Optional[str]: """Get proxy from environment variables""" http_proxy = os.getenv('HTTP_PROXY') or os.getenv('http_proxy') https_proxy = os.getenv('HTTPS_PROXY') or os.getenv('https_proxy') all_proxy = os.getenv('ALL_PROXY') or os.getenv('all_proxy') return http_proxy or https_proxy or all_proxy def _test_proxy_connection(self, proxy_url: str) -> bool: """Test proxy connection - fully inspired by concurrent-browser-mcp implementation""" try: # Simple port detection to avoid complex network requests from urllib.parse import urlparse parsed = urlparse(proxy_url) # Create socket connection test sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(3) # 3-second timeout, consistent with concurrent-browser-mcp try: result = sock.connect_ex((parsed.hostname, parsed.port)) return result == 0 # 0 means connection successful finally: sock.close() except Exception: return False def _get_macos_system_proxy(self) -> Optional[str]: """Get macOS system proxy settings - inspired by concurrent-browser-mcp implementation""" try: # Use the same command as concurrent-browser-mcp result = subprocess.run([ 'networksetup', '-getwebproxy', 'Wi-Fi' ], capture_output=True, text=True, timeout=5) if result.returncode != 0: # Try Ethernet result = subprocess.run([ 'networksetup', '-getwebproxy', 'Ethernet' ], capture_output=True, text=True, timeout=5) if result.returncode == 0: lines = result.stdout.strip().split('\n') enabled = any('Enabled: Yes' in line for line in lines) if enabled: server = None port = None for line in lines: if line.startswith('Server:'): server = line.split(':', 1)[1].strip() elif line.startswith('Port:'): port = line.split(':', 1)[1].strip() if server and port: return f"http://{server}:{port}" except Exception as e: logger.debug(f"macOS proxy detection failed: {e}") return None def _setup_proxy(self): """Setup proxy environment variables""" if self.config.http_proxy: os.environ['http_proxy'] = self.config.http_proxy os.environ['HTTP_PROXY'] = self.config.http_proxy if self.config.https_proxy: os.environ['https_proxy'] = self.config.https_proxy os.environ['HTTPS_PROXY'] = self.config.https_proxy if self.config.no_proxy: os.environ['no_proxy'] = self.config.no_proxy os.environ['NO_PROXY'] = self.config.no_proxy def _log_config_summary(self): """Log configuration summary""" enabled_engines = [] for engine_name in ['google', 'serper', 'jina', 'duckduckgo', 'exa', 'bing', 'baidu']: if self.is_engine_enabled(engine_name): config = self.get_engine_config(engine_name) if config.api_key: enabled_engines.append(f"{engine_name.title()} (with API key)") else: enabled_engines.append(f"{engine_name.title()} (free)") logger.info(f"📊 Enabled search engines: {', '.join(enabled_engines) if enabled_engines else 'None'}") if self.config.http_proxy or self.config.https_proxy: logger.info(f"🌐 Proxy configuration: HTTP={self.config.http_proxy}, HTTPS={self.config.https_proxy}") logger.info(f"📝 Logging: Level={self.config.log_level}, File={self.config.log_file}") def get_engine_config(self, engine_name: str) -> SearchEngineConfig: """Get configuration for specified search engine""" return getattr(self.config, engine_name.lower(), SearchEngineConfig(enabled=False)) def is_engine_enabled(self, engine_name: str) -> bool: """Check if search engine is enabled and has valid configuration""" engine_config = self.get_engine_config(engine_name) # DuckDuckGo doesn't require API key if engine_name.lower() == 'duckduckgo': return True # All other engines (including Jina) require API keys return engine_config.enabled and bool(engine_config.api_key) def get_proxy_config(self) -> Dict[str, Optional[str]]: """Get proxy configuration""" return { "http://": self.config.http_proxy, "https://": self.config.https_proxy, } if self.config.http_proxy or self.config.https_proxy else None

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/sailaoda/search-fusion-mcp'

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