@nebuloss/r2-re-mcp
Provides tools for reverse-engineering Broadcom BCM6726b0 WiFi-driver firmware, including stateful radare2 sessions, Thumb-2 disassembly, call graph analysis, emulation, and search capabilities, tailored for the GT-BE98 router's firmware analysis workflow.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@nebuloss/r2-re-mcpthumb disassemble at 0xf335c"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@nebuloss/r2-re-mcp
A custom, lean, task-specific radare2 MCP server for the GT-BE98 / Broadcom BCM6726b0 WiFi-driver reverse-engineering workflow.
Why this exists
The stock r2mcp (on :8765) is thin and stateless: it re-opens/re-analyzes
the binary on every call and returns unbounded text. Agents end up bypassing
it for raw ssh … r2, which floods the context window with megadumps.
This server fixes that by being:
Stateful — one persistent
r2pipesession per opened target, reused across every tool call (keyed by a friendlyname). Analysis/flags/comments accumulate in-process and can be persisted via r2 projects.Token-disciplined — every tool caps output at ≤200 lines AND ≤4000 chars by default, truncating with a clear
…[truncated N lines]marker. No raw megadumps, ever.Recipe-encoded — the team's r2 recipes (esp. the Thumb-2 recipe and the firmware addressing facts) are baked into the tools.
Lean — ~30 small, single-purpose tools (see below), no kitchen sink. Anything not first-class is reachable through the
r2cmdescape hatch.Concurrency-safe — r2pipe is a single pipe; this server is driven by parallel agents, so every
cmd/cmdjon a session is serialized through a per-session promise-chain mutex (each command awaits the previous one before touching the pipe). No interleaved/corrupted output.
It runs alongside the stock r2mcp on a new port (8766) — it does not
clobber :8765.
Related MCP server: re-mcp
CRITICAL addressing & recipe facts (baked in)
Addresses are the firmware virtual address DIRECTLY. e.g. the iDMA gate setter function is at firmware-VA
0xf335c. This is UNLIKE the team's Ghidra setup, which carries a+0x10000image-base skew. No 0x10000 offset is applied here. For the raw blobram.shift.binthe load base is0, so r2-addr == firmware-VA.Thumb-2 disassembly recipe (mandatory).
e asm.bits=16alone does NOT work — r2 will decode ARM-32 garbage at a Thumb address. You must run:e asm.bits=16 ; ahb 16 @ ADDR ; pd N @ ADDRThe
ahb 16 @ADDRanalysis hint is required. Thethumb_disasmtool does exactly this for you.Reference targets in
ram.shift.bin:hme_sys_g = 0xeb700, per-user gateu16 @ 0xebb2c(=hme_sys_g + 0x42c), iDMA gate setter fn@ 0xf335c.Staged binaries (in
RE_BINS, default/opt/re-bins):ram.shift.bin— dongle firmware, ARM Thumb-2, loaded raw (-a arm -b 32, base 0). Thumb is set per-region viaahb.rtecdc.bin— dongle firmware blob.dhd.ko— aarch64 ELF kernel module.hmoswp.elf— ELF.
Analysis depth matters (READ THIS)
The xref, function, and call-graph tools return nothing until the binary is
analyzed. A bare open_target runs light aa (analysis:'basic'), which
finds only a fraction of functions. For real navigation:
open with
open_target({ name, analysis: 'full' })(runsaaa), orcall
analyze({ target, depth: 'full' })afterward.
aaa/emu analysis can be slow on a ~5 MB blob — that's the trade-off.
analyze depths: 'basic'=aa · 'full'=aaa · 'refs'=aar+aac ·
'emu'=aae+aaae.
Analysis auto-persist (no more re-running aaa every restart)
Re-running aaa (~30 s) on every restart is wasteful, so analysis is now
persisted automatically:
After
analyzecompletes it auto-saves the r2 project (e dir.projects=$R2_PROJECT_DIR; Ps <projName>), best-effort (a save failure never fails the tool — it's just reported).open_targetloads a matching project first (e dir.projects=…; P <projName>over the already-open file). If the project already carries functions, the expensiveaa/aaais skipped and the open summary reportsanalysis: recovered from project (skipped aa/aaa)(analysisFromProject=true) — so a reopen is near-instant.save_projectandanalyze's auto-save share the same code path.
Name sanitization (important). r2 6.1.7 rejects project names containing dots (
Ps ram.shift.bin→ "Invalid project name"), which used to make saves silently succeed while reopen never loaded. Names are now sanitized to r2-safe form — every char outside[A-Za-z0-9_]becomes_(soram.shift.bin→ram_shift_bin) — for both save and load. The on-disk project is a directory$R2_PROJECT_DIR/<projName>/containingrc.r2; load existence is checked against thatrc.r2. (BenignERROR: ar: Unknown registerlines that always appear on load are filtered out before judging success.)
Symbol import (bridge from Ghidra)
The blob has no symbols (everything is fcn.000f335c). The team's real
names live in the bethington Ghidra project — export them separately (a
Ghidra script dumping {name, addr, kind} to JSON or CSV under RE_BINS), then:
import_symbols({ target: "ram.shift.bin", file: "bethington-syms.json" })
save_project({ target: "ram.shift.bin" }) # make the names durableAfter that, xrefs / decompile / callgraph render readable names. Inline use is
also supported: import_symbols({ target, symbols: [{ name, addr, kind }] }).
Names are sanitized to be r2-flag-safe ([A-Za-z0-9_.]); kind:"function" also
defines+names the function (af/afn). Addresses are firmware-VA directly (NO
+0x10000 — strip the Ghidra image-base skew during export).
Pagination
The high-volume tools — list_functions, list_symbols, strings, search,
disasm, disasm_function — accept optional offset (default 0) and limit.
Windowing is applied before the output cap; when windowed, a footer
[showing N..M of TOTAL — pass offset=M to continue] is appended so you can page
through without flooding context. Omitting both preserves the original behavior.
Decompilers
decompile probes for the best available decompiler in this order:
r2ghidra (pdg) > r2dec (pdd) > builtin pseudo (pdc). pdg/pdd are
the preferred decompilers — install r2ghidra and/or r2dec on the container
for good output; pdc is the always-present fallback.
The tools (~30, grouped)
addr-or-name: every tool that takes a function/address argument accepts either
a hex/dec address (firmware-VA) or a symbol / flag / function name (resolved
via r2 — ?vi, the flag table, or the function list).
Session & analysis control
Tool | What it does |
| Open/reuse a persistent session from |
| Deepen analysis: |
| Close the session and free its r2 handle. |
Triage / overview
Tool | What it does |
| Consolidated metadata ( |
| Sections/segments ( |
|
|
| Whole-binary strings ( |
Functions / navigation
Tool | What it does |
| Analyzed functions ( |
| One function (addr-or-name): name/addr/size/bits(16=Thumb)/bbs, args/locals ( |
| Whole-function disasm ( |
| Function entry → |
Call graph
Tool | What it does |
| Sites referencing this fn ( |
| Calls made FROM this fn ( |
Disassembly / decompile / memory / search
Tool | What it does |
| Thumb-2 disasm via the mandatory recipe. n=32. |
| Plain ARM disasm ( |
| Disassemble several regions in one call (token saver). Each region |
| Decompile fn at addr: probes r2ghidra |
| Compact hexdump ( |
|
|
| Refs TO addr ( |
| Refs FROM addr ( |
Emulation (ESIL)
Tool | What it does |
| Bounded ESIL run: |
Symbols / types / diff (NEW)
Tool | What it does |
| Bridge from Ghidra. Set flags from |
| Define a C type/struct in the session type DB ( |
| Format/print memory at |
| List known types ( |
| Best-effort cross-binary fn diff. Uses |
Persistence & escape hatch
Tool | What it does |
| Persist flags/comments/analysis via r2 project ( |
| Set a flag ( |
| Escape hatch — run any r2 command for anything not first-class above. Output still capped. |
Stack
TypeScript / Node 20+ / ESM.
MCP:
@modelcontextprotocol/sdk—McpServer+StreamableHTTPServerTransport(streamable-http), bound to0.0.0.0:$R2_MCP_PORT.radare2 via the
r2pipenpm package (one persistent process per target).A
GET /healthendpoint is provided for proxy/systemd checks.
Environment
Var | Default | Meaning |
|
| Listen port (NEW — does not clobber stock r2mcp on 8765). |
|
| Staged binaries directory. |
|
| r2 project storage. |
|
|
|
Deploy / test — ON THE CONTAINER (10.0.50.147)
This repo is source-only. Do not build on dev-code. Deploy to the RE MCP container, which already has radare2 + r2pipe deps from
provision-re-mcp-server.sh.
# 1. get the source onto the container at /opt/r2-re-mcp
# (rsync/scp/git-clone the tools/r2-re-mcp/ dir there)
sudo mkdir -p /opt/r2-re-mcp
sudo rsync -a --delete ./tools/r2-re-mcp/ /opt/r2-re-mcp/ # or git clone + cp
# 2. install deps + build
cd /opt/r2-re-mcp
npm install
npm run build # tsc -> dist/server.js
# 3. install + start the systemd unit (runs alongside r2mcp on 8765)
sudo cp systemd/re-r2-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now re-r2-mcp
systemctl status re-r2-mcp
curl -s http://127.0.0.1:8766/health | jq .Register in a client .mcp.json (streamable-http)
To be fronted by the lab reverse proxy with TLS later; raw form:
{
"mcpServers": {
"r2-re": {
"type": "http",
"url": "http://10.0.50.147:8766/mcp"
}
}
}Quick manual smoke test
After the service is up, drive it through any MCP client (or the inspector) and run, in order:
open_target({ name: "ram.shift.bin" })→ expect a summary: file/opt/re-bins/ram.shift.bin, archarm/32, base0x0, some function count.thumb_disasm({ target: "ram.shift.bin", addr: "0xf335c" })→ expect Thumb-2 instructions (the iDMA gate setter), NOT ARM-32 garbage.xrefs_to({ target: "ram.shift.bin", addr: "0xebb2c" })→ expect the trimmed list of code sites that reference the per-user gate u16.
Then try read_mem({ target: "ram.shift.bin", addr: "0xeb700", len: 64 }) to
peek at hme_sys_g, and save_project({ target: "ram.shift.bin" }) to persist.
Automated live smoke test (test/smoke.mjs)
A runnable MCP client that connects over streamable-http, lists tools, and runs
the core flow open_target → info → analyze → list_functions → function_info → xrefs_to → emulate, printing each (already-capped) result. It needs a live
deployed server (radare2 + a staged binary) — it is not a CI test (CI only
typechecks; radare2 isn't on GitHub runners):
# against a deployed container:
R2_MCP_URL=http://10.0.50.147:8766/mcp SMOKE_TARGET=ram.shift.bin \
npm run smoke
# or locally if the server is up on 8766:
npm run smokeEnv: R2_MCP_URL (default http://127.0.0.1:8766/mcp), SMOKE_TARGET
(default ram.shift.bin), SMOKE_FN (default 0xf335c), SMOKE_XREF
(default 0xebb2c). Exits non-zero on connection or required-step failure.
CI
.github/workflows/ci.yml runs on push/PR: npm ci + npm run build (tsc
typecheck) on Node 20. It deliberately does not run the r2-dependent smoke
test — radare2 isn't available on GitHub runners.
Provisioning note (add to provision-re-mcp-server.sh later)
The provisioning script (tools/provision-re-mcp-server.sh) is not modified
here. To fold this service into provisioning, add a block alongside the existing
r2mcp.service unit, e.g.:
# ---- custom lean r2 RE MCP (alongside stock r2mcp) -----------------------
PORT_RE_R2MCP=8766
git -C /opt/r2-re-mcp pull --ff-only 2>/dev/null || \
git clone --depth 1 <repo>/tools/r2-re-mcp /opt/r2-re-mcp # or rsync from repo
( cd /opt/r2-re-mcp && npm install && npm run build )
cp /opt/r2-re-mcp/systemd/re-r2-mcp.service /etc/systemd/system/
# (then add re-r2-mcp to the `systemctl enable --now …` line and the status loop)(Port 8766; env-var style matches the other units.)
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nebuloss/r2-re-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server