When port 8192 was already in use by a non-MCGhidra container (e.g.,
LTspice), docker_start would fail instead of trying the next port.
Now loops through the pool, checking each candidate against Docker's
published ports before using it.
Also includes Docker build retry improvements from earlier session.
- Change from 'except Exception' to bare 'except' to catch Java
exceptions from Ghidra that don't inherit from Python Exception
- Use sys.exc_info() to safely extract error messages when str(e)
might fail on certain Java exception types
- Add null checks after getAddress() since it can return None
instead of throwing for invalid addresses
- Add last-resort response handling to prevent silent connection
drops when exception handling itself fails
These endpoints now return proper JSON error responses instead of
causing "Empty reply from server" errors.
ui_get_current_address and ui_get_current_function require Ghidra GUI
context to know what the user has selected. Since MCP always runs
headless (Docker container), these tools always fail with HEADLESS_MODE
error. Removed them to avoid confusion.
Alternative: Use explicit addresses with functions_get(address=...) or
data_list(addr=...) instead.
- http_client: Defensive copy before .pop() to avoid mutating caller's dict
- analysis.py: Add debug logging for fallback paths instead of silent swallow
- docker.py: Add debug logging to PortPool exception handlers
- docker.py: Fix file descriptor leak in _try_acquire_port with inner try/except
- docker.py: Lazy PortPool initialization via property to avoid side effects
- server.py: Wrap initial discovery in _instances_lock for thread safety
- server.py: Call configure_logging() at startup with GHYDRAMCP_DEBUG support
- pagination.py: Use SHA-256 instead of MD5 for query hash consistency
- base.py: Add proper type annotations (Dict[str, Any])
- filtering.py: Use List[str] from typing for consistency
- filtering.py: Add docstrings to private helper methods
- structs.py: Rename project_fields param to fields for API consistency
- logging.py: Fix import path from deprecated mcp.server.fastmcp to fastmcp
docker_health now uses get_instance_port(port) like all other tools,
so it defaults to the current working instance when no port is specified.
Workflow:
1. docker_auto_start(binary) -> returns port
2. Poll docker_health(port=N) until healthy
3. instances_use(port=N) to set as current
4. All subsequent analysis calls omit port
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.
Sprint 3+4 API gap filling plus critical stability fixes:
Features:
- Symbol CRUD (create, rename, delete)
- Bookmark management (list, create, delete)
- Enum/typedef creation
- Variable rename with type resolution
Stability fixes:
- Lazy instances_use (no blocking HTTP calls)
- Non-blocking health checks via thread executor
- Session isolation for docker_stop/cleanup
- Auto port allocation (removed client-specified ports)
- wait=False default for docker_auto_start
- 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
Rewrite GhydraMCPServer.py from 348 to 2138 lines, implementing all 45
routes that the MCP client expects. Previously, most endpoints returned
{"error": "Not found"}, breaking tools like data_list, xrefs_list, and
memory_read.
Key changes:
- Regex-based routing table with method-aware dispatch
- Thread-safe Ghidra transactions via threading.Lock()
- Full read endpoints: functions, data, strings, memory, xrefs, structs
- Full write endpoints: rename, comment, signature, create function/data
- Analysis endpoints: callgraph traversal, dataflow, run analysis
- Jython/Python 2 compatible (no f-strings, type hints, or walrus ops)
Tested with Docker build and curl against all major endpoint groups.
MCP client integration verified working.
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
- Switch from Java to Python scripts (avoids OSGi bundle issues)
- Update pyproject.toml with proper src layout and ruff config
- Add binaries/ and ghidra-src/ to gitignore
- Clean up Module.manifest
- Add GhydraMCPServer.py with fixed strings endpoint (Jython compatible)
- Fix strings endpoint to iterate through defined data instead of using
DefinedDataIterator.definedStrings() which isn't accessible in Jython
- Add entrypoint.sh for Docker container initialization
Separate Maven dependency resolution from compilation:
- COPY pom.xml first, run dependency:resolve (cached layer)
- COPY src second (only this invalidates on code changes)
- Build step reuses cached dependencies
Result: Code changes rebuild in ~30s instead of 3-5 min
(Ghidra download and Maven deps stay cached)
GhydraMCPServer.java imports Gson but headless scripts run in a
separate OSGi classloader that can't access extension lib JARs.
Fix: Download gson-2.13.1.jar to Framework/Generic/lib/ where it's
available to all scripts regardless of execution mode.
Closes issue documented in BUG_REPORT_HEADLESS_GSON.md
Add start_line, end_line, and max_lines parameters to decompile
functions, allowing AI models to retrieve only specific portions
of decompiled code for better context management.
Parameters:
- start_line: Start at this line number (1-indexed)
- end_line: End at this line number (inclusive)
- max_lines: Maximum lines to return (overrides end_line)
Response includes filter metadata with total_lines when filtering
is applied, helping models understand what portion they're viewing.
Examples:
- Get first 20 lines: max_lines=20
- Get lines 10-30: start_line=10, end_line=30
- Get 15 lines from line 25: start_line=25, max_lines=15
When applying a larger struct to an address, clear enough space
for the new data type rather than just the old data's length.
This prevents 'Conflicting data exists' errors when the new
type is larger than the existing data.
Fixes issue where ConfigParametersStruct couldn't be applied
due to conflicting smaller data items in the address range.
Comprehensive documentation overhaul reflecting all enhancements since fork:
- Documented new namespaced tool organization (instances_*, functions_*, data_*, structs_*, etc.)
- Added complete struct management API documentation
- Updated example usage with realistic reverse engineering workflows
- Added uvx installation method (recommended: local install for version sync)
- Updated all repository URLs to starsong-consulting
- Bumped plugin and bridge versions to v2.1.0, API version to 2010
Add endpoints and MCP tools to create, read, update, and delete struct
data types in Ghidra's data type manager. Enables programmatic definition
of complex data structures for reverse engineering workflows.
Includes pagination, category filtering, and field-level operations
(add, update by name or offset).
Improve documentation to make it clear that max_depth can be increased
for deeper call chains. This helps AI models understand when and how to
use larger depth values.
Refs #8
- Update all Ghidra JAR dependencies to 11.4.2
- Improve TransactionHelper to properly handle endTransaction return value
- Add GHIDRA_HOME environment variable support for flexible builds
- Update version references in extension.properties and MANIFEST.MF
The transaction fix now checks the return value from endTransaction() and
properly reports transaction failures, providing better error handling for
Ghidra 11.3.2+ compatibility.
Refs #7
- Update version to v2.0.0 in ApiConstants.java and bridge_mcp_hydra.py
- Create CHANGELOG v2.0.0 section with release date
- Fix Ghidra 11.3.2+ compatibility in TransactionHelper (endTransaction signature)
- Clarify instances_list vs instances_discover usage in documentation
- Remove commented-out code in pom.xml
Fixes#7Closes#5
- Fixed memory_write() endpoint to use correct ProgramEndpoints path
- Standardized all error responses to structured format with error codes
- Enhanced instances_discover() to return both new and existing instances
- Updated API version to 2005 for compatibility tracking
- Verified all bridge-to-plugin endpoint mappings are correct
- Confirmed route registration order follows proper specificity
Ready for v2.0.0 stable release after final testing.