remcp
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., "@remcpDisassemble this ELF binary and list its exported functions."
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.
remcp — a general reverse-engineering MCP server
A read-only binary analysis engine exposed over MCP. Built from first principles after auditing a predecessor whose defects are documented here and pinned by regression tests, so they cannot come back quietly.
Status: working, tested. 139 tests — 138 passing, 1 environment-dependent skip — plus an end-to-end MCP wire test. Licensed MIT.
git clone https://github.com/Mnehmos/mnehmos.reverse-engineer.mcp.git
cd mnehmos.reverse-engineer.mcp
pip install -r requirements.txt
python server.py # stdio MCP server
python -m pytest tests/ -q # 138 passed, 1 skipped
python tests/wire_test.py # end-to-end over stdioWhat it analyzes
Axis | Support |
Containers | PE/COFF, ELF, Mach-O (thin), headerless blobs |
Architectures | x86-16/32/64, ARM, AArch64, MIPS 32/64, PowerPC 32/64, SPARC, RISC-V — 19 arch/bits/endian combinations |
Bitness | Derived from the header. A mismatch is refused, never guessed |
Symbols | PE imports/exports, ELF symtab + dynsym + DT_NEEDED, Mach-O symtab + dylibs |
Fat Mach-O | Detected and refused with instructions, not silently sliced |
Anything unsupported raises a typed error naming what was detected and what to do instead. Nothing is analyzed on a guess.
Related MCP server: easy-code-reader
The seven design principles
Each one exists because its absence was measured as a defect.
1. An address is meaningless without its space
File offsets, RVAs and VAs are three different spaces. remcp/image.py owns
every conversion; they go through the section table and return None for
addresses that do not map.
The predecessor computed string addresses as image_base + file_offset.
Measured against a real PE, 1.17% of the resulting addresses were correct
— and the coincidental overlap was the only reason any were. The matching
signal built on them was dead in every shipped database.
test_naive_base_plus_offset_is_wrong asserts the shortcut is not
equivalent, so nobody reintroduces it thinking it is harmless.
2. Decode with the declared architecture, or refuse
remcp/disasm.py derives the capstone mode from the image header. An
unsupported combination raises UnsupportedError listing what is supported.
The predecessor hardcoded 32-bit x86 in three places. Fed a 64-bit PE it
returned 1,187 fabricated call edges and zero warnings, one call after
its own inspector had correctly printed AMD64 (64-bit x64).
3. Warnings travel with the answer
Every response is an evidence envelope:
{ "ok": true, "target": {"sha256": "...", "arch": "x86", "bits": 64},
"method": "...", "reliability": "sound|degraded|unreliable",
"warnings": [{"code": "...", "detail": "...", "impact": "..."}],
"result": { ... } }The predecessor detected packed code correctly and printed the warning to
stderr, which no MCP client surfaces to a model. Its re_callgraph on
the default target returned function_count: 9 alongside 21,339
fabricated edges, with nothing in the payload marking them as noise.
4. Remove the foot-gun rather than documenting it
High-entropy executable sections are not swept by default. On a real
16.5 MB CEG-encrypted image, sweeping .text produced 5,080,489 "instructions"
in 394 seconds — all of it noise.
predecessor: 355.4 s per call, every call, 21,339 fabricated edges, no warning
remcp: 0.00 s, an in-band warning naming the encrypted sectionsinclude_packed=true is the deliberate override, and it attaches an
unreliable warning of its own.
5. Disassemble once, answer many questions
remcp/codeindex.py does one linear sweep and records cross-references, the
call graph, function starts and instruction counts. It is cached by content
hash, so a rebuilt binary gets a new key automatically and there is no
invalidation to forget.
23.4 MB unpacked PE, 5.0 M instructions | |
cold index build | 97.8 s |
warm (cached) | 2.7 s — 37× |
found | 42,085 functions, 278,187 calls, 499,814 jumps, 67,819 indirect sites |
The predecessor rebuilt the whole graph on every call — 355 s for a smaller file — then discarded everything except one function's neighbourhood.
6. A cross-build signal must not depend on the layout
This is the audit's central finding. The predecessor's matcher advertised four-signal triangulation. Measured over its own shipped databases:
min=0.55: 559 matches | without byte-identical sig: 0 | refs>0: 0
min=0.40: 2325 matches | without byte-identical sig: 1764 | refs>0: 0
min=0.30: 3363 matches | without byte-identical sig: 2802 | refs>0: 0
distinct scores: [0.70, 0.75] theoretical max 1.00Three independent defects:
The "masked" signature masked nothing but branch displacements —
fixed = min(fixed, len(insn.bytes))wherefixedalready waslen(insn.bytes). Absolute addresses stayed literal.The 3-gram set came from the same 64-byte window as the signature, so the two were perfectly collinear:
sig_eq == 1impliedngram == 1in 559 of 559 matches.The reference signal compared RVAs across builds. The same global sits at a different RVA in each build, so the intersection was empty by construction — 82% of functions carried reference data and the signal contributed to zero matches at any threshold.
remcp compares what a function references, not where:
referenced string contents
referenced import names (
kernel32!CreateFileW)non-address constants — magic numbers, masks, seeds
mnemonic 5-grams over the whole function, a deliberately different window from the signature so the two can disagree
structural shape; masked signature contributes only 0.05
Signals with no evidence on either side abstain and the weights renormalize, instead of scoring 0.0 and capping every achievable score.
Verified working across architectures. Matching the 32-bit and 64-bit builds of the same program — where byte signatures cannot match:
19 matches, all with >=2 independent agreeing signals
0 relying on a byte-identical signature
18 distinct scores (predecessor: 2)
0 ambiguous7. Where a claim can be checked, check it
The predecessor's re_save_symbol documented a six-tier policy and
implemented one rule. Its 0.80 gate tested a predicate identical to its 0.60
gate and could never fire independently. Unenforced: per-class caps (five
stored entries sat at 0.60 under a class capped at 0.40), the
two-independent-classes rule, the provenance vocabulary
(structural-analysis — not a policy term — was the most common class in the
database), and evidence contents, which were never inspected and could be
empty. A caller wanting 0.95 typed dynamic_trace and got it.
remcp/kb.py splits provenance in two:
Verifiable — checked against the binary at write time; a false claim is rejected
Class | Cap | Checked by |
| 0.40 | the string exists and is referenced from inside this function |
| 0.40 | a call or reference to that named import lies in this function |
| 0.40 | the constant is in the function's fingerprint |
| 0.40 | the claimed call edge exists |
| 0.60 | a real match result with score ≥ 0.55 and ≥ 2 agreeing signals |
| 0.95 | the image's own symbol table names this address |
| 0.95 | the export table names this address |
Asserted — cannot be checked from static bytes; require a named artifact and are stored as claims
structural_guess (0.20), community_source (0.60), dynamic_trace (0.80),
experimental_manipulation (0.95), authoritative_source (1.00).
Also enforced: a closed vocabulary, per-class caps, ≥ 2 independent classes above 0.40, RVA-first storage with the VA always derived, build ids bound to a SHA-256, atomic writes with a backup, and prior evidence preserved as history rather than overwritten.
Every stored record says which classes were verified and which were trusted. That boundary is stated rather than papered over — a debugger trace is not checkable here, and the record admits it.
Deep algorithm recovery
Two capabilities, aimed at the question a disassembly listing cannot answer: what is this code actually doing?
JVM decompilation
Bytecode is typed, addresses locals by index, keeps method and field references symbolic, and carries an explicit exception table. All the information a native compiler destroys is still present, so real decompilation is achievable rather than approximated.
tests/fixtures/Algo.java is compiled and checked in beside its .class, so
the decompiler is tested against known source rather than plausibility.
Given this input:
public int sumEven(int[] xs) {
int total = 0;
for (int i = 0; i < xs.length; i++) {
if (xs[i] % 2 == 0) { total += xs[i]; }
}
return total;
}re_jvm_decompile recovers:
public int sumEven(int[] xs) {
int total = 0;
int i = 0;
while (i < xs.length) {
if (xs[i] % 2 == 0) {
total += xs[i];
}
i++;
}
return total;
}Parameter and local names come from the LocalVariableTable; types are
recovered from descriptors; for appears as the while javac actually
emitted. Also recovered: switch with case labels, try/catch with its
handler body, this.counter++, string concatenation folded back from both
the StringBuilder chain (Java 8) and invokedynamic
makeConcatWithConstants (Java 9+), and ternaries recovered from the
diamond that materialises a boolean as a value.
Measured on a real 1,767-class jar: 2,241 methods decompiled in 0.3 s, zero exceptions, 89% fully structured.
The structured flag is the point. Irreducible control flow, jsr/ret
subroutines and obfuscated jump tables produce structured: false and a
best-effort listing. A decompiler that always emits clean-looking output is
wrong some of the time and never says which time.
Two correctness bugs found by these tests and fixed:
h = h * 31 + cwas folded toh *= 31 + c, which meansh * (31 + c). Compound assignment now folds only when the right-hand side is a single operand (_has_toplevel_operator).catchhandler bodies were dropped entirely:safeDivdecompiled to just itstrybody with the whole clause missing.
Native algorithm identification
Crypto and hashing primitives are recognisable by the exact constants their
specifications pin. re_algorithms scans for 22 catalogued algorithms across
two evidence sources -- byte tables in data, and immediates recovered from
the code index for optimisers that materialise constants inline.
The catalog's value depends on not crying wolf, so three guards apply:
Guard | Why |
|
|
| a single hit is a |
| MD5 and SHA-1 share four init words; an identification requires a marker unique to that algorithm |
Verified against bcryptprimitives.dll and ntdll.dll: SHA-1, SHA-224/256,
SHA-384/512, MD5, CRC-32 and CRC-32C identified with discriminating markers
and attributed to the functions referencing them. secp256k1 matched one
32-bit word in ntdll and is correctly reported as a lead, not Bitcoin
code.
re_function_cfg adds the structural half: basic blocks, dominator-based
natural loops, cyclomatic complexity, exit points. A one-block self-loop was
being reported as four blocks -- the natural-loop walk seeded its worklist
with the header and escaped backwards through the header's external
predecessors. Fixed in both the native and JVM implementations.
Tools
Tool | Purpose |
| Format, arch, sections, imports/exports, and whether code analysis is possible. Start here |
| Container type from magic bytes, without parsing |
| Convert between file offset, RVA and VA through the section table |
| Strings with correct VAs; regex filter; |
| Disassemble at an address — the predecessor had no disassembler at all |
| References to a VA or a string, from code operands and data pointers |
| Discovered functions with the evidence source for each |
| Function-attributed graph; |
| Layout-invariant fingerprint of one function |
| Whole-image fingerprint database |
| Cross-build matching with corroboration required |
| The policy, introspectable — caps, vocabulary, what is verified |
| Bind a build id to a SHA-256 |
| Record a symbol; verifiable provenance is verified |
| Query the knowledge base |
| JVM: .class/.jar inventory, packages, manifest, constant-pool strings with source entry |
| .NET: CLR target, metadata streams, table row counts, #Strings identifiers, #US user strings |
| Bounded hex+ASCII dump of raw bytes at a file offset, RVA or VA |
| Methods with signatures, complexity and loop counts |
| Bytecode listing with constant-pool references resolved |
| Bytecode back to readable Java |
| Per-method blocks, loops, exception table |
| Statically resolve every symbolic reference a plugin makes against a host classpath; report what would fail at first call |
| Plugin code that loads but does nothing: unimplemented abstract methods, overloads that match no supertype, class names in data that no longer resolve |
| Classes defined in more than one jar on a shared classpath, where load order silently picks the winner |
| Identify crypto/hash/PRNG by constants, attributed to functions |
| Native per-function blocks, loops, complexity |
| Cache stats and clearing |
re_disasm matters more than it looks. The predecessor exposed no
disassembly and no decompilation — only a reader for a pre-populated cache —
so an agent following its documented workflow could never actually read
code.
re_java and re_dotnet exist because two field tests left the tool. A JVM
jar sniffed as "zip" and the analysis exited to hand-rolled constant-pool
mining; a .NET assembly identified fine but the CLI metadata walk was
reimplemented outside the engine. Both gaps are now first-class: class files
and CLI metadata are structured inventories, not VA spaces, so these modules
do not pretend to be Images — no fabricated addresses, constant-pool indices
reported as indices. re_read closes the third escape: reading the exact
bytes behind any reported address without dropping to a shell.
Read-only by design
There is no patch, write, inject, or execute tool, and none is planned. Runtime work belongs in a debugger a human is driving. The only writes are to the knowledge base and the analysis cache.
Other eliminations: a 512 MB file-size ceiling
(REMCP_MAX_FILE_BYTES), a response-size clip that tells you to narrow the
query instead of flooding the context (REMCP_MAX_RESPONSE_CHARS), and
bounded index growth so one huge image cannot produce an unbounded cache
entry.
Nothing escapes a tool
Every handler is wrapped by @tool, which catches BaseException and
returns a structured error.
The predecessor's load_pe raised SystemExit on a non-PE file.
SystemExit is a BaseException, so it slipped past FastMCP's
except Exception and killed the stdio loop. Wire-tested against the live
predecessor:
re_form_type('98') -> {"ordinal": 98, "record": "ECZN"} ok
re_inspect('README.md') -> <TIMEOUT: no response> dead
re_form_type('98') -> <STDOUT CLOSED> dead
stderr: BaseExceptionGroup: unhandled errors in a TaskGroupReachable from six tools with any .esp, .txt or .json path — the most
likely mistake an agent makes, costing the whole session's tooling.
tests/wire_test.py drives seven failure paths — non-binary file, directory,
missing file, unmapped address, unparseable address, invalid regex, missing
database — and probes the server after each one. All pass.
Configuration
Variable | Default | Purpose |
|
| Analysis cache |
|
| Knowledge base |
| 536870912 | File-size ceiling |
| 120000 | Response clip |
The knowledge base ships empty and is created on first write — no analyzed-target data comes with the repo.
No hardcoded absolute paths. The predecessor had five, plus a game-directory
fallback, and its .mcp.json pointed at a different checkout than the one it
shipped in.
Register with an MCP client
{
"mcpServers": {
"remcp": {
"command": "python",
"args": ["/path/to/mnehmos.reverse-engineer.mcp/server.py"]
}
}
}Point command at an interpreter that actually has the requirements
installed — a bare python may resolve to an environment without capstone
or pyelftools, which kills the server at import time.
Layout
server.py 28 MCP tools; every one exception-wrapped
remcp/
errors.py typed errors; never SystemExit from a library path
evidence.py envelopes and in-band warnings
image.py the address model -- sections, three spaces, one conversion path
formats/
__init__.py magic-byte detection, target validation, dispatch
pe.py elf.py macho.py raw.py
classfile.py JVM .class/.jar inventory behind re_java
dotnet.py CLI metadata inventory behind re_dotnet
disasm.py arch-derived decoder, resilient sweep, correct masking
codeindex.py one sweep -> xrefs, calls, jumps, indirect sites, functions
strings.py extraction with correct VAs, both encodings
xrefs.py code-operand + data-pointer passes
graph.py function-attributed call graph
fingerprint.py layout-invariant features and matching
cache.py content-addressed, gzip, atomic, corruption-tolerant
kb.py policy with binary-backed verification
jvm/
bytecode.py full opcode table, wide/switch/indy decoding
classfile.py constant values and attribute bodies (Code, LVT, exceptions)
cfg.py blocks, dominators, natural loops, post-dominators
decompile.py stack reconstruction + control-flow structuring
linkage.py static resolution of a plugin's references to its host
checks.py health checks over linked classes
algo/
constants.py 22-algorithm marker catalog with discriminator rule
nativecfg.py native blocks, loops, complexity
tests/
test_engine.py 37 tests, one per defect class
test_policy.py 22 tests pinning every documented policy rule
test_formats_meta.py 13 tests for container detection
test_jvm.py 32 tests against compiled-from-source ground truth
test_linkage.py 20 tests for host/plugin linkage and health
test_algo.py 15 tests, mostly false-positive guards
fixtures/Algo.java the ground truth, checked in beside its .class
wire_test.py end-to-end MCP over stdioKnown limits
Stated rather than discovered later:
Indirect calls are recorded as sites, not resolved to targets. Virtual dispatch is visible as "dispatch happens here", which is strictly better than the predecessor's silence but is not a resolved edge. Devirtualization would need vtable recovery.
Function boundaries are estimates when an image has no symbols. The end is the next known start, refined by a terminator scan. Reported as
estimated_extent, with a warning when there is no symbol evidence.Mach-O is parsed but untested against a real binary on this machine. The address translation is covered by a synthetic fixture; treat real-world Mach-O as unproven until exercised.
No native decompiler. JVM bytecode decompiles to Java; x86/ARM gets disassembly, control flow and algorithm identification, not pseudocode. Lifting native code to an IR is the next step, and a Ghidra headless bridge remains the pragmatic alternative.
JVM structuring is 89% on real jars. The remaining 11% is reported as
structured: false, not disguised.The algorithm catalog is 22 entries. An algorithm outside it, or a bitsliced implementation with no constant tables, produces nothing. Absence of a hit is not evidence of absence.
Linear sweep decodes data as instructions in code sections that interleave data, producing some spurious instructions. Recursive-descent from known entry points would be more precise and is not implemented.
data_refsfinds pointer-width matches, which can coincide with unrelated data. Each is reported with its section and alignment so the caller can judge.
License
MIT © 2026 Mnehmos.
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.
Related MCP Connectors
Repository knowledge graph MCP server for codebase understanding and debugging.
MCP server for developer documentation, generated by doc2mcp.
Read-only verifier for 25 ProofRelay MCP tools and non-confidential evidence bundles.
MCP server for dev documentation, generated by doc2mcp.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables LLMs to autonomously reverse engineer applications by exposing Ghidra's core functionality through MCP tools. Supports decompiling binaries, analyzing code structure, and automatically renaming methods and data.2Apache 2.0
- AlicenseNot gradedqualityDmaintenanceAn MCP server for intelligently reading Java source code, supporting extraction from Maven dependencies and local projects with dual decompilers.155Apache 2.0
- AlicenseNot gradedqualityBmaintenanceExposes Ghidra reverse engineering capabilities via MCP, enabling LLMs and agents to analyze binaries, decompile, search, and edit programs headlessly or with GUI integration.423Apache 2.0
- AlicenseNot gradedqualityCmaintenanceProduction-grade MCP server for universal reverse engineering automation.75GPL 3.0