Skip to main content
Glama

Malaysia Prayer Time MCP Server

config.py8.15 kB
""" Configuration management for the Malaysia Prayer Time MCP Server. This module provides a configuration system with: - Environment variable overrides - YAML/JSON file loading - Value validation - Configuration locking - Default values - Type safety using dataclasses Usage: # Load from environment variables config.load_from_env() # Load from file config.load_from_file('config.yaml') # Manual configuration config.configure({ 'cache': {'ttl': 7200}, 'http': {'timeout': 5} }) """ import os import json import yaml from pathlib import Path from threading import Lock from typing import Dict, Any, Optional, ClassVar, Final from dataclasses import dataclass, field # Constants DEFAULT_CONFIG_PATHS: Final[list[str]] = [ "./config.yaml", "./config.yml", "./config.json", ] # No required environment variables by default, can be modified if needed REQUIRED_ENV_VARS: Final[list[str]] = [] @dataclass class CacheConfig: """ Cache configuration settings. Attributes: type: Cache backend type ('memory' or 'redis') ttl: Default cache TTL in seconds max_size: Maximum number of items in memory cache redis_url: Redis connection URL (only used when type='redis') """ type: str = field(default="memory") ttl: int = field(default=3600) max_size: int = field(default=1000) redis_url: Optional[str] = field(default=None) def __post_init__(self) -> None: """Validate configuration values.""" if self.type not in ["memory", "redis"]: raise ValueError("Cache type must be 'memory' or 'redis'") if self.ttl < 0: raise ValueError("Cache TTL must be non-negative") if self.max_size < 1: raise ValueError("Cache max_size must be positive") if self.type == "redis" and not self.redis_url: raise ValueError("Redis URL required when cache type is 'redis'") @dataclass class HTTPConfig: """ HTTP client configuration settings. Attributes: timeout: Request timeout in seconds max_retries: Maximum number of retry attempts pool_connections: Maximum number of connections in pool base_url: Base URL for API requests verify_ssl: Whether to verify SSL certificates """ timeout: int = field(default=10) max_retries: int = field(default=3) pool_connections: int = field(default=10) base_url: str = field(default="https://api.waktusolat.app") verify_ssl: bool = field(default=True) def __post_init__(self) -> None: """Validate configuration values.""" if self.timeout < 1: raise ValueError("HTTP timeout must be positive") if self.max_retries < 0: raise ValueError("Max retries must be non-negative") if self.pool_connections < 1: raise ValueError("Pool connections must be positive") if not self.base_url.startswith(("http://", "https://")): raise ValueError("Base URL must start with http:// or https://") @dataclass class Config: """ Main configuration container. This class manages all configuration settings and provides methods for loading from different sources. Attributes: cache: Cache-related settings http: HTTP client settings """ cache: CacheConfig = field(default_factory=CacheConfig) http: HTTPConfig = field(default_factory=HTTPConfig) _lock: ClassVar[Lock] = Lock() _initialized: bool = field(default=False, init=False) def __post_init__(self) -> None: """Mark as initialized after creation.""" self._initialized = True @classmethod def default(cls) -> "Config": """Create a default configuration instance.""" return cls(cache=CacheConfig(), http=HTTPConfig()) @classmethod def from_dict(cls, config_dict: Dict[str, Any]) -> "Config": """ Create a configuration instance from a dictionary. Args: config_dict: Configuration dictionary Returns: New Config instance Raises: ValueError: If configuration values are invalid """ cache_config = CacheConfig(**config_dict.get("cache", {})) http_config = HTTPConfig(**config_dict.get("http", {})) return cls(cache=cache_config, http=http_config) def load_from_env(self) -> None: """ Load configuration from environment variables. Environment variables should be prefixed with WAKTU_SOLAT_ Example: WAKTU_SOLAT_CACHE_TTL=7200 """ with self._lock: # Validate required environment variables first missing_vars = [var for var in REQUIRED_ENV_VARS if not os.getenv(var)] if missing_vars: raise EnvironmentError( f"Missing required environment variables: {', '.join(missing_vars)}" ) # Cache config self.cache.ttl = int(os.getenv("WAKTU_SOLAT_CACHE_TTL", self.cache.ttl)) self.cache.max_size = int( os.getenv("WAKTU_SOLAT_CACHE_MAX_SIZE", self.cache.max_size) ) self.cache.type = os.getenv("WAKTU_SOLAT_CACHE_TYPE", self.cache.type) self.cache.redis_url = os.getenv( "WAKTU_SOLAT_REDIS_URL", self.cache.redis_url ) # HTTP config self.http.timeout = int( os.getenv("WAKTU_SOLAT_HTTP_TIMEOUT", self.http.timeout) ) self.http.max_retries = int( os.getenv("WAKTU_SOLAT_HTTP_MAX_RETRIES", self.http.max_retries) ) self.http.pool_connections = int( os.getenv( "WAKTU_SOLAT_HTTP_POOL_CONNECTIONS", self.http.pool_connections ) ) self.http.base_url = os.getenv( "WAKTU_SOLAT_HTTP_BASE_URL", self.http.base_url ) self.http.verify_ssl = os.getenv( "WAKTU_SOLAT_HTTP_VERIFY_SSL", str(self.http.verify_ssl) ).lower() in ("true", "1", "yes") def load_from_file(self, path: Optional[str] = None) -> None: """ Load configuration from a YAML or JSON file. Args: path: Path to config file. If None, searches default locations. Raises: FileNotFoundError: If no config file found ValueError: If config file format is invalid """ if path is None: for default_path in DEFAULT_CONFIG_PATHS: if Path(default_path).exists(): path = default_path break else: raise FileNotFoundError("No configuration file found") with Path(path).open() as f: if path.endswith(".json"): config_dict = json.load(f) else: # YAML config_dict = yaml.safe_load(f) with self._lock: self._update_from_dict(config_dict) def _update_from_dict(self, config_dict: Dict[str, Any]) -> None: """Update configuration from dictionary with validation.""" if "cache" in config_dict: cache_config = CacheConfig(**config_dict["cache"]) self.cache = cache_config if "http" in config_dict: http_config = HTTPConfig(**config_dict["http"]) self.http = http_config # Global configuration instance config = Config.default() def configure(config_dict: Dict[str, Any]) -> None: """ Update the global configuration from a dictionary. Args: config_dict: Configuration dictionary Raises: ValueError: If configuration values are invalid RuntimeError: If called after server has started """ global config if config._initialized: raise RuntimeError( "Configuration cannot be modified after initialization. " "Set all configuration values before starting the server." ) config = Config.from_dict(config_dict)

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/amanasmuei/mcp-server-malaysia-prayer-time'

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