Skip to main content

mcpfz-probe

CI PyPI

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.

Install

The Python monitor + policy engine (used by the fuzzer):

pip install mcpfz-probe

CI also publishes a prebuilt, eBPF-enabled Linux sidecar binary on each v* tag. Fetch the latest and point the fuzzer at it via MCPFZ_PROBE_BIN:

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.
  • fake backend: complete. A deterministic test double that replays scripted events into the correct bucket (startup/call/ambient) attributed to the active call_id — the whole pipeline runs on any OS without root.
  • ebpf backend: real, multi-probe. Loads a CO-RE BPF program on syscalls:sys_enter_* tracepoints — exec (execve), process creation (fork/vfork/clone), network egress (connect, sendto), listener exposure (bind, listen), file open (openat), file mutation (mkdir, unlink, rmdir, rename, symlink, link), chmod (chmod/fchmodat), and ptrace — 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 by mcp-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)

RuntimePolicy.net_allow accepts exact destinations (host:port), host names, IP literals, and shell-style host globs. For example, api.example.com allows api.example.com:443, while *.internal allows db.internal:5432. Exact destination entries continue to work for port-specific policies.

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.process_spawn, runtime.net_connect, runtime.net_bind, runtime.net_listen, runtime.sensitive_read, runtime.fs_write, runtime.fs_delete, runtime.fs_chmod, runtime.fs_mkdir, runtime.fs_rename, runtime.fs_symlink, runtime.fs_link, 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.

Metadata

Release files for mcpfz-probe 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcpfz-probe 0.1.3
File Size Uploaded
mcpfz_probe-0.1.3.tar.gz 14.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcpfz-probe 0.1.3
File Interpreter ABI Platform
mcpfz_probe-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 24.6 kB

Release files / mcpfz_probe-0.1.3.tar.gz

Download URL mcpfz_probe-0.1.3.tar.gz
Size 14.8 kB
Tags Source
SHA-256 checksum
How to use checksums
086b46d5a703325031f575173d4d3a3a3d38a1846ef0ce6023cfd0b842fb251c
BLAKE2b-256 checksum
How to use checksums
716cbae6a0eb385d42eda156fa91c9e9a5c480e8895c6c80b07646e8332dd19b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / mcpfz_probe-0.1.3-py3-none-any.whl

Download URL mcpfz_probe-0.1.3-py3-none-any.whl
Size 9.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
51bd542d9c8654eb2ce9ff53e701758d9aedafe73bc732bc80891cc9ca148650
BLAKE2b-256 checksum
How to use checksums
b1d71174370fd96e365aac622e8b06df78d5bed06acfdd8248a08f702eec5e41
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page