docs: add REA tool design guidance (#485)

docs: clarify MCP tool design guidance
This commit is contained in:
morluto
2026-09-29 16:28:13 +08:00
committed by GitHub
parent 5dd3b4655e
commit 36aeec9586
3 changed files with 103 additions and 106 deletions
+31 -106
View File
@@ -17,129 +17,54 @@ REA is a local-only tool; do not sanitize actionable local diagnostics such as a
## Project Structure & Module Organization
REA is a layered ESM TypeScript application. Dependencies flow inward: `domain` underlies `contracts` and the shared `process` primitives; providers depend on those layers, followed by `application`, `server`, and the entry adapters.
REA is a layered ESM TypeScript application. Dependencies flow inward from pure domain logic through contracts, providers, application workflows, and CLI/MCP adapters. See [docs/architecture.mermaid](docs/architecture.mermaid) for the component map.
See [docs/architecture.mermaid](docs/architecture.mermaid) for a visual architecture diagram.
- `scripts/rea.mjs`: executable dispatcher. Routes only bare `mcp` and `--mcp` to the production stdio server; Incur handles registration utilities and one-shot commands.
- `src/main.ts`: MCP adapter. Parses config, wires the shared session runtime, starts stdio transport, and owns process-lifetime shutdown.
- `src/cli.ts`: one-shot CLI adapter for setup, diagnostics, analysis, and decompilation.
- `src/config.ts`: Zod-validated parsing of environment configuration into `AppConfig`.
- `src/domain/`: pure, side-effect-free modules. `errors.ts` owns the tagged error algebra; `result.ts` owns `Result`/`ok`/`err`; `hopperValues.ts` owns shared function-dossier values plus Hopper boundary parsers; `symbolAnalysis.ts` parses Swift/ObjC names; `javascriptApplicationGraph.ts` validates and canonically commits the provider-neutral JavaScript Application Graph; `javascriptStaticAnalysis.ts` performs bounded AST-only JavaScript structure recovery.
- `src/contracts/`: caller-visible tool schemas and catalog metadata; `toolContracts.ts` owns the canonical inventory and `enhancedInputs.ts` owns enhanced input parsing.
- `src/process/`: provider-neutral process ownership and lifecycle primitives. It owns private runtime roots, session-assigned run identity and token-verified lineage, absolute startup deadlines, correlated request waits, bounded output capture, and TERM-to-KILL cleanup without defining any provider wire protocol.
- `src/replay/`: Linux x64 controlled-JavaScript-replay adapter. It owns exact runtime closure inspection, Bubblewrap/seccomp/cgroup admission, the disposable worker, strict parent/worker protocol validation, and complete cleanup observation.
- `src/browser/`: loopback CDP/Inspector discovery, bounded WebSocket transport, exact-origin or canonical-root target authorization, passive browser/Electron and attach-only Node/Electron V8 observation, plus controlled Playwright scenarios with explicit launch/attach ownership and cleanup.
- `src/hopper/`: Hopper launch and Unix-socket protocol mechanics. `BridgeLauncher.ts` spawns the Hopper app with the in-process bridge, `HopperClient.ts` correlates request/response over the socket with timeouts and cancellation, `protocol.ts` frames bridge messages.
- `bridge/hopper_bridge.py`: runs inside Hopper and adapts declared operations to Hopper's public Python API. Hopper's bundled MCP server is not used.
- `src/ghidra/`: exact Ghidra 12.1.2/JDK 21 inspection, analysis-profile commitment, digest-bound target snapshots, isolated `analyzeHeadless` launch, authenticated Unix-socket or Windows loopback transport, bounded serial request queue, and strict inventory/function boundaries.
- `src/dotnet/`: execution-free managed PE/CLI inspection. It owns bounded PE, CLI metadata, heap, table, and resource parsing for `rea-dotnet-static`; it must never load, reflect, execute, decompile, or resolve target assemblies.
- `bridge/ghidra/ReaGhidraBridge.java`: packaged read-only `HeadlessScript` loaded through Ghidra's `scriptPath`; it owns the persistent decompiler and adapts admitted inventory, function, reference, and CFG operations to public Ghidra APIs.
- `src/application/`: shared CLI/MCP session composition, setup and diagnostics, and enhanced workflows. `AnalysisProviderRegistry` discovers overlapping deep candidates without starting them; `SessionProviderRouter` binds one candidate per target and composes it with disjoint auxiliary providers; `JavaScriptArtifactReconstruction.ts` safely projects local directories/ASARs and inert bundle syntax into the application graph without adding a caller-visible tool yet.
- `src/server/`: MCP request translation. `createServer.ts` assembles the MCP server, `registerOfficialTools.ts`/`registerEnhancedTools.ts` register each tool set, `toolResult.ts` maps `Result` values to MCP content.
- `docs/product-catalog.json`: generated package, tool-family, provider, setup-client, and CLI facts. Regenerate it from source; do not edit it by hand.
- `tests/`: Vitest suite. Focused immutable domain builders live beside their production owner as `src/domain/*.fixture.ts`; `tests/fixtures/` holds reusable provider-process fixtures, source-owned Electron/Webpack/Rspack artifact trees, and the fake launcher, Hopper bridge, and CDP seams.
- `scripts/verify-real-hopper.mjs`: real-Hopper end-to-end verifier.
- `scripts/verify-real-ghidra.mjs`: real Linux Ghidra verifier for x86-64 debug/stripped ELF, AArch64 ELF, PE, and Mach-O function semantics plus complete cleanup.
- `scripts/verify-real-ghidra-windows.mjs`: real Windows x64 Ghidra P0 verifier for the source-owned native PE fixture, all 19 admitted operations, digest linkage, loopback transport, and cleanup.
- `scripts/verify-real-browser.mjs`: real Chrome end-to-end verifier for the passive CDP provider.
- `scripts/print-mcp-config.mjs`: prints an MCP server config with absolute paths filled in (`npm run config:print -- /path/to/binary`).
- `src/domain/` owns pure provider-neutral semantics; `src/contracts/` owns caller-visible schemas and the canonical tool inventory.
- `src/hopper/`, `src/ghidra/`, `src/browser/`, `src/replay/`, `src/native/`, `src/artifacts/`, and `src/dotnet/` own provider-specific boundaries. Keep provider protocols out of domain and application code.
- `src/application/` composes shared CLI/MCP workflows; `src/server/` translates MCP requests; `src/cli.ts` and `src/main.ts` are the CLI and MCP entry points.
- `src/process/` owns shared process lifecycle primitives, not provider wire protocols. `bridge/` contains provider-side adapters.
- `tests/` contains unit, composition, boundary, acceptance, and conformance tests. `scripts/verify-*` contains real-toolchain checks.
- `docs/product-catalog.json` is generated. Update its source contracts and regenerate it; do not edit it directly.
## Build, Test, and Development Commands
- `npm ci`: install exact lockfile dependencies.
- `npm run deps:check`: verify that installed direct dependency versions match the root lockfile; on failure, run `npm ci`, then retry.
- `npm run metadata:generate`: refresh checked-in package metadata after package, lockfile, or skill-version changes.
- `npm run metadata:check`: verify checked-in package metadata without rewriting it.
- `npm run build`: compile `src/` into `dist/`.
- `npm run build:cached`: compile through Turbo and reuse a valid local build cache entry.
- `npm test`: restore or build `dist/`, then run the Vitest suite once without coverage or retries locally.
- `npm run test:local`: run only changed and related tests without rebuilding `dist/`; use this for the lightweight local loop.
- `npm run test:fast`: run pure and subprocess test projects without the serial integration group.
- `npm run test:changed`: run changed and related non-serial tests once.
- `npm run test:integration`: run the serial filesystem, process, and CLI integration group.
- `npm run test:watch`: watch changed and related non-serial tests, without coverage.
- `npm run test:coverage`: run the complete suite with coverage; CI shards the suite and owns JUnit reporting.
- `npm run typecheck`: run strict TypeScript checks without emitting files.
- `npm run lint`: apply oxlint rules (complexity, max-lines, unused vars, and TypeScript-specific checks).
- `npm run lint:fix`: auto-fix oxlint violations where possible.
- `npm run format:check`: verify Prettier formatting.
- `npm run check`: run cached typecheck, lint, format:check, knip, and package-metadata freshness tasks.
- `npm run check:fast`: run only cached typecheck and lint for rapid local and pre-push feedback.
- `npm run check:ci`: run the static gate plus duplicate-code and debt-marker scans.
- `npm run check:test`: run the cached static tasks plus the uncached complete test suite.
- `npm run check:pr`: run the test gate plus uncached generated-document validation for PR preparation.
- `npm run knip`: detect unused files, dependencies, and exports.
- `npm run jscpd`: detect duplicate code blocks.
- `npm run scan:todos`: scan for TODO, FIXME, and HACK markers.
- `npm run verify:hopper`: build and run the real-Hopper verifier with two distinct binaries.
- `npm run verify:ghidra`: build and run the real-Ghidra verifier against `GHIDRA_INSTALL_DIR` and optional `GHIDRA_TARGET_PATH`.
- `npm run verify:ghidra:windows`: build and run the real Windows Ghidra verifier against `GHIDRA_INSTALL_DIR` and the source-owned x64 PE fixture.
- `npm run verify:browser`: build and run the real Chrome verifier against `REA_BROWSER_EXECUTABLE` or a platform-default Chrome-family executable, including browser-scenario CLI parity, attach disconnect-only ownership, launched-process termination, and temporary-profile cleanup.
- `npm run verify:inspector`: build and run the real attach-only Node Inspector verifier through the public CLI.
- `npm run verify:managed`: build and run the source-owned managed PE/CLI conformance verifier for artifact triage, member inspection, managed/native boundary declarations, token drift comparison, malformed metadata, non-managed degradation, managed application-graph projection, manifest-verifier self-test, and optional BYO ILSpy oracle checks; set `REA_MANAGED_APP_MANIFEST_PATH` to verify an operator-local managed app manifest, including optional graph/trace assertions, and set `REA_ILSPY_CMD_PATH` to an absolute `ilspycmd` path to run the real ILSpy reconstruction oracle.
- `npm run evidence:generate`: regenerate the managed conformance manifest and evidence completion ledger from live verifier output.
- `npm run evidence:check`: rerun managed conformance and fail on generated manifest, completion ledger, or bundled skill drift.
- `npm run verify:replay`: build and run the real Linux Bubblewrap/seccomp/cgroup verifier against source-owned replay fixtures; set `REA_REPLAY_INPUT_PATH` to verify an operator-local manifest.
- `npm run verify:package`: pack and test the CLI, setup transaction, skill, and canonical target-free MCP catalog in an isolated environment.
- `npm run docs:api`: render uncommitted API documentation from JSDoc comments into `build/api-docs/` using TypeDoc.
- `npm run docs:api:cached`: render API documentation through Turbo or restore a valid local result.
- `npm run docs:generate`: refresh committed metadata and render the uncommitted API documentation.
- `npm run docs:check`: verify generated package metadata, the canonical product catalog, caller-visible documentation facts, TypeDoc rendering, and the error JSON schema.
- `npm ci`: install the locked dependencies.
- `npm run build:cached`: build the CLI and MCP server.
- `npm run test:local`: run focused tests for changed code.
- `npm run check:fast`: run cached typecheck and lint checks.
- `npm run check:pr`: run the deterministic PR gate and generated-document checks.
- For provider-dependent changes, see [docs/testing.md](docs/testing.md) and run the matching real-provider verification.
Local full-suite Vitest runs use one worker and serial project groups because
boundary tests own subprocess fixtures. CI retains the existing two-worker
budget. The `test`, `docs:check`, and `docs:generate` commands use
repository-local locks and refuse overlapping runs; interrupted commands can
leave a stale lock that is removed automatically when its owner PID is no
longer alive. The `npm test` build runs inside that lock.
- `npm run config:print -- /path/to/binary`: print an MCP server config with absolute paths.
- `HOPPER_TARGET_PATH=/path/to/binary npm start`: launch Hopper and run the built stdio MCP server.
Pre-commit hooks via Husky format then lint staged source files. Pre-push runs `npm run check:fast`; CI owns the broader static and generated-document gates. Use `npm run lint:fix` to auto-correct violations locally.
See [docs/testing.md](docs/testing.md) for test scopes and verification lanes and [CONTRIBUTING.md](CONTRIBUTING.md) for contribution checks. Pre-commit formats and lints staged source; pre-push runs `npm run check:fast`.
## Configuration & Environment Variables
- `REA_ANALYSIS_PROVIDER` (optional, default `auto`): require one deep-analysis provider ID for startup and one-shot commands, or use deterministic automatic selection. A request-level `provider_id`/`--provider` takes precedence.
- `GHIDRA_INSTALL_DIR` (optional): absolute root of an extracted official Ghidra 12.1.2 distribution. The adapter supports Linux x64 and the experimental Windows x64 P0 boundary documented in `docs/windows-ghidra-p0.md`.
- `JAVA_HOME` (optional): absolute 64-bit full JDK 21 root used by Ghidra. When absent, doctor probes `java`/`javac` from `PATH`.
- `REA_ILSPY_CMD_PATH` (optional): absolute path to a bring-your-own `ilspycmd`; doctor reports its configured version, and `verify:managed` can use it as a real reconstruction oracle without making ILSpy a setup-installed dependency or canonical parser.
- `HOPPER_TARGET_PATH` (optional): absolute initial binary or `.hop` target. Target-free MCP sessions use `open_binary` instead.
- `HOPPER_LAUNCHER_PATH` (optional): override the Hopper executable path (defaults to `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper`).
- `HOPPER_TARGET_KIND` (optional, default `executable`): startup kind for `HOPPER_TARGET_PATH`; dynamic targets are classified from their paths and headers.
- `HOPPER_LOADER_ARGS_JSON` (optional): JSON array overriding derived Hopper loader arguments for supported executable targets, e.g. `["-l","Mach-O","--aarch64"]`.
- `REA_BROWSER_OBSERVE_ENABLED` (optional, default `false`): add browser observation authority to the administrator ceiling.
- `REA_BROWSER_CDP_ENDPOINTS_JSON` (optional): approved literal loopback CDP HTTP endpoints.
- `REA_BROWSER_ALLOWED_ORIGINS_JSON` (optional): exact HTTP(S) page origins approved for passive observation.
- `REA_BROWSER_SCENARIO_ENABLED` (optional, default `false`): add controlled browser automation to the administrator ceiling.
- `REA_BROWSER_SCENARIO_AUTO_GRANT` (optional, default `false`): issue the exact configured browser-automation ceiling as an administrator grant for trusted unattended use.
- `REA_BROWSER_SCENARIO_EXECUTABLE_ROOTS_JSON` (optional): canonical executable roots approved for provider-owned browser launch.
- `REA_BROWSER_SCENARIO_CDP_ENDPOINTS_JSON` (optional): literal-loopback CDP endpoints approved for external-browser attachment.
- `REA_BROWSER_SCENARIO_ALLOWED_ORIGINS_JSON` (optional): exact HTTP(S) page origins approved for browser scenarios.
- `REA_BROWSER_SCENARIO_ALLOWED_ENV_JSON` (optional): environment-variable names that scenario secret references may resolve.
- `REA_ELECTRON_OBSERVE_ENABLED` (optional, default `false`): add passive Electron file-page observation authority to the administrator ceiling.
- `REA_ELECTRON_CDP_ENDPOINTS_JSON` (optional): approved literal loopback Electron CDP HTTP endpoints.
- `REA_ELECTRON_FILE_ROOTS_JSON` (optional): canonical filesystem roots approved for passive Electron page observation.
- `REA_V8_INSPECTOR_OBSERVE_ENABLED` (optional, default `false`): add passive attach-only Node/Electron Inspector observation authority to the administrator ceiling.
- `REA_V8_INSPECTOR_ENDPOINTS_JSON` (optional): approved literal-loopback Node/Electron Inspector HTTP endpoints.
- `REA_V8_INSPECTOR_FILE_ROOTS_JSON` (optional): canonical filesystem roots approved for Inspector targets and script locations.
- `REA_V8_INSPECTOR_ALLOWED_ORIGINS_JSON` (optional): exact HTTP(S) origins approved for Electron renderer Inspector targets and scripts.
- `REA_JAVASCRIPT_REPLAY_ENABLED` (optional, default `false`): add controlled extracted-module execution authority to the administrator ceiling.
- `REA_JAVASCRIPT_REPLAY_ROOTS_JSON` (optional): exact canonical source roots approved for controlled replay.
- `REA_JAVASCRIPT_REPLAY_NODE_PATH`, `REA_JAVASCRIPT_REPLAY_BWRAP_PATH`, `REA_JAVASCRIPT_REPLAY_SYSTEMD_RUN_PATH`, `REA_JAVASCRIPT_REPLAY_SYSTEMCTL_PATH`, and `REA_JAVASCRIPT_REPLAY_SHELL_PATH` (optional): absolute paths committed by each replay plan; defaults target the current Node runtime and standard Linux system executables.
Configuration is parsed and validated by `src/config.ts` and `src/config/`. Keep user-facing setup and provider configuration in [README.md](README.md) and the relevant guide under `docs/`; do not maintain a second environment-variable catalogue here.
## Coding Style & Naming Conventions
Use ESM TypeScript, two-space indentation, and Prettier defaults. Keep compiler strictness intact (`strict`, `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`). Use `camelCase` for values/functions, `PascalCase` for classes/types, and `UPPER_SNAKE_CASE` for constants. Parse unknown values at every MCP, environment, and subprocess boundary. Avoid `any`, unchecked casts, non-null assertions, import-time I/O, and floating promises. Exported APIs require concise JSDoc. Model expected failures with the tagged error algebra and `Result`, not broad exception wrappers.
## Designing MCP Tools
Start from the analyst question and desired result, not a provider API. Before adding a tool, inspect the existing contract and its nearest alternative.
- Use bounded **inspect/search** tools for facts about a target or candidate set; use **trace** tools for relationships; use **compare** tools for explicitly paired inputs.
- Add a **workflow** when agents repeatedly orchestrate the same calls. Compose inside REA and return joined evidence instead of making callers loop.
- Use **observe/capture/replay** only when runtime activity is required, and declare authority and lifecycle effects in the contract.
- Extend an existing tool when intent and result contract are unchanged. Add a tool for a distinct analyst outcome or materially different authority.
- Keep caller-facing names and results provider-neutral. Put engine-specific behavior in provider adapters and report each provider's exact coverage.
- Bound work, make truncation and unsupported facets explicit, and keep observed, derived, inferred, and unknown results distinct.
- Include artifact identity, source locations, Evidence links, actionable errors, and limits when they affect conclusions.
- Implement shared application workflows behind CLI and MCP adapters. Update canonical contracts and generated catalog together.
See [docs/tool-design.md](docs/tool-design.md) for the design checklist. When usability or tool selection changes, evaluate representative CLI/MCP tasks as well as schema and transport behavior.
## Testing Guidelines
Name tests `*.test.ts`. Use Vitest and production seams (`tests/fixtures/`) rather than module mocks. Domain tests assert pure behavior; adapter tests use fake launcher/socket seams; MCP tests connect with the client SDK version pinned in `package.json`. Preserve the canonical tool inventory defined by `TOOL_CONTRACTS` and verified through `CATALOG_IDENTITY` and generated product metadata. Cover malformed input, cancellation, timeouts, process exit, concurrency, limits, and clean shutdown. Real Hopper, Ghidra, browser, JavaScript replay, managed conformance, and any real managed-tool claims cannot be replaced by mocks; use the corresponding `verify:*` command.
MCP tools are agent-computer interfaces. Optimize them first for successful agent use: distinct purposes, clear names and parameters, relevant result context, actionable errors, and realistic multi-step workflows. Validate tool-design changes with representative agent tasks and transcripts when possible; schema validity and transport success alone do not prove that a tool is easy for an agent to use.
MCP tool catalogs must remain complete and self-describing. Prefer capability- and session-scoped tool advertisement over schema truncation. Never use serialized byte counts as a proxy for model-context cost, token use, tool usability, or agent performance. Measure bytes only when evaluating a named transport, storage, or protocol constraint. Any model-context or token-efficiency claim must identify the client and its actual model-facing projection and tokenizer, since clients may omit, transform, dereference, or otherwise reproject MCP schemas. Do not weaken names, descriptions, schemas, examples, errors, or result context to improve a byte-based metric.
Keep tool catalogs complete and self-describing; prefer capability- and session-scoped availability over schema truncation. Serialized bytes alone do not measure agent usability or model context cost.
## Commit & Pull Request Guidelines
+2
View File
@@ -2,6 +2,8 @@
REA welcomes focused bug fixes, documentation improvements, tests, and reverse-engineering workflow enhancements. Open an issue before a large contract or architecture change so its scope can be agreed before implementation.
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
## Development setup
REA development requires Node.js 24.18.x and npm 11.16.x. Real-Hopper verification additionally requires either macOS 12+ or an officially supported Linux host (Ubuntu 24.04+, Fedora 41+, or 64-bit Arch) and an installed Hopper application. Linux demo verification uses its own private Xvfb display and does not require a desktop session. Run `nvm use` before installing dependencies.
+70
View File
@@ -0,0 +1,70 @@
# MCP tool design
Design tools around analyst tasks. A provider API is an implementation detail;
expose it directly only when users need a bounded, reusable primitive that
cannot be expressed through an existing contract.
## Choose the tool shape
| Shape | Use it for | Contract should return |
| --- | --- | --- |
| `inspect` | Facts about one explicit target, address, object, or resource | Bounded fields, source locations, and facet-level availability |
| `search` / `list` | Finding candidate targets or entities | Stable ordering, filters, pagination, and exact totals or an explicit unknown |
| `trace` | Relationships across code, metadata, UI resources, or observations | Typed edges, evidence per edge, traversal limits, and unresolved paths |
| `compare` | Two explicitly identified artifacts, versions, or evidence sets | Paired identity, comparable coverage, and deltas with evidence |
| `workflow` | A repeated analyst question that otherwise requires a fragile call sequence | The composed result, contributing evidence, and partial/unavailable facets |
| `observe` / `capture` / `replay` | A question that requires runtime behavior | Required authority, launch/attach behavior, bounds, lifecycle, and cleanup status |
These are task shapes, not required prefixes. Name a tool for the action and
object agents reason about; keep the name distinct from nearby alternatives.
## Decide whether to add a tool
1. State the user intent and the smallest result that answers it.
2. Inspect the canonical tool contracts, current provider capabilities, and
representative CLI/MCP traces. Record the nearest competing tool.
3. Extend an existing contract when the intent and result are the same. Add a
bounded batch when agents repeat scalar calls over a coherent set. Add a
workflow when agents repeatedly compose the same stages. Add a new tool when
it answers a distinct analyst question or has a materially different
authority or result contract.
4. Keep public tool names and result semantics provider-neutral. Put engine-
specific parsing and protocol handling in adapters. Advertise exact support
per provider; do not create parallel tools just because engines differ.
5. Keep prompts instructional. A prompt can suggest a sequence, but it does not
replace a tool that must perform bounded work or return durable evidence.
Avoid scalar-tool sprawl, model-authored N+1 loops, opaque mode flags, and
mega-tools that combine unrelated discovery, execution, and mutation.
## Define the contract
- Use strict object inputs with explicit required fields, enums, and bounds.
- Make pagination, truncation, partial failure, cancellation, and expected
unavailable states explicit. Missing evidence is unknown, not empty or false.
- Return task-oriented results with artifact identity, provider/version,
addresses or resource paths, Evidence IDs, confidence, and limitations when
they affect the conclusion.
- Separate observed facts from derived and inferred edges. Cite the evidence
supporting every important relationship; preserve unresolved edges.
- Declare read-only, mutation, process, filesystem, network, and UI effects
truthfully in the contract and permission boundary.
- Keep provider-specific types out of provider-neutral domain and application
layers. Normalize supported provider results without implying equal coverage.
## Implement and evaluate
- Put shared user workflows in the application layer; keep MCP translation in
the server adapter and CLI behavior aligned with the same workflow.
- Update the canonical contract inventory, output schemas, examples, generated
catalog, and relevant docs together.
- Verify valid, malformed, boundary, unavailable, cancellation, and partial
results at the owning boundaries. Use real-provider checks for claims that
depend on external analysis engines.
- Evaluate representative broad and direct task prompts on the intended MCP
host when tool selection is the question. Record the host, model-facing tool
catalog, selected tool, arguments, errors, retries, and task outcome.
Schema validity or word-overlap heuristics do not prove discoverability.
See [mcp-contracts.md](mcp-contracts.md) for shipped runtime behavior and
[testing.md](testing.md) for test boundaries and verification lanes.