From 458d4fb35bdde29fdaeb8ec772c3edae2be1b64f Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sat, 31 Jan 2026 20:20:30 -0700 Subject: [PATCH] fix: Eliminate blocking HTTP call from instances_use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit instances_use previously called register_instance which made a blocking safe_get (30s timeout) to validate the connection. If the Ghidra server was slow or unresponsive, this could hang the MCP tool call indefinitely from the client's perspective. Now instances_use creates a lazy stub entry and sets the port immediately — pure in-memory, no network I/O. The first actual tool call validates the connection naturally. Also fix background discovery thread using request_timeout (30s) instead of discovery_timeout (0.5s) per port — worst case went from 300s to 5s per scan cycle. --- src/ghydramcp/mixins/instances.py | 19 +++++++++++------ src/ghydramcp/server.py | 35 ++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/ghydramcp/mixins/instances.py b/src/ghydramcp/mixins/instances.py index 51a3df2..4b93269 100644 --- a/src/ghydramcp/mixins/instances.py +++ b/src/ghydramcp/mixins/instances.py @@ -166,13 +166,20 @@ class InstancesMixin(GhydraMixinBase): Returns: Confirmation message with instance details """ + # Register lazily without blocking HTTP calls. + # If the instance is unknown, create a stub entry — the first + # actual tool call (functions_list, etc.) will validate the + # connection and fail fast with a clear error if unreachable. with self._instances_lock: - needs_register = port not in self._instances - - if needs_register: - result = self.register_instance(port) - if "Failed" in result or "Error" in result: - return result + if port not in self._instances: + config = get_config() + self._instances[port] = { + "url": f"http://{config.ghidra_host}:{port}", + "project": "", + "file": "", + "registered_at": time.time(), + "lazy": True, + } self.set_current_port(port) diff --git a/src/ghydramcp/server.py b/src/ghydramcp/server.py index 767572d..959e4b1 100644 --- a/src/ghydramcp/server.py +++ b/src/ghydramcp/server.py @@ -105,10 +105,14 @@ def create_server( def _periodic_discovery(interval: int = 30): """Background thread for periodic instance discovery. + Uses a short timeout per port so a full scan completes quickly + even when most ports are unreachable. + Args: interval: Seconds between discovery attempts """ - from .core.http_client import safe_get + import requests as _requests + from .mixins.base import GhydraMixinBase config = get_config() @@ -116,19 +120,26 @@ def _periodic_discovery(interval: int = 30): while True: time.sleep(interval) try: - # Quick scan of common ports + # Quick scan — use discovery_timeout (0.5s), NOT request_timeout (30s) for port in config.quick_discovery_range: try: - response = safe_get(port, "") - if response.get("success", False): - with GhydraMixinBase._instances_lock: - if port not in GhydraMixinBase._instances: - GhydraMixinBase._instances[port] = { - "url": f"http://{config.ghidra_host}:{port}", - "project": response.get("project", ""), - "file": response.get("file", ""), - "discovered_at": time.time(), - } + url = f"http://{config.ghidra_host}:{port}/" + resp = _requests.get( + url, + timeout=config.discovery_timeout, + headers={"Accept": "application/json"}, + ) + if resp.ok: + response = resp.json() + if response.get("success", False): + with GhydraMixinBase._instances_lock: + if port not in GhydraMixinBase._instances: + GhydraMixinBase._instances[port] = { + "url": url.rstrip("/"), + "project": response.get("project", ""), + "file": response.get("file", ""), + "discovered_at": time.time(), + } except Exception: pass except Exception: