Skip to main content

Runtime sidecar protocol and Python monitor for MCP server fuzzing

Project description

mcpfz-probe

CI

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.
  • 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), network (connect, sendto), file open (openat), delete (unlink/unlinkat), 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)

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


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.0.tar.gz (13.2 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.0-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

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

Hashes for mcpfz_probe-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b7f9a31b67e7df307899c8fd83fec05d91bf8a320eb8d09669edaf196cd580de
MD5 39929b370e21dce06c5d52198226e5f9
BLAKE2b-256 2d55acc7e49e92faf9c983f3c4bdce00336ab95472c4d5128fbd952b109b4253

See more details on using hashes here.

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

Hashes for mcpfz_probe-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 139346a344d559359fbeb99189a930d313c713eb378661b33f488d17df422c61
MD5 fb217c948fa7fb8c1207652b223f14db
BLAKE2b-256 41c0910664ef14b81015bc79f97fb22aa35242fc5cab415d8e3a5ce28d87facd

See more details on using hashes here.

Supported by

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