An external Minecraft packet fabrication proxy. Your real client connects normally; McFabProxy intercepts the connection transparently and lets you capture, inspect, craft, and inject arbitrary serverbound packets — through a web UI or a CLI console — without touching the client, the server, or any mods.
v1 scope: protocol 774 (Minecraft 1.21.11), offline-mode servers. See Scope and limitations for exactly why, and what a v2 needs.
Prefer Meteor Client? Skip --upstream entirely — see
docs/METEOR.md.
CLI Console (nc / netcat)
|
|
▼
┌─────────────────────┐
│ Core Application │
│ │
│ proxy.rs ←──────── TCP client
│ │ │ \
│ pump_c2s pump_s2c → real server
│ │ │
│ capture broadcast channel
│ │
│ registry (Arc<RwLock>)
│ session (Arc<RwLock<Option>>)
└──────────┬──────────┘
│
HTTP + WebSocket API (:25568)
│
▼
Web Interface
(browser → 127.0.0.1:25568)
The web interface is one client of the core application, not the application itself. The CLI console remains fully functional with no web UI involved.
Every packet that passes through the proxy — in either direction — is broadcast over a WebSocket. The Monitor tab shows the live stream with direction, state, packet ID, byte count, and a hex preview. Click any row to expand its full hex dump.
Discover an unknown packet by watching it flow through the Monitor. Expand the row, click Create Registry Entry, and a wizard opens pre-filled with the packet's state and ID. Name the packet, define its fields and types, save — the registry JSON is updated on disk and reloaded live. The entry immediately appears in the Registry Explorer and Builder.
The Builder tab generates a form from any registry entry. Select a packet by name, fill in the typed fields, and send. For anything not in the registry, the Raw Inject panel accepts a packet ID and a hex payload directly.
The Script tab runs McFabScript programs against the live session. Automate packet sequences, wait for server responses, inspect field values, and react conditionally — all without touching the browser after hitting Run.
repeat 5 times {
send(swing_arm, hand=0);
sleep(100);
}
wait_for(entity_status, timeout=2000);
if (!timed_out) { log("entity: " + packet.entity_id); }
Full reference: docs/SCRIPTING.md.
Captured packets appear in the Builder's Replay panel. One click re-injects any captured packet into the live session.
A plain-text TCP console (nc 127.0.0.1:25567) gives the same
fabrication surface without a browser — send, raw, list, state.
Useful for environments where the web UI isn't convenient.
Add entries through the wizard, or edit the registry JSON and hit Reload
in the Registry tab / POST /api/registry/reload — the registry updates in
memory with no proxy restart. Hot-reload requires --registry to point to a
specific .json file, not a directory.
- Rust (stable, 2021 edition) —
rustc/cargo - No system OpenSSL or zlib headers —
flate2uses its pure-Rust backend, so this builds cleanly on Termux without extra packages.
pkg install rust
git clone https://github.com/codedsword/McFabProxy
cd mc-fab-proxy
cargo build --releaseThe binary is at target/release/mc-fab-proxy. The pre-built web UI is
already in web/dist/ and served automatically — no Node.js needed.
sudo apt install cargo # or use rustup
git clone https://github.com/codedsword/McFabProxy
cd mc-fab-proxy
cargo build --releaseOnly needed if you modify the frontend source:
cd web
npm install
npm run build # output goes to web/dist/1. Start your target Minecraft server in offline mode
(online-mode=false in server.properties).
2. Start McFabProxy:
./target/release/mc-fab-proxy --upstream 127.0.0.1:25565Default ports: proxy listens on :25566, CLI console on :25567, web/API
on :25568. Override any with --listen, --ctl, --api.
3. Point your Minecraft client at 127.0.0.1:25566 instead of the
real server and log in normally.
4. Open http://127.0.0.1:25568 in a browser. The Dashboard shows
session state; Monitor shows live packets immediately.
5. (Optional) CLI console:
nc 127.0.0.1 25567
> state
state=play compression=256
> raw 0x1b 00
sent raw id=27 (1 bytes)Meteor Client? See docs/METEOR.md — the SOCKS5 setup
is simpler and keeps your server list unchanged.
mc-fab-proxy [OPTIONS]
Options:
--upstream <host:port> Fixed target server. Omit for SOCKS5 mode.
--listen <addr:port> Proxy listen address (default 127.0.0.1:25566)
--ctl <addr:port> CLI console listen address (default 127.0.0.1:25567)
--api <addr:port> HTTP/WebSocket API address (default 127.0.0.1:25568)
--registry <path> Registry dir or .json file (default registry/)
--web <path> Web UI dist directory (default web/dist)
-h, --help Print this help
Connect with nc 127.0.0.1 25567 (or any raw TCP client).
| Command | Effect |
|---|---|
state |
Current protocol state and compression threshold |
list [state] |
Registered packets for a state (default: current) |
send <name> k=v ... |
Build and inject a named registry packet |
raw <id> <hex> |
Inject any packet by numeric ID and raw hex payload |
raw works for any packet — registered or not — from day one.
Open http://127.0.0.1:25568 while the proxy is running.
Connection status, upstream address, protocol state, compression, packet counts, and session uptime. Polls automatically every two seconds.
Live packet stream over WebSocket. Filter by ID, state, or direction. Pause the stream to freeze the view. Click any row to expand its hex dump and access the Registry Wizard.
All defined packets, grouped by state, with their fields and types. Reload hot-reloads the JSON file without restarting the proxy.
Three panels:
- Packet Builder — select a registry packet, fill in its typed fields, send.
- Raw Inject — packet ID (hex) + hex payload, send directly.
- Replay — recent captured packets; click Replay to re-inject.
The registry/ directory holds one JSON file per protocol version (e.g.
774.json for 1.21.11). The proxy reads the client's handshake and
automatically loads the matching file. Add more versions by dropping
additional <version>.json files in the same directory.
{
"play": [
{
"name": "swing_arm",
"id": 27,
"fields": [
{ "name": "hand", "type": "varint" }
]
}
]
}- Play normally with the proxy running.
- Open the Monitor tab and watch for the packet you want to name.
- Click the packet row → expand → Create Registry Entry.
- Fill in the name and add fields with their types.
- Save. The entry is live immediately.
Edit the file directly (e.g. registry/774.json), then click Reload in
the Registry tab or run POST /api/registry/reload (only works when
--registry points to a single file, not a directory).
curl -X POST http://127.0.0.1:25568/api/registry/add \
-H 'Content-Type: application/json' \
-d '{"state":"play","name":"swing_arm","id":27,"fields":[{"name":"hand","type":"varint"}]}'scripts/gen_registry.py turns a server's --reports output into a
skeleton registry (names and IDs for every packet, no field types). Run it
once per version and it cleans up after itself:
python3 scripts/gen_registry.py server.jar
# Output: registry/<detected_version>.json
# Or if version detection fails:
python3 scripts/gen_registry.py server.jar --version 774Field types must still be added manually via the Registry Wizard or by editing the JSON. The skeleton is enough to identify every packet in the Monitor by name.
This writes generated/reports/packets.json — the authoritative ID table
for that exact server jar.
| Type | Wire encoding |
|---|---|
bool |
1 byte, 0 or 1 |
i8 / u8 |
1 signed/unsigned byte |
i16 / u16 |
2 bytes, big-endian |
i32 / u32 |
4 bytes, big-endian |
i64 |
8 bytes, big-endian |
f32 / f64 |
4/8 bytes IEEE 754, big-endian |
varint |
Minecraft-style variable-length integer |
string |
VarInt length prefix + UTF-8 bytes |
uuid |
16 raw bytes (dashed or undashed hex input) |
hexbytes |
Raw bytes, no length prefix |
NBT, Slot, and prefixed-array types aren't supported — use raw /
Raw Inject for packets that need them.
Offline-mode only. Encryption in the Minecraft protocol is tied to Mojang session verification. Supporting online-mode servers requires the proxy to hold its own Microsoft OAuth token and perform its own session-join call — a real, separable chunk of work involving RSA, AES-CFB8, and OAuth. v1 skips this; target offline-mode servers (your own dev/test server is the right environment for packet research anyway).
It's not close to being the best. Tools like ui-utils, ui-utils cevapi rewrite and HelixCraft's packet logger are all miles ahead primarily because they are in the form of mods that can directly read game data and not just an intercepting proxy
Single session at a time. The proxy handles one client connection at a time by design — it is meant to be you, your client, your test server.
Skeleton registries. Packet IDs shift between even adjacent Minecraft
versions. The included registry/774.json has verified handshake,
login, and configuration entries; play entries are left empty. Use
scripts/gen_registry.py against any server jar to generate a full
skeleton (names + IDs for every state and direction) for any version.
mc-fab-proxy/
├── Cargo.toml
├── scripts/
│ └── gen_registry.py # generate skeleton registry from a server jar --reports
├── registry/
│ └── 774.json # packet definitions for protocol 774 (1.21.11)
├── docs/
│ ├── METEOR.md # Meteor Client SOCKS5 setup
│ ├── API.md # HTTP + WebSocket API reference
│ ├── ARCHITECTURE.md # internals, data flow, module map
│ ├── CONTRIBUTING.md # development guide
│ └── SCRIPTING.md # McFabScript language reference
├── web/
│ ├── src/ # Svelte 5 + TypeScript source
│ └── dist/ # pre-built, served by the binary at runtime
└── src/
├── main.rs # startup: args, registry load, API server, accept loop
├── args.rs # hand-rolled CLI flag parser
├── errors.rs # shared Result/error alias
├── varint.rs # Minecraft VarInt codec
├── frame.rs # length-prefix + zlib framing, FrameCodec
├── state.rs # Shared struct (protocol state, compression, counters)
├── registry.rs # JSON packet defs, field encoder, hot-reload
├── capture.rs # WsEvent enum, broadcast channel, CaptureEvent
├── session.rs # SessionHandle, ActiveSession (Arc<RwLock<Option<…>>>)
├── proxy.rs # bidirectional relay, pump_c2s / pump_s2c
├── control.rs # CLI fabrication console
├── socks5.rs # SOCKS5 CONNECT handshake (Meteor mode)
├── script/
│ ├── mod.rs # parse() entry point
│ ├── lexer.rs # tokenizer
│ ├── parser.rs # AST + recursive descent parser
│ ├── decode.rs # packet field decoder (inverse of encode_field)
│ └── interpreter.rs # async tree-walking evaluator
└── api/
├── mod.rs # AppState, axum router, ServeDir for web/dist
├── routes.rs # HTTP handlers (incl. script run/stop/status)
└── ws.rs # WebSocket /ws/events stream
Full reference: docs/API.md.
Quick reference:
| Method | Path | Purpose |
|---|---|---|
| GET | /api/status |
Session status (always 200) |
| GET | /api/registry |
All packet definitions |
| POST | /api/registry/reload |
Hot-reload registry from disk |
| POST | /api/registry/add |
Add/replace a packet definition |
| POST | /api/inject/raw |
Inject by ID + hex |
| POST | /api/inject/named |
Inject by registry name |
| POST | /api/script/run |
Run a McFabScript program |
| POST | /api/script/stop |
Stop the running script |
| GET | /api/script/status |
Script running state |
| GET (WS) | /ws/events |
Live event stream (packets + script output) |
- Online-mode / encryption — proxy holds its own MS OAuth token, performs its own session-join upstream. Cleanly separable from v1.
- NBT / Slot / array field types — broadens the set of packets reachable by name through the Builder.
- Script enhancements —
whileloops, return values, file-based scripts, a clock/timestamp built-in, and a macro save/load system.
Protocol details verified against the Minecraft Wiki (CC BY-SA 3.0), which explicitly permits building servers, clients, and bots from its contents.