docs: define v0.1 product and architecture contract

This commit is contained in:
hermes 2026-07-28 18:21:19 +00:00
commit c4fb0c6a01
12 changed files with 2130 additions and 64 deletions

View file

@ -0,0 +1,16 @@
---
status: accepted
---
# Version the JSON contract by compatibility boundary
Automation agents are the primary interface consumers, so success and error
shapes must remain predictable across upgrades. The project publishes reviewed
JSON Schema files under `schemas/v1/`: optional fields may be added within v1
and consumers must ignore unknown fields, while removing a field or changing
its type or meaning requires a new major schema version. The CLI initially
emits only its current schema rather than carrying multiple encoders; golden
fixtures and schema validation gate every success and error change. JSON mode
also preserves strict stream framing: a success is the only stdout document, a
failure is the only stderr document, and progress or diagnostic logs never
share either stream.

View file

@ -0,0 +1,11 @@
---
status: accepted
---
# Keep exit statuses coarse
Shell callers need broad control-flow signals, while automation agents need
precise diagnoses. The CLI therefore reserves exit statuses for success,
invalid invocation, runtime failure, timeout, and interruption; the versioned
JSON `error.code` carries the detailed failure taxonomy. This avoids maintaining
two overlapping error classifications that could drift apart.

View file

@ -0,0 +1,13 @@
---
status: accepted
---
# Default to JSON everywhere
Automation agents are the primary consumers, so the shortest invocation must
produce the stable machine interface. The CLI defaults to JSON regardless of
whether stdout is a terminal or pipe; human-readable presentation requires
`--format human`. Avoiding TTY-dependent format selection ensures that the same
command has the same framing and semantics in every execution environment.
JSON mode never emits interactive prompts; required confirmation is represented
as a structured error and satisfied by an explicit flag.

View file

@ -0,0 +1,11 @@
---
status: accepted
---
# Never reuse a partial Analysis
Ghidra preserves analyzer results completed before its analysis timeout, but
automation consumers cannot safely infer which facts are missing. New Analyses
are therefore built in staging and promoted atomically only after full success;
a timeout, interruption, or analysis failure preserves the Diagnostic Log,
removes the partial staging project, and leaves no reusable Analysis.

View file

@ -0,0 +1,13 @@
---
status: accepted
---
# Analyze a staged Sample snapshot
Passing the caller's path to Ghidra after hashing creates a race in which the
bytes analyzed may differ from the recorded Sample identity. The CLI instead
copies the input through a read-only handle into private staging while hashing,
verifies that source metadata did not change during the copy, and gives only
that snapshot to Ghidra. The extra temporary I/O and disk use buy reproducible
identity, isolate Java from the caller's path, and allow the snapshot to be
removed after either success or failure.

View file

@ -0,0 +1,14 @@
---
status: accepted
---
# Sandbox the Ghidra worker
Ghidra parses adversarial Samples through a large Java analysis engine, while
the Rust harness must manage trusted locks, store data, and atomic promotion.
The harness therefore remains outside a narrow sandbox containing only the
Ghidra/JVM worker and its invocation capabilities. Bubblewrap is the default
Linux backend, with explicit external-harness and disabled modes for
environments that cannot nest it. This limits a compromised worker's access
without turning the CLI into a daemon or allowing arbitrary sandbox-runner
commands.

View file

@ -0,0 +1,425 @@
# Ghidra MCP comparison for `ghidra-cli`
Status: reviewed and adopted into the PRD, 2026-07-28
## Scope and source snapshots
This note compares the accepted `ghidra-cli` version 0.1 design with two
primary-source repositories:
- LaurieWired/GhidraMCP at commit
[`27f316f80139e2d5dec882519a1bdf4aa46ac04c`](https://github.com/LaurieWired/GhidraMCP/tree/27f316f80139e2d5dec882519a1bdf4aa46ac04c),
tagged `1.4`.
- bethington/ghidra-mcp at commit
[`8cd2078e10b9ba28b188cb84ce5b9051a904b995`](https://github.com/bethington/ghidra-mcp/tree/8cd2078e10b9ba28b188cb84ce5b9051a904b995),
tagged `v6.0.0`.
The comparison uses repository source, manifests, CI, licenses, changelogs,
and the repositories' own issue reports. It does not treat README marketing
claims as independently verified performance evidence.
## Executive conclusion
Do not change the version 0.1 architecture or widen its command surface because
of these MCP implementations. The accepted synchronous Rust harness, private
file protocol, one Sample per command, immutable Analysis, strict JSON, and
sandboxed short-lived Ghidra worker directly avoid the largest sources of
complexity visible in both repositories.
The repositories nevertheless validate several useful mechanisms:
- Relationship queries are the most valuable next capability for an agent.
Prioritize `strings`, `xrefs`, and direct `calls` after version 0.1, followed
by targeted disassembly and detailed single-Function inspection.
- A large agent surface needs categories and capability discovery. Do not build
that for four commands, but preserve command metadata so a future MCP adapter
can expose a small, generated catalog without hand-written duplication.
- Every process/protocol boundary needs independent size validation and
parity tests. Add a small explicit ceiling for internal `request.json`, and
test that Rust operation types, Java dispatch, and published schemas contain
exactly the same operations.
- Explicit target selection is essential. Keep requiring the Sample and exact
Function Selector on every Query; never introduce a mutable “current
program” fallback.
- An eventual MCP integration should be a thin, separate adapter that invokes
`ghidr` and forwards its schemas. It should not add an embedded HTTP server,
persistent Ghidra session, or a second result contract to the core.
The principal rejection is breadth. One examined project exposes 272 catalog
entries, including mutations, arbitrary scripts, project lifecycle, emulation,
and debugging; its own users subsequently requested filtering because the
surface was too large for agents ([issue #267](https://github.com/bethington/ghidra-mcp/issues/267))
and raised broader maintainability and provenance concerns
([issue #307](https://github.com/bethington/ghidra-mcp/issues/307)). The current
`ghidra-cli` narrow vertical slice is a feature, not a gap.
## LaurieWired/GhidraMCP
### Architecture and state
The original project is a three-hop interactive topology:
```text
MCP client -> Python FastMCP bridge -> HTTP -> plugin in a running Ghidra GUI
```
The Python process speaks MCP over stdio by default and can expose SSE; every
tool then makes an HTTP request to a configured Ghidra URL
([bridge initialization and transports](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L15-L24),
[CLI transport selection](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L290-L334)).
The Java extension embeds `com.sun.net.httpserver.HttpServer` in Ghidra and
operates on the GUI's `ProgramManager.getCurrentProgram()`
([server construction](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L95-L114),
[current Program resolution](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1627-L1629)).
Installation therefore requires installing and enabling a GUI extension and
opening Ghidra, in addition to Python and the MCP SDK
([installation prerequisites and workflow](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/README.md#L30-L79)).
This is session-oriented rather than content-oriented. The Sample is not an
argument to a tool call; the implicit target is whichever Program is current
in the GUI. Cursor-dependent tools expose the current address and Function,
further coupling automation to interactive state
([current-selection tool wrappers](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L150-L176)).
### Tool surface and mutation
The snapshot has 27 Python `@mcp.tool` declarations mapped to 27 Java HTTP
contexts. Read tools cover Functions, classes/namespaces, segments, imports,
exports, data, strings, disassembly, and cross-references. Write tools rename
Functions, data, and variables; change prototypes and local types; and add
comments
([Python tool declarations](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L60-L288),
[HTTP context registration](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L109-L342)).
Writes run Ghidra transactions on the Swing event thread, so this is not a
read-only analysis layer
([rename transaction](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L512-L540)).
### Result contract and bounds
The outer MCP parameter schemas are inferred from Python type annotations, but
the inner HTTP protocol is mostly newline-delimited text. `safe_get` splits
text into lines, while failures are returned as strings in the same value
channel rather than typed MCP errors
([GET behavior](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L26-L43),
[POST behavior](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L45-L58)).
The Java server always responds with HTTP 200 and `text/plain`, including
application errors
([response writer](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1632-L1638)).
Many lists have `offset` and `limit`, normally defaulting to 0 and 100, but
there is no `total`, `returned`, or `has_more` metadata. Some duplicate tools
are unpaginated, such as `list_functions`
([list wrapper](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L171-L176)).
Pagination accepts negative and arbitrarily large values without a public
validation contract
([pagination implementation](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1582-L1606)).
There is no whole-response byte bound or spill mechanism.
Function selection is ambiguous by construction: name selection returns the
first exact basename encountered, and address selection accepts an interior
address by resolving the containing Function
([name selection](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L493-L509),
[interior-address fallback](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L782-L805)).
These are precisely the ambiguities the accepted `ghidra-cli` selector
contract should continue rejecting.
The timeout layers are inconsistent. The Python bridge times every HTTP call
out after five seconds, while Java decompilation can run for 30 or 60 seconds
([bridge timeouts](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L35-L51),
[decompiler timeout](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1202-L1217)).
An MCP caller can therefore receive a timeout while Ghidra continues working.
This validates `ghidra-cli`'s single derived child watchdog and process-group
termination design.
### Security, tests, and dependencies
`new InetSocketAddress(port)` binds the embedded server without an explicit
loopback address, and the request path shown has neither authentication nor a
body-size cap
([server bind](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L95-L108),
[unbounded form read](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1559-L1579)).
The Ghidra process and plugin are unsandboxed and inherit the GUI user's
filesystem and network access.
Runtime dependencies are Python 3.10+, `mcp`, and `requests`; the Java build
uses Maven plus manually copied Ghidra JARs
([Python requirements](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L1-L6),
[system-scoped Ghidra JARs](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/pom.xml#L12-L87)).
The only committed Java test is the Maven-template `assertTrue(true)` smoke
test, so no endpoint, schema, timeout, security, or real-Ghidra regression is
covered
([test source](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/test/java/com/lauriewired/AppTest.java#L1-L35)).
## bethington/ghidra-mcp
### Architecture and state
This repository identifies itself as a substantial derivative of the Laurie
project, not an independent implementation
([NOTICE](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/NOTICE#L1-L10)).
It retains the bridge topology but expands it:
```text
MCP client -> Python MCP bridge -> UDS or HTTP -> Ghidra GUI plugin
-> headless Java server
-> optional debugger service
```
The bridge supports stdio, Streamable HTTP, and deprecated SSE
([bridge CLI](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/cli.py#L102-L143)),
while repository documentation describes separate GUI and headless Java
servers and a Python protocol-conversion process
([architecture](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L977-L995)).
The bridge discovers server instances, remembers a connected project and
transport, dynamically registers tools, and attempts reconnection after Ghidra
restarts
([shared mutable state](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/state.py#L14-L35),
[reconnection](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/dispatch.py#L67-L128)).
The cost is visible in the project's own issue history. Concurrent sub-agent
calls caused intermittent bridge shutdown and forced a serialization lock
([issue #91](https://github.com/bethington/ghidra-mcp/issues/91),
[current lock](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/state.py#L18-L25)).
On macOS, a GUI/agent environment mismatch in `$TMPDIR` prevented Unix-socket
discovery until the implementation learned to scan multiple candidate
locations ([issue #170](https://github.com/bethington/ghidra-mcp/issues/170),
[current discovery rationale](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/discovery.py#L20-L40)).
These are real solutions for a persistent multi-instance service, but they are
states `ghidra-cli` intentionally does not have.
### Tool surface, discovery, and mutation
The committed endpoint catalog declares 272 tools across 15 categories
([catalog](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/tests/endpoints.json)).
The surface includes read-only listing, Functions, cross-references, call
graphs, strings, data flow, and memory inspection, but also symbol/type/comment
mutation, project deletion and version control, arbitrary Java script
execution, P-code emulation, and live debugging
([feature inventory](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L69-L95),
[API categories](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L624-L740)).
Its most reusable mechanism is annotation-driven registration. Java service
methods carry tool/parameter annotations; one scanner creates deterministic
HTTP endpoint definitions and a machine-readable input schema
([scanner contract and sorting](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/AnnotationScanner.java#L13-L87),
[descriptor construction](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/AnnotationScanner.java#L94-L118)).
The Python bridge fetches that schema and generates callable MCP signatures at
runtime
([dynamic handler construction](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L22-L146),
[schema fetch](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L302-L318)).
The current bridge can load categories lazily and exposes `search_tools`,
`list_tool_groups`, and load/unload/check helpers
([agent discovery workflow](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L377-L393)).
This directly addresses tool overload, but it also introduces another mutable
session dimension and depends on clients honoring `tools/list_changed`.
### Result contract and bounds
The Java response type is an improvement over the original but remains a
union of structured success, simple `{ "error": "message" }`, and raw text
passthrough
([sealed `Response`](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/Response.java#L3-L43)).
Consequently, some endpoints return JSON while many paginated endpoints still
return newline-delimited text, and the dynamically generated MCP handlers all
declare `str` results
([text pagination](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/ServiceUtils.java#L232-L244),
[generated return annotation](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L111-L145)).
The live tool schema describes inputs, not stable versioned output schemas or
typed error codes
([schema translation](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/schema.py#L54-L99)).
Request hardening is much stronger: version 6.0 applies a 64 MiB body ceiling
and bounds actual reads rather than trusting `Content-Length`
([request ceiling](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/SecurityConfig.java#L51-L75),
[HTTP enforcement](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/GhidraMCPPlugin.java#L2253-L2263)).
Response bounding remains endpoint-specific rather than a universal contract.
For example, the whole-program call graph accepts `limit=0` as unlimited and
can emit text, DOT, Mermaid, adjacency, or address-edge JSON
([call-graph parameters and implementation](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/XrefCallGraphService.java#L782-L877)).
`ghidra-cli`'s one schema, disclosed collection bounds, and universal serialized
byte cap are materially stronger for an automation agent.
The bridge coordinates endpoint-specific HTTP timeouts and retries GET calls;
write calls deliberately avoid blind retry because completion is ambiguous
after a connection loss
([timeout catalog](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/config.py#L7-L35),
[retry policies](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/dispatch.py#L131-L195)).
Notably, the bridge's `decompile_function` timeout is 45 seconds while the
Java endpoint defaults to 60 seconds
([bridge value](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/config.py#L21-L34),
[Java value](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/FunctionService.java#L149-L186)).
This is further evidence that one owning harness should derive and enforce the
complete child lifetime.
The project learned an important target-selection lesson. Calls default to a
mutable active Program; strict explicit Program selectors exist only behind an
environment variable because compatibility retained the fallback
([strict-mode rationale](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L350-L375),
[bridge enforcement](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L26-L87)).
`ghidra-cli` should keep Sample selection mandatory from its first release and
never acquire this compatibility burden.
### Security, tests, and dependencies
Current security controls include loopback defaults, refusal of a non-loopback
bind without bearer authentication, anti-CSRF/DNS-rebinding checks, script
execution disabled by default, filesystem-root containment, request-body
caps, and generic uncaught-error responses
([security configuration](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L440-L493),
[safe handler](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/GhidraMCPPlugin.java#L2210-L2279)).
The change history states that these were hardening changes after earlier
unauthenticated, ungated, or insufficiently bounded behavior
([v6.0.0 security history](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/CHANGELOG.md#L7-L73)).
These controls are valuable for a network service, but they do not isolate
Ghidra from the user's account. `ghidra-cli` should retain the stronger design
of no listener plus a capability-limited worker sandbox. If it ever adds an
MCP adapter, stdio should remain the default and network transport should be a
separately threat-modeled feature.
The shipped bridge requires Python 3.10+ and the `mcp` package; tests and
unshipped subsystems add many optional dependencies
([package manifest](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/pyproject.toml#L1-L24),
[dependency groups](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/pyproject.toml#L53-L101)).
The repository has a materially stronger regression practice than the original:
Java offline tests and coverage, a Python version matrix, Windows-specific
tests, performance regressions, and Pester setup tests are gating; formatting
and lint jobs are currently advisory
([CI gates](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/.github/workflows/tests.yml#L16-L181),
[quality jobs](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/.github/workflows/tests.yml#L288-L358)).
Its annotation/schema/catalog parity approach is worth borrowing; its broad
multi-subsystem matrix is not needed for version 0.1.
## Recommendations for `ghidra-cli`
### Adopt now
1. **Keep one source of truth for operations.** Define every internal operation
once with its request type, response type, public command kind, and schema
name. Generate or mechanically verify the Rust schema set and Java dispatch
set against it. The bethington annotation scanner demonstrates the value of
preventing hand-maintained endpoint/schema drift, while `ghidra-cli` can do
this without runtime reflection or dynamic registration.
2. **Cap both sides of the private protocol.** The accepted 256 MiB response
ceiling is good. Add a much smaller fixed serialized `request.json` ceiling,
suggested at 1 MiB, enforced by Rust before launch and Java before parse.
Requests contain metadata and tool-owned paths, not Sample bytes, so a
larger allowance has no known use. This is a protocol-hardening detail, not
a public CLI flag.
3. **Add protocol/catalog parity regressions.** A fixture should fail if a Rust
operation lacks Java handling, if Java accepts an unpublished operation, or
if a public schema/golden is missing. Keep the already accepted real-Ghidra
ELF and PE integration tests and strict lint/format gates.
4. **Retain mandatory targets and stable identifiers.** Every Query keeps an
explicit Sample; Function operations keep exact entry Address or exact name
selection with ambiguity errors. Do not add GUI cursor, active Program, or
implicit containing-Function behavior.
5. **Preserve extension metadata without exposing it yet.** Command/domain
definitions may carry a stable category and one-sentence agent description.
This costs little and lets a later MCP adapter generate a discoverable
surface, but version 0.1 needs no `search-tools`, runtime group loading, or
config file.
### Defer in priority order
1. **`strings` Query.** Return a bounded, stably ordered list with exact Address,
byte length, decoded representation with explicit encoding, and referring
Addresses or a separate bounded reference link. Avoid case-folded server-side
substring filters initially; agents can use `jq`, and raw Sample-derived
strings may be non-UTF-8 or extremely large.
2. **`xrefs` Query.** Model each Reference structurally: source Address,
destination Address or external target, Ghidra reference type, operand index,
and containing source Function entry when present. Direction should be an
explicit enum. Never emit prose such as `From ... in ... [READ]`.
3. **Direct `calls` Query.** Start with flat Call Edges containing caller entry,
callsite, and resolved or unresolved callee. Support pagination and stable
ordering. Do not begin with recursive whole-program graphs, DOT/Mermaid
renderers, graph algorithms, or `limit=0` unlimited output; agents can build
graphs from bounded edge pages.
4. **Targeted `disassemble` and `function` detail Queries.** These are more
useful than widening every `functions` item. A detailed Function result can
later carry signature, parameters, locals, body ranges, thunk resolution,
and alternate symbols without making discovery pages expensive.
5. **Batch Query execution.** Multiple decompilations in one Ghidra process may
eventually reduce startup cost, but only add it after measurements. It needs
explicit per-item success/error objects, deterministic order, aggregate
byte bounds, cancellation semantics, and no hidden parallelism.
6. **Thin MCP adapter.** After schemas and CLI behavior stabilize, a separate
package can expose one MCP tool per stable `ghidr` Query, invoke the CLI over
stdio/process execution, and forward its JSON unchanged. If the surface later
grows, generated categories and a capability-search tool become appropriate.
The adapter should not call Ghidra directly or own Analysis Store policy.
7. **Interactive/mutating analysis.** Renames, types, comments, scripts,
debugging, and shared-project workflows solve a different problem. If ever
pursued, put them behind a separate explicit write-capability model and a
separate domain contract; do not weaken immutable Analysis semantics.
### Explicitly reject for version 0.1
- Embedded HTTP, TCP, UDS, SSE, Streamable HTTP, discovery scans, reconnect
logic, persistent daemons, and background JVMs.
- Mutable current-Program/current-Address/current-Function session state.
- Any automatic first match for duplicate Function names or interior addresses.
- Raw text success/error unions, HTTP-status tunneling, untyped addresses, and
prose records that agents must parse.
- Hidden retry escalation. A caller may explicitly retry a failed read-only
Query; `ghidr` must not multiply time budgets behind the contract.
- Arbitrary Java/Python scripts, mutation, project management, debugger control,
emulation, binary export, and Ghidra Server administration.
- Whole-program graph renderers and custom search/query languages. Emit bounded
facts; let `jq` and downstream tools transform them.
- Runtime tool-group loading for the initial four-command surface.
- A database or global mutable index merely to support discovery or MCP state.
## Licensing and provenance
Both repositories use Apache-2.0
([LaurieWired license](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/LICENSE),
[bethington package license](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/pyproject.toml#L1-L9)).
The bethington repository explicitly retains attribution to LaurieWired for
portions that remain
([NOTICE](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/NOTICE#L1-L10)).
Concepts, public Ghidra API usage patterns, and independently designed schemas
may be studied without making `ghidra-cli` a derivative. If any source or close
adaptation is copied, Apache-2.0 requires preserving applicable copyright,
license, change, and NOTICE obligations. The cleanest course is to keep the
implementation independent, cite these projects as prior art in research and
design notes, and record provenance for any deliberately reused code before it
enters a commit.
The open maintainability issue also alleges that some contributor changes were
recommitted in ways that obscured authorship. This report does not adjudicate
that allegation, but it reinforces the accepted `ghidra-cli` rule: AI assistance
does not replace review, attribution, or a traceable change history
([issue #307](https://github.com/bethington/ghidra-mcp/issues/307)).
## Decision impact
No accepted version 0.1 product decision should be removed. The research adds
three concrete implementation-time checks and a clearer post-0.1 order:
- internal request-size ceiling;
- operation/Java/schema parity test;
- relationship-query order: `strings`, `xrefs`, direct `calls`, then targeted
disassembly and Function detail.
Everything else belongs either in a future thin MCP adapter or in a separately
designed interactive/mutating product.
The PRD now records the request ceiling, operation/dispatch/schema parity,
no-default-egress regression coverage, ordered relationship Queries, bounded
batch prerequisites, and thin-adapter MCP boundary. These adoptions do not add
a public version 0.1 command or widen the Java adapter's authority.