Runtime sidecar protocol and Python monitor for MCP server fuzzing
Project description
mcpfz-probe
Standalone runtime probe for MCP server fuzzing. It runs beside
mcp-server-fuzzer: the
fuzzer owns process launch and per-call timing; this repo owns runtime event
collection, per-call attribution, and the sidecar protocol. The privileged
sidecar is Rust (small, auditable, static Linux artifact); the integration layer
is Python (a simple RuntimeMonitor). See the fuzzer's
Runtime monitoring
section for how the two fit together.
Releases
CI publishes a prebuilt, eBPF-enabled Linux binary on each v* tag. Fetch the
latest and use it with the fuzzer:
curl -L -o mcpfz-probe \
https://github.com/Agent-Hellboy/mcpfz-probe/releases/latest/download/mcpfz-probe-x86_64-linux-ebpf
chmod +x mcpfz-probe
Repo layout
crates/mcpfz-probe/ Rust sidecar binary (fake + eBPF backends)
crates/mcpfz-probe-ebpf/ BPF program (compiled to bytecode, Linux only)
crates/mcpfz-probe-ebpf-common/ #[repr(C)] event type shared kernel<->userspace
src/mcpfz_probe/ Python monitor + policy engine
tests/ CI-friendly Python tests (fake backend, no root)
examples/ Sample events + live eBPF demo
docs/ Architecture and protocol notes
Status
- Python monitor + policy engine: complete and tested.
fakebackend: complete. A deterministic test double that replays scripted events into the correct bucket (startup/call/ambient) attributed to the activecall_id— the whole pipeline runs on any OS without root.ebpfbackend: real, multi-probe. Loads a CO-RE BPF program onsyscalls:sys_enter_*tracepoints — exec (execve), network (connect,sendto), file open (openat), delete (unlink/unlinkat), chmod (chmod/fchmodat), andptrace— attributes captured events to the active call window, and emits the same NDJSON as the fake backend. Verified on Ubuntu 24.04 / kernel 6.8 driven bymcp-server-fuzzer: a vulnerable server produced exec, TCP+UDP connect, sensitive read, out-of-workspace write, chmod, delete, and ptrace findings, each attributed to the exact tool call.
Sidecar protocol (NDJSON)
Python → sidecar stdin (control):
{"op":"scope","pgid":1234,"generation":1}
{"op":"mark","phase":"begin","call_id":"...","tool":"get_weather"}
{"op":"mark","phase":"end","call_id":"..."}
Sidecar → Python stdout (events); bucket is startup/call/ambient:
{"type":"exec","bucket":"call","call_id":"...","pid":1234,"argv":["/bin/sh","-c","curl ..."]}
{"type":"connect","bucket":"ambient","pid":1234,"dst":"203.0.113.7:443"}
Full spec in docs/protocol.md.
Using it (Python)
monitor.set_scope_pgid(server_pgid, generation=1)
monitor.begin_call(call_id, tool_name)
try:
result = call_tool(...)
finally:
summary = monitor.end_call(call_id) # events attributed to this call
findings = evaluate_events(summary.events, policy)
CLI
mcpfz-probe --backend fake --events-file examples/events.sample.json
mcpfz-probe --backend ebpf # Linux, needs root/CAP_BPF
mcpfz-probe --help
The fake backend reads an events script ({"events":[...]} or a bare [...]),
each entry a trigger (startup/scope/begin/end, optional tool filter)
and an event object the sidecar passes through, filling in bucket, call_id,
and ts_ns. See examples/events.sample.json.
mcp-server-fuzzer integration
The probe plugs into mcp-server-fuzzer through three small, opt-in hooks
(module mcp_fuzzer/runtime_probe.py): scope the sidecar to the server's process
group when the stdio server launches, begin/end marks around each
_execute_tool_call, and merge the resulting findings into the session at the
end. When MCP_FUZZER_RUNTIME_PROBE is unset the hooks are no-ops.
export MCP_FUZZER_RUNTIME_PROBE=1
export MCPFZ_PROBE_BIN=/path/to/mcpfz-probe # the sidecar binary
export MCPFZ_PROBE_BACKEND=ebpf # or "fake"
sudo -E mcp-fuzzer --mode tools --protocol stdio \
--endpoint "python examples/vulnerable_server.py" \
--runs 3 --max-concurrency 1
Kernel-observed syscalls become findings attributed to the exact tool + run:
runtime.exec, runtime.net_connect, runtime.sensitive_read,
runtime.fs_write, runtime.fs_delete, runtime.fs_chmod, and
runtime.ptrace. Verified on Linux against examples/vulnerable_server.py
(tools that shell out, read ~/.ssh, beacon over TCP/UDP, drop+chmod+delete a
file, and call ptrace): all seven categories were captured and merged into the
fuzzer's report, each attributed to the call that caused it.
Per-call attribution assumes stdio calls run serialized (--max-concurrency 1),
matching the design in docs/architecture.md; execs from overlapping calls still
get caught, but land in the ambient bucket.
Development
# Python tests
PYTHONPATH=src python3 -m unittest discover -s tests
# Rust sidecar (portable, fake backend)
cargo build
cargo test
eBPF backend (Linux)
Needs a BTF-enabled kernel and this toolchain:
rustup toolchain install nightly --component rust-src
cargo install bpf-linker # needs LLVM dev libs (e.g. llvm-18-dev)
cargo build --features ebpf # build.rs compiles the BPF crate to bytecode
sudo ./target/debug/mcpfz-probe --backend ebpf
Live end-to-end demo (real kernel capture + real policy), run as root:
python3 examples/ebpf_live_demo.py
Known limitation: scope filtering resolves a process's group via
/proc/<pid>/stat in userspace, which races short-lived processes. Per
docs/architecture.md, this should move into the kernel program
(task->group_leader via CO-RE) — next step alongside the connect/file_open
probes.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcpfz_probe-0.1.0.tar.gz.
File metadata
- Download URL: mcpfz_probe-0.1.0.tar.gz
- Upload date:
- Size: 13.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7f9a31b67e7df307899c8fd83fec05d91bf8a320eb8d09669edaf196cd580de
|
|
| MD5 |
39929b370e21dce06c5d52198226e5f9
|
|
| BLAKE2b-256 |
2d55acc7e49e92faf9c983f3c4bdce00336ab95472c4d5128fbd952b109b4253
|
File details
Details for the file mcpfz_probe-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mcpfz_probe-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
139346a344d559359fbeb99189a930d313c713eb378661b33f488d17df422c61
|
|
| MD5 |
fb217c948fa7fb8c1207652b223f14db
|
|
| BLAKE2b-256 |
41c0910664ef14b81015bc79f97fb22aa35242fc5cab415d8e3a5ce28d87facd
|