Reverse engineering typically requires juggling multiple specialized tools: Ghidra for decompilation, Hopper for disassembly, dynamic tracers for runtime behavior. Each tool has its own CLI, API surface, and session management. REA (Reverse Engineer Anything) exposes all of them through a single Model Context Protocol (MCP) server, letting agents coordinate static analysis, dynamic tracing, and binary inspection without managing tool-specific state.
The project sits at 14,029 stars and ranks #1 in trending TypeScript repos. It targets Claude Code, Codex, Cursor, Gemini, and Grok through a skills framework that abstracts vendor differences. The interesting part is not the agent frontend but the MCP server that maintains investigation state across tool boundaries.
The Investigation Model
REA treats reverse engineering as a multi-phase investigation. An agent might:
- Disassemble a binary with Hopper to identify function entry points
- Decompile suspicious functions with Ghidra to read pseudo-C
- Attach a dynamic tracer to observe runtime behavior
- Correlate static and dynamic results to understand control flow
The MCP server holds the investigation context. When an agent calls analyze_binary, the server decides whether to invoke Ghidra, Hopper, or both based on the query. When the agent requests runtime behavior, the server spawns a tracer and merges its output with prior static analysis.
This is not a simple tool wrapper. The server maintains a shared investigation graph: functions discovered in static analysis become targets for dynamic tracing. Symbols resolved at runtime feed back into the decompiler’s type inference. The agent sees a unified API; the server handles tool coordination.
MCP Server Architecture
The MCP server runs on Node.js 22+ and exposes a catalog of tools:
- Binary analysis: file format parsing, section enumeration, symbol extraction
- Decompilers: Ghidra headless analyzer, Hopper scripting bridge
- Disassemblers: objdump, radare2, Binary Ninja (via plugin)
- Runtime tracing: dtrace, strace, frida for dynamic instrumentation
- Credential injection: OAuth bindings and vendor CLI wrappers (Stripe, GitHub) for API-level inspection
Each tool runs in a subprocess. The server manages lifecycle (spawn, attach, teardown) and serializes results into a common schema. Agents call MCP methods like decompile_function or trace_syscalls; the server routes to the appropriate backend and caches results in the investigation context.
State Management Across Tools
The server maintains three state layers:
| Layer | Scope | Persistence |
|---|---|---|
| Investigation context | Cross-tool results, symbol maps, call graphs | In-memory, serialized to JSON on request |
| Tool sessions | Ghidra project files, Hopper database handles | Ephemeral, cleaned up on investigation close |
| Agent checkpoints | Intermediate findings, hypotheses, next steps | Stored in MCP resource URIs for resume |
When an agent requests analyze_binary, the server:
- Spawns Ghidra in headless mode with a temporary project directory
- Runs the decompiler, extracts functions and types
- Stores results in the investigation context with a unique function ID
- Returns a summary to the agent with function IDs as handles
If the agent later calls trace_function with a function ID, the server:
- Looks up the function’s address in the investigation context
- Spawns frida with a script targeting that address
- Merges runtime observations (arguments, return values) back into the function record
- Updates the investigation context and returns the merged view
The agent never manages Ghidra project files or frida script lifecycles. It sees a stateful investigation that spans tools.
Tool Boundary Decisions
The server uses heuristics to decide which tool to invoke:
- File format unknown: Start with
fileandobjdumpfor basic metadata - Symbols stripped: Prefer Ghidra for pattern-based function recovery
- Obfuscated control flow: Invoke Hopper’s graph analysis before decompilation
- Runtime behavior needed: Spawn frida or dtrace only after static analysis identifies targets
These rules live in the server’s investigation planner. The agent can override by calling specific tools directly, but the default flow minimizes redundant analysis. For example, if Ghidra already decompiled a function, the server skips Hopper disassembly unless the agent explicitly requests a second opinion.
Credential Injection and Vendor CLIs
REA includes wrappers for vendor CLIs like stripe and gh. These tools often require OAuth tokens or API keys. The MCP server handles credential injection server-side:
- OAuth flows: The server opens a local callback listener, exchanges authorization codes for tokens, and stores them in a secure credential store (OS keychain on macOS, secret-tool on Linux)
- API key injection: Environment variables are set in the subprocess environment before spawning the CLI
- Token refresh: The server monitors token expiry and refreshes automatically using stored refresh tokens
Agents call invoke_cli with a tool name and arguments. The server injects credentials, runs the command, and returns stdout. The agent never sees raw tokens. This keeps the MCP interface tool-agnostic while supporting authenticated workflows.
Parseltongue: Input Perturbation for Red-Teaming
REA ships with 33 Parseltongue techniques for testing agent robustness. These are input perturbations across three intensity tiers:
- Tier 1 (benign): Unicode normalization, whitespace variations, case folding
- Tier 2 (adversarial): Homoglyph substitution, zero-width characters, BIDI overrides
- Tier 3 (hostile): Polyglot payloads, format string injection, shell metacharacter encoding
The MCP server can apply Parseltongue transforms to any tool input before passing it to the backend. This lets agents test how decompilers, disassemblers, and tracers handle malformed or malicious inputs. For example, an agent might:
- Decompile a function normally
- Apply Tier 2 transforms to the function name
- Re-run the decompiler to see if it crashes or produces different output
This is useful for evaluating tool brittleness and finding edge cases in binary parsers.
Observability and Failure Modes
The MCP server logs every tool invocation with:
- Tool name and arguments
- Subprocess PID and exit code
- Stdout/stderr capture
- Execution time and memory usage
- Investigation context snapshot before and after
Logs are structured JSON, queryable with jq or ingested into observability platforms. When a tool crashes, the server:
- Captures the core dump (if available)
- Logs the investigation context at the time of failure
- Returns an error to the agent with the tool name and exit code
- Continues the investigation with remaining tools
Common failure modes:
| Failure | Cause | Mitigation |
|---|---|---|
| Ghidra timeout | Large binary, complex control flow | Server kills subprocess after 5 minutes, returns partial results |
| Frida attach failure | Target process protected by SIP or ptrace restrictions | Server falls back to passive tracing (dtrace, strace) |
| Hopper license error | Commercial license expired or not found | Server skips Hopper, uses Ghidra only |
| Memory exhaustion | Decompiler allocates too much RAM | Server enforces per-tool memory limits via cgroups (Linux) or ulimit |
The agent sees a unified error interface. It can retry with different tools or adjust the investigation scope.
Deployment Shape
REA runs as a single MCP server process. Typical deployment:
// server.ts
import { MCPServer } from 'rea-agents/mcp';
import { GhidraBackend, HopperBackend, FridaBackend } from 'rea-agents/tools';
const server = new MCPServer({
tools: [
new GhidraBackend({ headless: true, timeout: 300000 }),
new HopperBackend({ license: process.env.HOPPER_LICENSE }),
new FridaBackend({ spawn: true, attach: true }),
],
investigation: {
persistTo: './investigations',
maxConcurrent: 4,
},
observability: {
logLevel: 'debug',
metricsPort: 9090,
},
});
server.listen(3000);
The server exposes:
- MCP protocol: Port 3000, JSON-RPC over HTTP or WebSocket
- Metrics endpoint: Port 9090, Prometheus-compatible
- Investigation storage: Local filesystem or S3-compatible object store
Agents connect via MCP clients (Claude Desktop, Cursor, custom scripts). Multiple agents can share the same server, but each investigation is isolated. The server enforces concurrency limits to prevent resource exhaustion.
Security Boundaries
The MCP server runs tool subprocesses with restricted privileges:
- Filesystem access: Chroot jail or Docker container for Ghidra, Hopper
- Network access: Blocked by default, allowlist for API calls (Stripe, GitHub)
- Process spawning: Limited to tool binaries, no shell access
- Memory limits: Enforced via cgroups (Linux) or
ulimit(macOS)
Agents cannot execute arbitrary code on the server. They can only invoke predefined MCP tools. The server validates all inputs against a schema before passing them to backends. This prevents command injection and path traversal attacks.
Credential storage uses OS-level keychains. Tokens are encrypted at rest and never logged. The server rotates tokens on a schedule and revokes them when an investigation closes.
Code Example: Investigation Flow
// agent.ts
import { MCPClient } from 'rea-agents/client';
const client = new MCPClient({ serverUrl: 'http://localhost:3000' });
// Start investigation
const investigation = await client.createInvestigation({
target: './suspicious-binary',
goals: ['identify obfuscation', 'find network calls'],
});
// Static analysis
const functions = await client.call('analyze_binary', {
investigationId: investigation.id,
binary: './suspicious-binary',
tools: ['ghidra', 'hopper'],
});
// Filter for network-related functions
const networkFuncs = functions.filter(f =>
f.name.includes('socket') || f.calls.some(c => c.includes('connect'))
);
// Dynamic tracing
for (const func of networkFuncs) {
const trace = await client.call('trace_function', {
investigationId: investigation.id,
functionId: func.id,
duration: 10000, // 10 seconds
});
console.log(`${func.name} called with:`, trace.arguments);
console.log(`Returned:`, trace.returnValue);
}
// Export investigation
const report = await client.call('export_investigation', {
investigationId: investigation.id,
format: 'markdown',
});
console.log(report);
The agent sees a high-level API. The MCP server coordinates Ghidra, Hopper, and frida under the hood.
Technical Verdict
Use REA when:
- You need agents to perform reverse engineering across multiple tools without managing tool-specific sessions
- Your workflow combines static analysis (decompilers, disassemblers) with dynamic tracing (frida, dtrace)
- You want a single MCP interface for binary inspection, runtime behavior, and API-level analysis
- You need credential injection for vendor CLIs (Stripe, GitHub) without exposing tokens to agents
Avoid REA when:
- Your reverse engineering is tool-specific and does not benefit from cross-tool orchestration
- You need real-time collaboration (multiple analysts sharing the same investigation state)
- Your binaries are too large for in-memory investigation context (multi-GB firmware images)
- You require Windows-specific tools (x64dbg, IDA Pro) that lack headless modes
The MCP server architecture works well for agent-driven workflows where the agent decides what to investigate and the server handles tool coordination. It does not replace manual reverse engineering but automates the plumbing between tools.
Source Links
- Primary source: github.com/morluto/rea
- Documentation: morluto.github.io/rea
- Discord community: discord.gg/GkcryMnJDM