"""Configuration management for GhydraMCP. Handles environment variables, default settings, and runtime configuration. """ import os from dataclasses import dataclass, field from pathlib import Path from typing import Optional @dataclass class DockerConfig: """Docker-specific configuration.""" # Docker image settings image_name: str = "ghydramcp" image_tag: str = field(default_factory=lambda: os.environ.get("GHYDRAMCP_VERSION", "latest")) # Default container settings default_port: int = field(default_factory=lambda: int(os.environ.get("GHYDRA_PORT", "8192"))) default_memory: str = field(default_factory=lambda: os.environ.get("GHYDRA_MAXMEM", "2G")) # Project directory (for building) project_dir: Optional[Path] = None # Auto-start settings auto_start_enabled: bool = field(default_factory=lambda: os.environ.get("GHYDRA_DOCKER_AUTO", "false").lower() == "true") auto_start_wait: bool = True auto_start_timeout: float = 300.0 # Docker configuration instance _docker_config: Optional[DockerConfig] = None def get_docker_config() -> DockerConfig: """Get the Docker configuration instance.""" global _docker_config if _docker_config is None: _docker_config = DockerConfig() return _docker_config def set_docker_config(config: DockerConfig) -> None: """Set the Docker configuration instance.""" global _docker_config _docker_config = config @dataclass class GhydraConfig: """Configuration for GhydraMCP server.""" # Ghidra connection settings ghidra_host: str = field(default_factory=lambda: os.environ.get("GHIDRA_HOST", "localhost")) default_port: Optional[int] = None # Port scanning ranges for instance discovery quick_discovery_range: range = field(default_factory=lambda: range(18489, 18499)) full_discovery_range: range = field(default_factory=lambda: range(18400, 18600)) # HTTP client settings request_timeout: float = 30.0 discovery_timeout: float = 0.5 # Pagination defaults default_page_size: int = 50 max_page_size: int = 500 # Cursor management cursor_ttl_seconds: int = 300 # 5 minutes max_cursors_per_session: int = 100 # Response size limits (for return_all guard) max_response_tokens: int = 8000 # Hard budget — guard triggers above this large_response_threshold: int = 4000 # Warn above this in normal pagination # Expected API version expected_api_version: int = 2 # Feedback collection feedback_enabled: bool = field( default_factory=lambda: os.environ.get("GHYDRA_FEEDBACK", "true").lower() == "true" ) feedback_db_path: str = field( default_factory=lambda: os.environ.get( "GHYDRA_FEEDBACK_DB", str(Path.home() / ".ghydramcp" / "feedback.db"), ) ) # Resource caps for enumeration endpoints resource_caps: dict = field(default_factory=lambda: { "functions": 1000, "strings": 500, "data": 1000, "structs": 500, "xrefs": 500, }) def __post_init__(self): """Validate configuration after initialization.""" if self.default_page_size > self.max_page_size: self.default_page_size = self.max_page_size # Global configuration instance (can be replaced for testing) _config: Optional[GhydraConfig] = None def get_config() -> GhydraConfig: """Get the global configuration instance.""" global _config if _config is None: _config = GhydraConfig() return _config def set_config(config: GhydraConfig) -> None: """Set the global configuration instance.""" global _config _config = config def reset_config() -> None: """Reset to default configuration.""" global _config _config = None