CLI fuzzing for MCP servers
Tool fuzzing • Protocol fuzzing • HTTP/SSE/stdio/StreamableHTTP • Safety controls • Rich reporting
MCP Server Fuzzer tests MCP servers by fuzzing:
- tool arguments
- protocol request types
- resource and prompt request flows
- multiple transports:
http,sse,stdio, andstreamablehttp
It includes optional safety controls such as filesystem sandboxing, PATH-based command blocking, and network restrictions for safer local testing.
Beyond protocol/transport errors, runs are classified into categorized findings
(written to <output-dir>/findings.json, with per-crash repros under
<output-dir>/crashes/, a compact CI summary at
<output-dir>/run_summary.json, and summarized on stdout):
crash— server process terminated abnormally (segfault/abort/panic or non-zero exit); captures the signal and a stderr tailauth_bypass— a protected tool answered an unauthenticated callinjection_reflection— a dangerous input token was echoed back verbatimoversized_response— response exceeded the read cap (resource exhaustion)hang— request timed out (deadlock / infinite loop / ReDoS)internal_error— JSON-RPC-32603(unhandled server-side exception)error_leakage— stack trace / panic leaked in outputmemory_growth— server RSS grew multi-fold across runs (stdio targets)non_determinism— identical input produced differing outcomesaccepted_malformed— server returned a non-error response to an attack-pattern or schema-invalid fuzz input; repeated identical evidence is collapsed into one finding with run counts and a response sampleperformance_outlier— response time far above the per-target median
findings.json is always written for completed sessions, even when no findings
are present ({"findings": [], "count": 0}), so CI jobs can assert that report
generation succeeded separately from whether issues were discovered.
run_summary.json records whether the run completed or was blocked, plus
tool/protocol counts and run totals for simple CI assertions.
Tip: for compiled-language servers (Go/C/C++/Rust), run the target under a sanitizer or race build so memory bugs surface as observable crashes.
Requires Python 3.10+.
# PyPI
pip install mcp-fuzzer
# From source
git clone --recursive https://github.com/Agent-Hellboy/mcp-server-fuzzer.git
cd mcp-server-fuzzer
pip install -e .Docker is also supported:
docker build -t mcp-fuzzer:latest .
docker run --rm mcp-fuzzer:latest --helppip install "mcp[cli]" uvicorn
python3 examples/test_server.pyThat server uses the official Python MCP SDK, listens on
http://localhost:8000/mcp/, and exposes:
test_toolecho_toolsecure_toolrequiringAuthorization: Bearer secret123
mcp-fuzzer --mode tools --protocol http --endpoint http://localhost:8000/mcp/ --runs 10mcp-fuzzer --mode protocol --protocol-type InitializeRequest \
--protocol http --endpoint http://localhost:8000/mcp/ --runs-per-type 5mcp-fuzzer --mode all --phase both --protocol http --endpoint http://localhost:8000/mcp/# Enable command blocking + safety reporting
mcp-fuzzer --mode tools --protocol http --endpoint http://localhost:8000/mcp/ \
--enable-safety-system --safety-report
# Export results
mcp-fuzzer --mode tools --protocol http --endpoint http://localhost:8000/mcp/ \
--export-csv results.csv --export-html results.html
# Use auth config for the bundled secure_tool example
mcp-fuzzer --mode tools --protocol http --endpoint http://localhost:8000/mcp/ \
--auth-config examples/auth_config.json
# Load settings from YAML
mcp-fuzzer --config config.yamlThis repository bundles:
- an official Python MCP SDK HTTP example:
examples/test_server.py - an official Go MCP SDK stdio example:
examples/go_stdio_server - an official TypeScript MCP SDK stdio example:
examples/typescript-stdio-server - a StreamableHTTP example:
examples/streamable_http_server.py
For other stdio usage, point the fuzzer at your own server:
mcp-fuzzer --mode tools --protocol stdio --endpoint "python my_server.py" \
--enable-safety-system --fs-root /tmp/mcp-safeMore runnable example flows are documented in
examples/README.md.
Pair the fuzzer with mcpfz-probe, a runtime sidecar that watches what a tool call actually does at the kernel level (process exec, network connect/send, file open/delete, chmod, ptrace) and reports anything suspicious as findings attributed to the exact tool call.
Fetch the released Linux binary from mcpfz-probe and point the fuzzer at it:
# Latest eBPF-enabled Linux build (built and published by mcpfz-probe CI)
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
pip install "mcp-fuzzer[mcpfz-probe]" # pulls in the Python monitor package
export MCP_FUZZER_RUNTIME_PROBE=1
export MCPFZ_PROBE_BIN="$PWD/mcpfz-probe"
export MCPFZ_PROBE_BACKEND=ebpf # Linux + root/CAP_BPF; use "fake" elsewhere
sudo -E mcp-fuzzer --mode tools --protocol stdio \
--endpoint "python my_server.py" --runs 3 --max-concurrency 1Runtime findings (runtime.exec, runtime.net_connect, runtime.sensitive_read,
runtime.fs_write, runtime.fs_delete, runtime.fs_chmod, runtime.ptrace) are
merged into the normal report. The hooks are no-ops unless
MCP_FUZZER_RUNTIME_PROBE is set, so default behavior is unchanged. See the
mcpfz-probe README for the
backend/build details.
Keep the README for the basics. Use the docs for everything else:
MIT. See LICENSE.