Skip to main content
Glama

Mutation Clinical Trial Matching MCP

by pickleton89
config.py10.5 kB
""" Configuration management for the Clinical Trials MCP Server. """ import logging import os from dataclasses import dataclass from typing import cast from dotenv import load_dotenv logger = logging.getLogger(__name__) # Load environment variables from .env file load_dotenv() @dataclass class APIConfig: """Configuration for API endpoints and settings.""" # Clinical Trials API Configuration clinicaltrials_api_url: str = "https://clinicaltrials.gov/api/v2/studies" clinicaltrials_timeout: int = 10 # Anthropic API Configuration anthropic_api_url: str = "https://api.anthropic.com/v1/messages" anthropic_api_key: str = "" anthropic_model: str = "claude-3-opus-20240229" anthropic_max_tokens: int = 1000 anthropic_timeout: int = 30 # Retry Configuration max_retries: int = 3 retry_initial_delay: float = 1.0 retry_backoff_factor: float = 2.0 retry_max_delay: float = 60.0 retry_jitter: bool = True # Cache Configuration cache_size: int = 100 cache_ttl: int = 3600 # Circuit Breaker Configuration (for future use) circuit_breaker_failure_threshold: int = 5 circuit_breaker_recovery_timeout: int = 60 # User Agent Configuration user_agent: str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # HTTP Connection Configuration (for async clients) http_connect_timeout: int = 5 http_read_timeout: int = 30 http_write_timeout: int = 10 http_pool_timeout: int = 5 http_max_connections: int = 100 http_max_keepalive_connections: int = 20 # Advanced Connection Pool Configuration http_keepalive_expiry: int = 60 # seconds to keep connections alive http_max_keepalive_size: int = 1024 # max bytes for keep-alive connection http_retries: int = 3 # connection-level retries # Concurrent Request Management max_concurrent_requests: int = 10 # global semaphore limit max_concurrent_per_host: int = 5 # per-host concurrent limit # Performance Optimization enable_http2: bool = ( False # enable HTTP/2 support (requires h2 package) - disabled due to 403 errors ) enable_connection_pooling: bool = True connection_pool_size: int = 100 # dedicated pool size per service # Redis Configuration (for distributed caching) redis_url: str = "redis://localhost:6379" redis_max_connections: int = 10 redis_timeout: int = 5 def load_config() -> APIConfig: """ Load configuration from environment variables. Returns: APIConfig: Configuration object with values from environment variables """ config = APIConfig() # Clinical Trials API Configuration config.clinicaltrials_api_url = os.getenv( "CLINICALTRIALS_API_URL", config.clinicaltrials_api_url ) config.clinicaltrials_timeout = int( os.getenv("CLINICALTRIALS_TIMEOUT", str(config.clinicaltrials_timeout)) ) # Anthropic API Configuration config.anthropic_api_url = os.getenv("ANTHROPIC_API_URL", config.anthropic_api_url) config.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY", config.anthropic_api_key) config.anthropic_model = os.getenv("ANTHROPIC_MODEL", config.anthropic_model) config.anthropic_max_tokens = int( os.getenv("ANTHROPIC_MAX_TOKENS", str(config.anthropic_max_tokens)) ) config.anthropic_timeout = int(os.getenv("ANTHROPIC_TIMEOUT", str(config.anthropic_timeout))) # Retry Configuration config.max_retries = int(os.getenv("MAX_RETRIES", str(config.max_retries))) config.retry_initial_delay = float( os.getenv("RETRY_INITIAL_DELAY", str(config.retry_initial_delay)) ) config.retry_backoff_factor = float( os.getenv("RETRY_BACKOFF_FACTOR", str(config.retry_backoff_factor)) ) config.retry_max_delay = float(os.getenv("RETRY_MAX_DELAY", str(config.retry_max_delay))) config.retry_jitter = os.getenv("RETRY_JITTER", str(config.retry_jitter)).lower() in ( "true", "1", "yes", "on", ) # Cache Configuration config.cache_size = int(os.getenv("CACHE_SIZE", str(config.cache_size))) config.cache_ttl = int(os.getenv("CACHE_TTL", str(config.cache_ttl))) # Circuit Breaker Configuration config.circuit_breaker_failure_threshold = int( os.getenv( "CIRCUIT_BREAKER_FAILURE_THRESHOLD", str(config.circuit_breaker_failure_threshold) ) ) config.circuit_breaker_recovery_timeout = int( os.getenv("CIRCUIT_BREAKER_RECOVERY_TIMEOUT", str(config.circuit_breaker_recovery_timeout)) ) # User Agent Configuration config.user_agent = os.getenv("USER_AGENT", config.user_agent) # HTTP Connection Configuration config.http_connect_timeout = int( os.getenv("HTTP_CONNECT_TIMEOUT", str(config.http_connect_timeout)) ) config.http_read_timeout = int(os.getenv("HTTP_READ_TIMEOUT", str(config.http_read_timeout))) config.http_write_timeout = int(os.getenv("HTTP_WRITE_TIMEOUT", str(config.http_write_timeout))) config.http_pool_timeout = int(os.getenv("HTTP_POOL_TIMEOUT", str(config.http_pool_timeout))) config.http_max_connections = int( os.getenv("HTTP_MAX_CONNECTIONS", str(config.http_max_connections)) ) config.http_max_keepalive_connections = int( os.getenv("HTTP_MAX_KEEPALIVE_CONNECTIONS", str(config.http_max_keepalive_connections)) ) # Redis Configuration config.redis_url = os.getenv("REDIS_URL", config.redis_url) config.redis_max_connections = int( os.getenv("REDIS_MAX_CONNECTIONS", str(config.redis_max_connections)) ) config.redis_timeout = int(os.getenv("REDIS_TIMEOUT", str(config.redis_timeout))) return config def validate_config(config: APIConfig) -> list[str]: """ Validate configuration and return list of validation errors. Args: config: Configuration object to validate Returns: List of validation error messages (empty if valid) """ errors = [] # Required fields if not config.anthropic_api_key: errors.append("ANTHROPIC_API_KEY is required") # URL validation if not config.clinicaltrials_api_url.startswith(("http://", "https://")): errors.append("CLINICALTRIALS_API_URL must be a valid URL") if not config.anthropic_api_url.startswith(("http://", "https://")): errors.append("ANTHROPIC_API_URL must be a valid URL") # Numeric validation if config.clinicaltrials_timeout <= 0: errors.append("CLINICALTRIALS_TIMEOUT must be positive") if config.anthropic_timeout <= 0: errors.append("ANTHROPIC_TIMEOUT must be positive") if config.anthropic_max_tokens <= 0: errors.append("ANTHROPIC_MAX_TOKENS must be positive") if config.max_retries < 0: errors.append("MAX_RETRIES must be non-negative") if config.retry_initial_delay <= 0: errors.append("RETRY_INITIAL_DELAY must be positive") if config.retry_backoff_factor <= 0: errors.append("RETRY_BACKOFF_FACTOR must be positive") if config.retry_max_delay <= 0: errors.append("RETRY_MAX_DELAY must be positive") if config.cache_size <= 0: errors.append("CACHE_SIZE must be positive") if config.cache_ttl <= 0: errors.append("CACHE_TTL must be positive") if config.circuit_breaker_failure_threshold <= 0: errors.append("CIRCUIT_BREAKER_FAILURE_THRESHOLD must be positive") if config.circuit_breaker_recovery_timeout <= 0: errors.append("CIRCUIT_BREAKER_RECOVERY_TIMEOUT must be positive") # HTTP Connection Configuration validation if config.http_connect_timeout <= 0: errors.append("HTTP_CONNECT_TIMEOUT must be positive") if config.http_read_timeout <= 0: errors.append("HTTP_READ_TIMEOUT must be positive") if config.http_write_timeout <= 0: errors.append("HTTP_WRITE_TIMEOUT must be positive") if config.http_pool_timeout <= 0: errors.append("HTTP_POOL_TIMEOUT must be positive") if config.http_max_connections <= 0: errors.append("HTTP_MAX_CONNECTIONS must be positive") if config.http_max_keepalive_connections <= 0: errors.append("HTTP_MAX_KEEPALIVE_CONNECTIONS must be positive") if config.http_max_keepalive_connections > config.http_max_connections: errors.append("HTTP_MAX_KEEPALIVE_CONNECTIONS cannot be greater than HTTP_MAX_CONNECTIONS") # Redis Configuration validation if not config.redis_url.startswith(("redis://", "rediss://")): errors.append("REDIS_URL must be a valid Redis URL (redis:// or rediss://)") if config.redis_max_connections <= 0: errors.append("REDIS_MAX_CONNECTIONS must be positive") if config.redis_timeout <= 0: errors.append("REDIS_TIMEOUT must be positive") # Logical validation if config.retry_initial_delay > config.retry_max_delay: errors.append("RETRY_INITIAL_DELAY cannot be greater than RETRY_MAX_DELAY") return errors def get_config() -> APIConfig: """ Get validated configuration. Returns: APIConfig: Validated configuration object Raises: ValueError: If configuration is invalid """ config = load_config() errors = validate_config(config) if errors: error_msg = "Configuration validation failed:\n" + "\n".join( f" - {error}" for error in errors ) logger.error(error_msg) raise ValueError(error_msg) logger.info( "Configuration loaded successfully", extra={ "clinicaltrials_api_url": config.clinicaltrials_api_url, "anthropic_api_url": config.anthropic_api_url, "anthropic_model": config.anthropic_model, "max_retries": config.max_retries, "cache_size": config.cache_size, "action": "config_loaded", }, ) return config # Global configuration instance _config: APIConfig | None = None def get_global_config() -> APIConfig: """ Get the global configuration instance (lazy-loaded). Returns: APIConfig: Global configuration instance """ global _config if _config is None: _config = get_config() return cast(APIConfig, _config) def reset_global_config() -> None: """ Reset the global configuration instance (useful for testing). """ global _config _config = None

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/pickleton89/mutation-clinical-trial-matching-mcp'

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