An MCP (Model Context Protocol) server for inspecting and editing binary files: hex read/write, byte-pattern search, string extraction, file-type detection (magic bytes + entropy), hex dumps, hashing, binary diffing, XOR transforms, entropy scanning, file carving, and PE/ELF section listing.
Zero dependencies. Unlike the other mcp-* servers in this repo, this
one does not use the mcp Python SDK (pip install mcp) or any other
third-party package — not even for the MCP protocol itself. Everything,
including the JSON-RPC/stdio transport, is implemented with the Python
standard library only (json, struct, re, math, hashlib, zlib,
pathlib, sys). Nothing to install beyond Python itself.
No pip install needed. Just point your MCP client at:
python K:\mcp-tools\mcp-binary-tools\mcp-binary-tools.py(adjust the path if you've moved this folder elsewhere). It's a plain stdio MCP server — any MCP-compatible client can spawn it directly.
Claude Desktop — add to claude_desktop_config.json:
{
"mcpServers": {
"binary-tools": {
"command": "python",
"args": ["K:\\mcp-tools\\mcp-binary-tools\\mcp-binary-tools.py"]
}
}
}Goose — add to ~/.config/goose/config.yaml (key names have shifted
between Goose versions — check goose configure / your version's docs if
this doesn't match):
extensions:
binary-tools:
type: stdio
cmd: python
args: ["K:\\mcp-tools\\mcp-binary-tools\\mcp-binary-tools.py"]
enabled: trueAny other MCP client: configure it to run
python K:\mcp-tools\mcp-binary-tools\mcp-binary-tools.py as a stdio-based
MCP server — that's the whole interface, no ports, no config files, no
extra setup involved.
- Python 3.10+ (uses
from __future__ import annotationsandX | Nonestyle type hints) - That's it. No
pip install, no bundled executables, nobin/folder.
Read raw bytes at a given offset. Returns hex (compact), hex_spaced
(byte-separated), and ascii (printable chars, . for the rest), plus
file_size and an eof flag. Capped at 65536 bytes per call — page
through larger regions with offset.
Patch bytes into an existing file in place, hex-editor style:
with open("firmware.bin", "r+b") as f:
f.seek(offset)
f.write(bytes.fromhex(hex_data))hex_data accepts spaces/underscores/0x prefixes and is cleaned up
automatically ("DE AD BE EF", "DEADBEEF", "0xDE 0xAD..." all work).
Writing past the current end of file is refused unless extend=true,
which zero-pads the gap. This tool only patches files that already exist —
it will not create a new one. Capped at 10 MB per call. There is no undo
or automatic backup — this modifies the file on disk directly.
Search for a byte pattern given as hex. ?? acts as a single-byte
wildcard: "DE AD ?? EF" matches DE AD + any byte + EF. Returns match
offsets (decimal + hex) and the matched bytes. If length is omitted, the
scan covers the rest of the file capped at 32 MB (scan_truncated: true
signals when that cap was hit — page through with offset/length for
larger files). Matches are non-overlapping.
Extract printable strings, like the classic strings command-line tool.
encoding is "ascii", "utf16le" (common in Windows binaries), or
"both". Same offset/length paging and 32 MB scan cap as binary_search.
Detects file type from magic bytes/header structure and reports Shannon entropy over a sample of the file (hinting at compressed/encrypted/packed content when it's high, e.g. > 7.5).
Nearly every signature comes from magic_numbers.yaml — an editable, human-readable database of 816 signatures, grouped by category:
| Category | Contents |
|---|---|
archives |
ZIP family (+ OOXML/EPUB/JAR sub-variants), RAR, 7-Zip, GZIP (+ generic fallback), TAR (generic + POSIX/GNU ustar), BZIP2 (+ legacy fallback), XZ, Zstandard, LZ4, CAB, ISO 9660, zlib streams (all 8 level/dictionary combinations), LZH, RNC, lzip, cpio, RPM, ARJ, Zoo, FreeArc, and dozens more compressors/archivers |
images |
JPEG/PNG/GIF/BMP/WebP/TIFF/ICO/PSD plus Canon RAW, BigTIFF, JPEG 2000/XL, SPIFF, DPX, and dozens of legacy/scientific/retro formats (FITS, Sun Rasterfile, XCF, XPM, ...) |
video_audio |
MP4/MOV/MP3/WAV/AVI/MKV/FLAC/OGG/WMV-WMA/MIDI plus Shockwave/Director (disambiguated), AMR, SILK, FLV, DSS, EnCase EWF, and many RIFF/IFF-family formats |
documents |
PDF, Office (DOCX/XLSX/PPTX/DOC/XLS/PPT + several detected sub-variants like encrypted/Java-archive ZIPs), RTF, ODF, EPUB, XML (+ several specific XML-dialect signatures), HTML, PostScript/EPS |
executables |
EXE/DLL (+ several specific MZ-based tool signatures), ELF, Mach-O, Java Class, DEX, WebAssembly, Shell Script, and more |
fonts |
TrueType, OpenType, WOFF/WOFF2 |
disk_images |
Disk/CD/cartridge images, VHD/VHDX/WIM, filesystem journals, EnCase/AFF forensic images |
virtual_machines |
VMDK, VDI, qcow |
databases |
SQLite-adjacent and legacy spreadsheet/database formats (Lotus 1-2-3, MS Access/Money, Approach, columnar formats like Parquet/Avro/ORC, and many more) |
network |
pcap / pcap-ng packet captures |
text_encoding |
Unicode/text byte-order-marks (UTF-8/16/32/7, SCSU, EBCDIC) and XML-prolog encoding signatures |
email |
mbox-family formats |
security |
Certificates, private/public keys (PEM, PuTTY, OpenSSH), keystores, CrowdStrike channel files, and more |
game_data |
ROMs and engine/resource files (Doom WAD, Unreal, Roblox, NES, ...) |
retro_computing |
Commodore/Amiga/Atari/CP-M/Palm/AppleWorks-era formats |
scientific |
FITS (full signature), HDF, DICOM, NIfTI, GRIB, NetCDF |
3d_models |
glTF, Blender, voxel/mesh formats |
misc |
PGP keyrings/encrypted data plus several hundred assorted formats (mostly from the largest source below) that don't fit elsewhere |
extras |
mcp-binary-tools' original built-in signatures, plus every entry from the sources below that is itself too generic to safely place in a topic category (bare RIFF, a bare JPEG SOI marker, a PDF signature missing its trailing -, ...) — checked last, after everything above, specifically so these deliberately-generic fallbacks never shadow a more specific entry that happens to live in a different category |
Sources: every entry except mcp-binary-tools' own original signatures
is transcribed from one of the four sources listed at the bottom of this
README — see that section for which source covers what, and how real
overlaps/conflicts between them were resolved. The short version: exact
or near-redundant duplicates were skipped; genuinely ambiguous shared
signatures were kept as multiple entries with a note explaining the tie
(e.g. Java class files vs. Mach-O Universal binaries, or the ~90 formats
across the last source that happen to share a signature with something
else); specific-vs-generic pairs were kept as both, ordered so the
specific one is checked first (Canon CR2 before generic TIFF, POSIX/GNU
ustar before generic TAR, a confirmed-Exif JPEG check before the looser
one, UTF-32 BOMs before UTF-16 BOMs, dozens more from the largest source);
and a few entries were upgraded in place to a fuller, more precise
signature found in a later source (WMV/WMA's full 16-byte GUID, FITS's
full 30-byte header record).
Add new entries or whole new categories to magic_numbers.yaml without
touching the Python code — see the schema comment at the top of the file
(it also documents the note field). A match reports
"source": "magic_numbers.yaml" plus the matched entry's category,
description, extensions, and note (when present).
The file is parsed by a small hand-written loader in the script
(load_simple_yaml) — not a general YAML parser, just enough for this
file's flat "category → list of name/extensions/offset/hex/
ascii/description/note mappings" shape, kept dependency-free on
purpose. If magic_numbers.yaml is missing or fails to parse, a warning
is logged to stderr (not stdout) and magic_analyze falls back to a
minimal built-in check ("source": "built-in") that only recognizes
generic RIFF/ftyp containers and otherwise reports "unknown" — detection
quality depends on this file being present.
Independent of which of the two above finds a match, PE and ELF files
additionally get a details block (machine type, PE32/PE32+, DLL vs EXE
for PE; class, endianness, type, machine for ELF) from structural header
parsing that can't be expressed as a simple byte pattern.
Classic hex-editor dump: 16 bytes per row, hex on the left, ASCII on the right.
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 |MZ..............|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
Capped at 65536 bytes per call.
Compute one or more hashes/checksums of a whole file, streamed in 1 MiB
chunks so it never loads a large file entirely into memory. Supported
algorithms: md5, sha1, sha256, sha384, sha512, sha3_256,
sha3_512 (all via hashlib), and crc32 (via zlib.crc32).
Byte-for-byte comparison of two files over the same offset/length range.
Returns each contiguous differing region (offset, length, and up to a
32-byte hex preview from each file), not an aligned/LCS-style text diff —
appropriate for binary files where insertions/deletions are rare and you
mainly want to know which bytes changed. If the files are different
sizes, only the range both actually have is compared, and a
size_mismatch_note says so.
XOR a byte range against a repeating hex key (key_hex can be one byte or
several, e.g. a 4-byte key "DE AD BE EF" repeats across the range).
Defaults to a preview only — returns the transformed bytes as hex/ASCII
without touching the file, capped at 65536 bytes, useful for testing a
candidate key against a suspected simple-XOR-obfuscated region before
committing to anything. Pass write=true to write the transformed bytes
back into the file in place instead (capped at 10 MB per call, same as
binary_write).
Shannon entropy in a sliding window across a file, instead of the single
whole-sample value magic_analyze reports — useful for spotting where
in a larger file a packed/encrypted/compressed region starts and ends
(e.g. an embedded payload inside an otherwise plain-text or structured
file). Windows are non-overlapping and window_size bytes each; if that
would produce more than max_results windows, the window size is
automatically coarsened (and the actual size used is reported back) so
the response stays a manageable length. Each window over entropy 7.5 is
also counted in high_entropy_window_count as a quick signal.
Scans for magic_numbers.yaml signatures at any
offset in the file, not just the start — e.g. finding a PNG or ZIP hiding
inside a firmware dump or memory image. Only signatures whose real-world
position is offset 0 are used (a TAR-at-257 or ISO-at-32769 style
signature only means anything relative to a whole file's own start, not
an arbitrary carve position). min_signature_length filters out very
short/generic signatures that would otherwise produce a lot of noise
against padding/zero-filled regions; categories narrows (and speeds up)
the scan to specific magic_numbers.yaml categories. Scanning the full
default 32 MB range against all ~650 offset-0 signatures typically takes
well under a second per MB on ordinary hardware, but large ranges are
still slower than the other single-pass scanning tools since every
signature is checked independently — narrow with offset/length or
categories for faster interactive use on big files.
Lists a PE (Windows .exe/.dll/.sys) file's section headers: name,
virtual address/size, raw (on-disk) offset/size, and characteristics
decoded into flags (executable, readable, writable,
contains_code, ...). Extends the structural PE parsing magic_analyze
already does (machine type, PE32 vs PE32+, DLL vs EXE) with the section
table.
Lists an ELF (Linux/Unix executable/object/shared-library) file's section
headers: name (resolved via the file's own .shstrtab), type (PROGBITS,
SYMTAB, DYNAMIC, ...), flags (alloc, exec, write, ...), address,
file offset, and size. Handles both 32-bit and 64-bit, either endianness.
Reports section_count: 0 with a note (rather than an error) for a
stripped binary that has no section header table at all.
binary_writeis destructive and has no undo, and so isxor_transformwhen called withwrite=true(its default,write=false, is read-only). Both patch the target file directly on disk with no confirmation prompt, dry-run mode, or backup — that's the caller's responsibility. Copy the file first if you want to be able to revert.- Every other tool (
binary_read,binary_search,strings_search,magic_analyze,hex_dump,file_hash,binary_diff,entropy_scan,file_carve,pe_sections,elf_sections, andxor_transformin its default preview mode) is read-only and never modifies anything on disk. - Paths are resolved with
Path.expanduser().resolve()— there is no sandboxing to a specific directory. This server can read/write anywhere the running Python process has filesystem permissions, by design (it's a general-purpose binary analysis toolbox, not a scoped file-upload service). - Large-file protections are per-call caps, not full streaming (except
file_hash, which genuinely streams the whole file in 1 MiB chunks and has no size cap):binary_read/hex_dump/xor_transformpreview mode cap at 64 KB per call,binary_write/xor_transformwrite mode at 10 MB per call, andbinary_search/strings_search/entropy_scan/file_carve/binary_diffscan at most 32 MB per call by default. Useoffset/lengthto page through bigger files. A pattern, string, or diff region that straddles the boundary between two separately-paged calls can be missed — if that matters, pass an explicitlengthlarge enough to cover the region of interest in one call.
MCP's stdio transport is newline-delimited JSON-RPC 2.0: one JSON object
per line on stdin (requests/notifications from the client) and one per
line on stdout (responses from the server). mcp-binary-tools.py
implements exactly that surface by hand:
initialize→ returnsprotocolVersion,capabilities: {tools: {}},serverInfo, and a shortinstructionsstring.notifications/initialized→ acknowledged silently (it's a notification, no response is sent).tools/list→ returns each tool'sname,description, and JSON SchemainputSchema.tools/call→ dispatches to the matching Python function by name; exceptions are caught and returned asisError: truetool results (not JSON-RPC protocol errors), so the calling model sees a readable error message instead of a broken connection.ping→ returns an empty result.
stdout is reserved exclusively for JSON-RPC messages — nothing else in this script prints to stdout.
mcp-binary-tools.py - the entire server: tool implementations, tool
registry, the magic_numbers.yaml loader, and the
native MCP/JSON-RPC stdio loop
magic_numbers.yaml - editable file-signature database used by
magic_analyze (see that section above)
magic_numbers.yaml must sit next to mcp-binary-tools.py (the script
locates it via Path(__file__).resolve().parent, independent of the
current working directory) — if it's missing or fails to parse,
magic_analyze keeps working but loses almost all named-format detection
(see the magic_analyze section above for exactly what the minimal
built-in fallback still covers).
magic_numbers.yaml's signatures were transcribed from:
- Ilias1988/Magic-Bytes-List —
the original
archives,images,video_audio,documents, andexecutablesentries. - leommoore's File Magic Numbers gist —
10 more
imagesentries (FITS, Graphics Kernel System, IRIS rgb, ITC, NIFF, PM, Sun Rasterfile, Xfig, XPM, XCF Gimp), thedocumentsPostScript entry, and the originalmisccategory (the 4 PGP entries). Rows from this source that exactly duplicated an entry already transcribed from the first source (plain JPEG/PNG/TIFF/GZIP/ZIP/MZ/ELF/TAR, in each case with the same bytes) were skipped rather than re-added; rows with no fixed signature at all (Targa, X11 Bitmap, TAR pre-POSIX) were skipped too. Two rows described the same format as an existing entry but with a shorter, looser signature (Bzip's 2-byte magic vs. the existing 3-byte BZIP2 signature; gzip's 2-byte magic vs. the existing 3-byte GZIP signature) — both were kept as fallback entries positioned right after the more specific one, so the specific label always wins first (see thenotefield onBzip (legacy)andGZIP (generic)in the YAML for the detail). - Wikipedia: List of file signatures —
everything else: the rest of
images,video_audio,documents,executables, andarchives, plus every new category fromfontsonward. This table is far larger than the first two sources (~340 rows), so the same duplicate/redundant-row skipping rules applied, plus a few extra cases worth calling out:- Genuinely ambiguous shared signatures (kept as separate entries,
each with a
note): Mach-O Universal Binary vs. Java Class (bothCA FE BA BE), and Macromedia Director vs. Adobe Shockwave (initially looked identical at 8 bytes, but the full 12-byte signatures in the source table turned out to disambiguate them cleanly - see below). - Specific-before-generic pairs (both kept, ordered so the specific
one wins): Canon RAW/CR2 before generic TIFF, Canon CR3 before the
generic
MP4ftyp entry, a confirmed-Exif JPEG check before the looser APP1-marker one, Encapsulated PostScript v3.0/v3.1 before generic PostScript, POSIX/GNUustarbefore generic TAR, AMR and SILK audio before Shell Script (both start with the Unix shebang bytes#!), UTF-32LE/BE byte-order-marks before UTF-16LE/BE ones (a UTF-16LE BOM is a byte-for-byte prefix of a UTF-32LE BOM), and a Lotus 1-2-3 v1 spreadsheet signature beforeCURinextras(they share their first 4 bytes;extrasis checked last specifically because of cases like this one). - In-place upgrades to a fuller, more precise signature found in
this source:
WMV/WMAnow uses the full 16-byte ASF Header Object GUID instead of an 8-byte prefix, andFITSnow uses the full 30-byte first-header-record text instead of just the 6-byteSIMPLEkeyword. - Parsing quirks in the source table itself, resolved by hand: a few multi-line signatures (FITS's full record, AppleWorks 5/6, the VirtualBox VDI comment header, XML's UTF-32 prolog variants) are wrapped across several display lines in the wiki table in a way that looks identical to genuinely-separate alternative signatures (like GIF87a vs. GIF89a) - each was checked against the raw source and reassembled into one correct signature by hand. The Director/Shockwave pair above is the same story: the table's first row for each format only showed 8 bytes, but the other rows in the same group had the full 12 bytes needed to actually tell them apart.
- Rows with no fixed signature (Targa, X11 Bitmap, TAR pre-POSIX, COM,
.pyc, MXF's variable "run-in", a footer-relative Apple Disk Image signature) or too weak to be useful (a lone0x00byte, an all-zero Palm PDB signature) were skipped, same as for the second source.
- Genuinely ambiguous shared signatures (kept as separate entries,
each with a
- Gary Kessler's File Signatures Table —
by far the largest source (~540 header signatures covering ~630 named
formats, since many entries share one signature), adding to every
category above plus roughly 400 entries in
misc. Its HTML table pairs each signature with one or more "extension + description" rows, so a single magic number legitimately maps to several named formats far more often than in the other three sources - this is where most of thenote-carrying "shares this exact signature with N other formats" entries come from. Skipped as usual: exact duplicates, no-fixed-signature rows, trailer-only (end-of-file) signatures, and single-byte-style signatures too weak to be useful (0x00,0x47,0xFF, ...) - plus a few rows Kessler himself flags as speculative ("possibly, maybe, might be a fragment of an Ethernet frame...").- Specific-before-generic pairs: ~40 more cases in the same spirit as
the Wikipedia source (e.g. a Synology-specific 4-byte GZIP variant
before generic GZIP,
EF BB BF 3C 3Fbefore the plain UTF-8 BOM,FF D8 FF E0 ... 4A 46 49 46 00before the existing JPEG/JFIF entry). Where several of Kessler's own entries needed inserting at the same point (e.g. three increasingly-specific XML-prolog signatures all ahead of generic XML), they're ordered longest-signature-first among themselves too, for the same reason. - Deliberately-generic entries moved to
extras: a handful of Kessler's signatures are the bare prefix of a whole family of more specific signatures spread across multiple categories - bareRIFF(shared by Windows animated cursors, CorelDRAW, VirtualDub, and a dozen other RIFF-based formats already spread acrossimages/video_audio/documents/misc), a bare JPEGFF D8start-of-image marker, and aPDFsignature missing the real signature's trailing-. A single category's "append after the category's own entries" placement can't guarantee these land after every more-specific sibling in other categories, so they're placed inextrasinstead, which is always checked dead last.
- Specific-before-generic pairs: ~40 more cases in the same spirit as
the Wikipedia source (e.g. a Synology-specific 4-byte GZIP variant
before generic GZIP,
- aSecuritySite: Magic Numbers —
a short (~46-row) reference table. Almost every row was an exact or
redundant-subset duplicate of a signature already transcribed from one
of the first four sources (e.g. its 2-byte
TIF/4-bytePNG/2-byteJPEGentries are all shorter, less specific prefixes of signatures already here; its Word/Excel/PowerPoint/Visio/MSI/Outlook-message rows are the same OLE Compound File signature already covered byDOC'snote), so those were skipped. Two rows were genuinely new:MP4 (mp42 brand)- a specific ftyp brand not yet invideo_audio, inserted before the genericMP4entry alongside the existing isom/M4A/MOV brand-specific ones.SDF File- a real conflict: its listed signature (78 9C) is byte-for-byte identical to the existing "zlib (default compression, no dict)" entry. Kept as a second entry with anoteon both sides explaining the tie, in the same spirit as theDOC/XLS/PPTand Java-Class/Mach-O ambiguities -SDF Filecan never actually be the reported result (the zlib entry is checked first and matches identically), so it exists purely for documentation. A couple of rows looked like they might be new but weren't reliable enough to add: a.pstsignature one byte longer than the well-established 4-byte Outlook PST magic, and a.mdbsignature missing the 4 leading bytes every other source (and the MS-JET spec) agrees real Access files start with - both read as transcription simplifications in the source rather than genuinely different signatures, so the existing, more reliable entries were left as-is instead of adding unverified variants.
The original handful of extras entries (byte-swapped Mach-O, an extra
MP3 frame variant, CUR, Unix compress, SQLite 3, Unix ar) predate
all five sources above and aren't transcribed from any of them; the rest
of extras is the deliberately-generic entries from the Kessler source
described above.