mcghidra/src/ghydramcp/config.py
Ryan Malloy 290252c0db
Some checks are pending
Build Ghidra Plugin / build (push) Waiting to run
feat: Add feedback collection via fastmcp-feedback
Allows AI clients to submit feedback about tool quality, report issues,
and track statistics. Persists to ~/.ghydramcp/feedback.db (SQLite).

- Add fastmcp-feedback dependency
- Add feedback_enabled / feedback_db_path config fields
- Wire add_feedback_tools() into create_server() with graceful fallback
- Show feedback path in startup banner

Disable with GHYDRA_FEEDBACK=false
2026-01-30 10:09:26 -07:00

130 lines
3.7 KiB
Python

"""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