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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcpfz_probe-0.1.3.tar.gz (14.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcpfz_probe-0.1.3-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file mcpfz_probe-0.1.3.tar.gz.

File metadata

  • Download URL: mcpfz_probe-0.1.3.tar.gz
  • Upload date:
  • Size: 14.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for mcpfz_probe-0.1.3.tar.gz
Algorithm Hash digest
SHA256 086b46d5a703325031f575173d4d3a3a3d38a1846ef0ce6023cfd0b842fb251c
MD5 422d5ba27c9b37cbf572b7c72c854530
BLAKE2b-256 716cbae6a0eb385d42eda156fa91c9e9a5c480e8895c6c80b07646e8332dd19b

See more details on using hashes here.

File details

Details for the file mcpfz_probe-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: mcpfz_probe-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 9.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for mcpfz_probe-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 51bd542d9c8654eb2ce9ff53e701758d9aedafe73bc732bc80891cc9ca148650
MD5 17bc68637049111f67477e66a03b072a
BLAKE2b-256 b1d71174370fd96e365aac622e8b06df78d5bed06acfdd8248a08f702eec5e41

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.2

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page