mcp-fuzz
Runtime behavioral testing for MCP servers.
mcp-doctor reads an MCP server's source code and checks whether its tools are well-documented. mcp-fuzz does the opposite: it actually launches the server and calls its tools, with inputs derived from each tool's own declared JSON schema, and checks whether the server behaves the way that schema and its description claim — does a missing required field get a structured error back, or does the server crash? Does a wrong-typed field get rejected cleanly, or does it hang?
Static analysis can't see any of that. Only running the code can.
Install
pip install mcp-runtime-check
(The PyPI distribution name is mcp-runtime-check — mcp-fuzz and close variants were blocked by PyPI's anti-typosquat check as too similar to existing packages, same naming friction mcp-doctor hit. The installed CLI command is still mcp-fuzz, and the repo/import package are unchanged.)
Use
mcp-fuzz -- python server.py
mcp-fuzz -- npx -y some-mcp-server
mcp-fuzz launches the command you give it as an MCP server over stdio, lists its tools, and for each one runs three kinds of calls built purely from that tool's own inputSchema — no LLM, no network calls of its own:
- valid — one plausible value per property (respecting
type,enum,minimum/maximum,format, ...) — should succeed. - missing required — the valid call, with one required property removed at a time — should come back as a structured MCP error, not a crash.
- wrong type — the valid call, with one property swapped to a value of a different JSON type — same expectation.
Any call that crashes the server, hangs past --timeout (default 15s), or leaves the connection unusable triggers a full reconnect before the next case runs, so one bad tool doesn't invalidate the rest of the report.
Safety — read this before pointing it at anything real
mcp-fuzz actually executes tool calls. Unlike mcp-doctor, it has real side effects if a tool does. By default, only tools annotated readOnlyHint: true are tested — everything else is skipped and listed as such in the report. Pass --include-destructive to test everything, but only against a server you're confident is safe to call blindly (a local sandbox, a test/staging backend) — never a server wired to production data, a real inbox, a real payment system, etc. Many real-world servers don't set readOnlyHint accurately or at all, in which case those tools are conservatively skipped rather than assumed safe.
What the score means
The reported "crash resilience" percentage covers only the missing-required and wrong-type cases — the fraction that came back as a structured error instead of a crash or hang. It does not grade whether the tool's "valid" call produced a correct result: a synthetic, schema-only-derived value (a placeholder string where the field really expects a real arXiv ID, or a URL that has to actually resolve) often isn't realistic enough for a failure there to be a fair judgment. A failed "valid" call is reported separately, flagged explicitly as "may be a synthetic-input false positive, not a confirmed bug" — worth a manual look, not proof of a bug.
JSON output / CI
mcp-fuzz --json -- python server.py
mcp-fuzz --fail-under 90 -- python server.py # non-zero exit if crash resilience < 90%
Real-world spot check
| Repo | Stars | Lang | What mcp-fuzz found |
|---|---|---|---|
modelcontextprotocol/server-everything |
— | TS | Official reference server, run cross-language via npx. Clean pass — 9/9 read-only tools handled every bad-input case cleanly (100%/A). trigger-long-running-operation's valid call correctly timed out — it's deliberately a long-running operation, exactly the kind of result the report's own "may be a false positive" framing exists for. |
blazickjp/arxiv-mcp-server |
3.1k | Python | Found a real bug in mcp-fuzz itself, not the target: installing this repo (which pins mcp<2.0) into the same environment downgraded the shared mcp package from 2.1.1 to 1.29.1. mcp<2.0 exposes several fields under their raw camelCase wire name (isError, inputSchema, readOnlyHint); mcp>=2.0 renamed them to snake_case. Every hardcoded snake_case attribute access broke with an AttributeError the moment an older mcp happened to be installed. Fixed with a small compatibility helper that tries the current name first, falls back to the old one. 11 tools tested cleanly afterward (100%/A) — the several "valid call errored" flags are exactly the documented synthetic-input false-positive case (a placeholder "paper_id": "test" isn't a real arXiv ID). |
punitarani/fli |
— | Python | Clean pass — all 4 read-only tools (Google Flights MCP) handled every bad-input case cleanly, and even the synthetic "valid" inputs succeeded without error (100%/A, no "worth investigating" flags at all). |
modelcontextprotocol/server-sequential-thinking |
— | TS | Official reference server. Clean pass, tested with --include-destructive (its one tool isn't read-only-annotated but has no real side effects) — 100%/A. |
modelcontextprotocol/server-filesystem |
— | TS | Official reference server, run against a throwaway sandbox directory. Clean pass — 6 of 10 read-only tools "valid call errored" on ENOENT: no such file, exactly the documented synthetic-input false positive: a generic placeholder string isn't a real path that exists in the sandbox. The 4 write/edit/move/create tools were correctly skipped as not read-only. |
Known limitations
- Input generation is schema-only. A property with no
type(or a genuinely ambiguousanyOf) is skipped from the wrong-type test set rather than guessed at. - No semantic check of what a successful response actually contains — that's a deliberately separate, opt-in, LLM-backed capability planned for a later release, not v1.
- stdio transport only for now; no HTTP/SSE servers yet.
License
MIT
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 mcp_runtime_check-0.1.1.tar.gz.
File metadata
- Download URL: mcp_runtime_check-0.1.1.tar.gz
- Upload date:
- Size: 15.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e64792587e821ae796e6351d086115b665fb86acddd17797f400e394cfa061d8
|
|
| MD5 |
f9b320d5e007eab4c551d98725805e52
|
|
| BLAKE2b-256 |
25d7178ff61982da25fb484b5b90d04bba86abbede2c8d501f0702225cae6aa8
|
Provenance
The following attestation bundles were made for mcp_runtime_check-0.1.1.tar.gz:
Publisher:
publish.yml on vishalhabib99/mcp-fuzz
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_runtime_check-0.1.1.tar.gz -
Subject digest:
e64792587e821ae796e6351d086115b665fb86acddd17797f400e394cfa061d8 - Sigstore transparency entry: 2719099634
- Sigstore integration time:
-
Permalink:
vishalhabib99/mcp-fuzz@85cdadc6eb37d4ff7479f5e96900e472b58b5e5c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/vishalhabib99
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@85cdadc6eb37d4ff7479f5e96900e472b58b5e5c -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file mcp_runtime_check-0.1.1-py3-none-any.whl.
File metadata
- Download URL: mcp_runtime_check-0.1.1-py3-none-any.whl
- Upload date:
- Size: 14.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e4d667249c96149400589b56e5af98bf7907c3f912d9131eb1e2d72d89a757a
|
|
| MD5 |
5334bd1ad29f6fcff1d13aba04a966ed
|
|
| BLAKE2b-256 |
fc9df1e7917325eb81373a865ccc2f6ddf1d6c4f1d26f1720e90043d07559f3b
|
Provenance
The following attestation bundles were made for mcp_runtime_check-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on vishalhabib99/mcp-fuzz
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_runtime_check-0.1.1-py3-none-any.whl -
Subject digest:
4e4d667249c96149400589b56e5af98bf7907c3f912d9131eb1e2d72d89a757a - Sigstore transparency entry: 2719100217
- Sigstore integration time:
-
Permalink:
vishalhabib99/mcp-fuzz@85cdadc6eb37d4ff7479f5e96900e472b58b5e5c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/vishalhabib99
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@85cdadc6eb37d4ff7479f5e96900e472b58b5e5c -
Trigger Event:
workflow_dispatch
-
Statement type: