docker_wait was the same anti-pattern as wait param - it blocked
a single tool call for up to 5 minutes with no visibility.
LLMs should poll docker_health(port) in their own loop. This gives:
- Visibility into progress between polls
- Ability to check docker_logs while waiting
- Control over timeout and retry logic
- Opportunity to bail out early
The wait parameter was a convenience anti-pattern that caused LLMs
to block on a single tool call for up to 5 minutes with no visibility
into progress.
Now docker_auto_start always returns immediately. Clients should use
docker_wait(port) separately to poll for container readiness. This
gives visibility into progress and allows early bailout.
Previously only docker_health was fixed to use run_in_executor(),
but all other Docker operations (docker_status, docker_start,
docker_stop, docker_logs, docker_build, docker_cleanup) still
used synchronous subprocess.run() which blocked the async event
loop. This caused docker_auto_start(wait=True) to freeze the
entire MCP server.
Now _run_docker_cmd is async and runs subprocess calls in thread
executor. All callers updated to use await.
- docker_health now runs HTTP call in thread executor instead of
blocking the async event loop (prevents MCP server freeze during polls)
- docker_auto_start defaults to wait=False so tool returns immediately
(clients should call docker_wait separately if needed)
- docker_stop now validates container belongs to current session
before stopping (prevents one agent from stopping another's work)
- docker_cleanup now defaults to session_only=True for safety
(agents can still use session_only=False with caution)
Addresses audit finding: tools could cause cross-session interference
Ports are now always allocated from the pool (8192-8199) automatically.
This prevents session collisions where different agents would specify
the same port and interfere with each other.
Clients can't accidentally (or intentionally) override the port allocation
— the pool manager handles all assignments.
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.
instances_use held _instances_lock while calling register_instance,
which also acquires the same lock — non-reentrant Lock = hang forever.
- Release lock before calling register_instance (avoids blocking
other threads during the HTTP health check too)
- Upgrade Lock → RLock as safety net for any other reentrant paths
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
return_all=True on large binaries (1800+ functions) produced 72K char
responses that exceeded the MCP tool result limit. Instead of truncating,
oversized responses now return a structured summary with sample data,
available fields, and actionable instructions for narrowing the query.
Three layers of filtering:
- Server-side grep: Jython HTTP handlers filter during Ghidra iteration
- Field projection: jq-style key selection strips unneeded fields
- Token budget guard: responses exceeding 8k tokens return a summary
New files: core/filtering.py (project_fields, apply_grep, estimate_and_guard)
Modified: config, pagination, base mixin, all 5 domain mixins, headless server
Refactors Docker mixin to support multiple Claude processes sharing
the same MCP server without port/container conflicts:
- PortPool class with flock-based cross-process synchronization
- Session-scoped container naming with UUID prefixes
- Docker label-based tracking for cross-process container discovery
- Automatic port allocation from pool (8192-8199)
- Cleanup mechanism for orphaned containers and stale locks
- New tools: docker_cleanup, docker_session_info
- Add ghydramcp Python package with FastMCP server implementation
- Add docker-compose.yml for easy container management
- Add Makefile with build/run targets
- Add QUICKSTART.md for getting started
- Add uv.lock for reproducible dependencies