Skip to main content

mcp-defense-bench (Python)

Score any Python MCP defence — a FastMCP middleware, a policy gate, a gateway — on the mcp-defense-bench corpus, and get the same numbers as the JS leaderboard: CorpusRobustCoverage per vector, per family and in total, plus the false-positive rate on 337 benign, real-world tool definitions and results, with a Wilson 95% interval.

Scanners tell you what a server looks like, and unit tests tell you whether your server works. This tells you how well a runtime defence catches attacks it was not tuned on, and how often it blocks legitimate traffic — and fails your CI when either gets worse.

  • Byte-for-byte parity with the JS scorer (bin/run.mjs) on the dev and benign corpora, enforced by tests.
  • Zero runtime dependencies. The rubric and corpora are vendored at a pinned bench commit and checked against a sha256 manifest on every load.
  • pytest plugin: pytest --mcp-defense=my_guard:Guard fails when coverage drops or false positives rise against a committed baseline.
  • FastMCP adapter: each fixture runs through a real in-memory FastMCP server with your middleware mounted.
  • Python 3.10–3.14. Apache-2.0.

Install

pip install mcp-defense-bench            # scorer, CLI, pytest plugin
pip install "mcp-defense-bench[fastmcp]" # + the FastMCP middleware adapter

Score a defence

mcp-defense-bench run my_package.guard:Guard --corpus all

my_package.guard:Guard can be any of these:

What Guard is What happens
A FastMCP Middleware class, or a factory that returns one Wrapped in FastMCPAdapter; a fresh instance is built for each fixture
An adapter class or instance (see below) Used directly
A module with assess() and meta Used like a JS adapter.mjs

The output is the JS runner's JSON. --out-dir results/ writes results/<tool>.json, results/benign/<tool>.json and results/coverage/<tool>.json in the bench's own layout, so the files can be submitted to the leaderboard.

From Python:

from mcp_defense_bench import run

dev = run("my_package.guard:Guard")                     # dev corpus (default)
benign = run("my_package.guard:Guard", corpus="benign")
print(dev["summary"]["overall"], benign["summary"]["fpRate95ci"])

Write an adapter

An adapter reports what the defence did with one input. It is the Python form of the bench's adapter contract.

from mcp_defense_bench import AdapterMeta, Verdict

class Guard:
    meta = AdapterMeta(tool="my-guard", class_="runtime-proxy", reproducible=True,
                       fixture_types=("malicious-tool-definition", "benign-tool-definition"))

    async def assess(self, input, testcase):            # sync works too
        hit = my_engine.inspect(input)                   # your defence, on what it really sees
        return Verdict(detect=hit, enforce=hit and my_engine.blocks, signal="why")

assess is called on each case's malicious fixture and on its benignControl. Then:

  • missing the attack scores none;
  • catching it but also flagging the control scores none, with a false-positive flag;
  • warning only scores detect (0.5);
  • blocking scores enforce (1).

A vector's CorpusRobustCoverage is the mean over all its fixtures, evasion variants included. fixture_types limits the adapter to inputs it can actually see; vectors of other types stay unknown (not counted as misses), and skipped items leave the benign denominator.

Integrity rule (from the bench): report only what your defence actually inspects at runtime, never what its rules could catch if pointed at data it never sees.

FastMCP middleware

from mcp_defense_bench import run
from mcp_defense_bench.adapters.fastmcp import FastMCPAdapter

run(FastMCPAdapter(MyMiddleware, warn_logger="my_guard"), corpus="benign")

For each fixture the adapter serves the fixture's tools from a fresh FastMCP server, mounts your middleware, and drives it with fastmcp.Client over the in-memory transport. It then replays the same traffic without the middleware and compares the two runs.

Fixture type MCP traffic
malicious- / benign-tool-definition tools/list
tool-call tools/call with the fixture's arguments
malicious- / benign-tool-result tools/call; the tool returns the fixture's result
tool-call-sequence each call in order, in one session
definition-change tools/list, swap the definition, tools/list again (rug-pulls)

The middleware scores enforce if it raises, returns an error, stops the call reaching the tool, hides or rewrites a definition, or rewrites the arguments or the result. It scores detect if it only logs a WARNING (or worse) on warn_logger. Other fixture types (HTTP transport, registries, delegation tokens, …) can't be expressed as FastMCP traffic, so they stay unknown.

See examples/keyword_guard.py, a deliberately naive guard, and examples/mcp_assure_example.py, which scores mcp-assure's shipped baseline pack.

Fail CI on regressions (pytest)

pytest --mcp-defense=my_package.guard:Guard --mcp-defense-update-baseline   # once; commit the file
pytest --mcp-defense=my_package.guard:Guard                                 # in CI

Or configure it once:

[tool.pytest.ini_options]
mcp_defense = "my_package.guard:Guard"
mcp_defense_baseline = "tests/mcp-defense-baseline.json"
# mcp_defense_corpus = "all"   # dev, benign or all
# mcp_defense_tolerance = "0"  # allowed CorpusRobustCoverage drop

The plugin adds three test items:

  • mcp-defense::dev-coverage fails if total or any per-vector CorpusRobustCoverage fell.
  • mcp-defense::dev-false-positives fails if more matched controls are flagged.
  • mcp-defense::benign-false-positives fails if more benign items are flagged.

The summary prints the headline numbers. A session fixture, mcp_defense_results, gives your own tests the full result documents. Without pytest, mcp-defense-bench check SPEC --baseline FILE applies the same gate and exits 1 on a regression.

Totals per family and per denominator

mcp-defense-bench totals results/my-guard.json --markdown

This prints the leaderboard's "published vs all vectors" row: the 32 vectors published before v0.9, all 37, and the 5-vector delegation family. --heldout FILE adds the held-out totals from a recorded held-out run.

The held-out corpus

The bench's held-out set (63 fixtures, 29 vectors) is a one-shot generalisation test. Score a defence on it once, at a frozen version, after all tuning on the dev corpus is done (protocol).

To protect it:

  • The held-out set is not vendored. The manifest holds only its file hashes and counts.
  • Scoring it needs a bench checkout and an explicit acknowledgement:
mcp-defense-bench run my.guard:Guard --corpus heldout --bench-path ../mcp-defense-bench --heldout-confirm my-guard

mcp-defense-bench verify --bench-path ../mcp-defense-bench checks a checkout's hashes and counts, including the held-out set's, without scoring anything.

Parity with the JS scorer

The tests replay the verdicts recorded by the JS runner for four tools (null-baseline, mcp-bastion, mcp-firewall and pipelock) through the Python scorer. On both the dev and benign corpora, the output must equal the recorded results/*.json byte for byte, apart from the measuredAt timestamp. The suite also checks:

  • the per-vector coverage against each coverage.json;
  • the totals rows against the generated LEADERBOARD.md;
  • a live Python run of the null baseline.

Three porting traps are handled in jscompat.py:

  • JS toFixed rounds a tie up; Python rounds half to even. (0.25).toFixed(1) is "0.3", but f"{0.25:.1f}" is "0.2".
  • Python 3.12+ sum() compensates floating-point error; JS reduce is a plain left fold.
  • JSON.stringify writes numbers differently: 1 for 1.0, and different exponent cut-offs.

Pinned versions

Bench mcp-defense-bench 0.9.0 (tag v0.9.0, commit c4a4863)
Rubric 0.9.0, 37 vectors
Dev corpus 66 cases
Benign corpus 337 items
Held-out corpus 63 cases (hashes only)
FastMCP adapter fastmcp>=4.0,<5 (MCP SDK v2)

Run mcp-defense-bench info to see these, and mcp-defense-bench verify to check the vendored data.

Licence

Apache-2.0. The vendored rubric and corpora come from mcp-defense-bench (Apache-2.0); see NOTICE.

Metadata

Release files for mcp-defense-bench 0.1.0

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

Source distribution (sdist)

Source distribution for mcp-defense-bench 0.1.0
File Size Uploaded Provenance
mcp_defense_bench-0.1.0.tar.gz 274.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-defense-bench 0.1.0
File Interpreter ABI Platform
mcp_defense_bench-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 775.5 kB

Release files / mcp_defense_bench-0.1.0.tar.gz

Download URL mcp_defense_bench-0.1.0.tar.gz
Size 274.0 kB
Tags Source
SHA-256 checksum
How to use checksums
abc38ea015446b2a89fa43d07cf03f56543d884c9de9b32c3045b8d898fbe529
BLAKE2b-256 checksum
How to use checksums
135a191e220218d64efa4d127b1ff9b8f20c9e76984a7b9d8e67aaabf95df9e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / mcp_defense_bench-0.1.0-py3-none-any.whl

Download URL mcp_defense_bench-0.1.0-py3-none-any.whl
Size 501.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0d9c82925c0a4492ea69645e1664526cb9cb72045a75596d1ccbebff3edc8aa1
BLAKE2b-256 checksum
How to use checksums
03e07a34ba0ca09078ec091c2a9886ceb1460b97515d7d5ddecc9ffe4918d6e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Security for mcp-defense-bench 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mcp_defense_bench-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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