MCP Red Team
MCP Red Team is a dynamic red-team harness for MCP agents: a transparent proxy injects attacks into live tool traffic and scores them with a deterministic oracle. Held-out headline: 0 of 978 exfiltration trials moved the planted secret; attack success concentrated under one agent framework (exploratory). See Results.
Per model, per framework; exploratory panel labeled.
MCP Red Team is a dynamic red-team harness for agents that speak the Model Context Protocol. A transparent proxy sits between an agent and its MCP servers, injects attacks into the traffic the agent sees, measures attack success rate (ASR) with a deterministic oracle, and ships an information-flow mitigation layer whose cost to the agent's useful work is measured alongside attack success. It is defensive security research: attacks run only against mock servers in this repo, and canaries are synthetic.
Threat model
- Attacker: controls exactly one upstream MCP server, or poisons data inside one otherwise-benign server's data source (a note, a file, a resource).
- Trusted: the user, the agent scaffold, the model provider, and every other upstream server.
- Attacker goals: exfiltrate a secret, trigger a forbidden action, hijack the final answer, or persist across turns.
- Out of scope (v1): compromised clients, network MITM, malicious gateways, model-weight attacks, and adaptive attackers stronger than the rule-based search reported in Results (e.g., LLM-driven or human red-teamers).
Metrics: ASR and exposure
- Raw ASR = successes ÷ trials.
- Exposure-conditioned ASR = successes ÷ trials in which the payload reached the model.
- Exposure is measured at the LLM-request boundary, via the wrapped model client: a trial is exposed when the payload (or its decoded form, for encoding mutators) appears in any model request. We report both ASR definitions together, with exposure counts and Wilson 95% CIs.
Model manifest
- Static held-out (the headline):
claude-sonnet-5-5,claude-opus-5-5(Anthropic API), reported per model and pooled across scaffolds. - Ladder (
claude-haiku-5-5, Anthropic API, +qwen3:8b): a static slice (baseline, combined, strict; OpenAI Agents SDK only) + an adaptive search.qwen3:8b= local Ollama 0.40.2, digest500a1f067a9f, Q4_K_M, 8.2B (parallel_tool_calls=false,max_tokens=8192, sampling unset).qwen3:14bwas tested and dropped (86.1% < 90% benign). - Scaffolds: OpenAI Agents SDK + LangGraph (held-out, pooled for the headline). The Claude Agent SDK is a separate confirmatory benign slice and is excluded from held-out.
Reproduce
Two sanitized held-out recording sets are published as release assets (synthetic canaries only, home paths redacted, secret-scanned): a small rebuild bundle and the full set of every trial.
Rebuild the aggregate (small bundle — the aggregate inputs plus the answer-hijack recordings the aggregate hydrates):
gh release download heldout-static-recordings-v1 -R SrihaasaK/mcp-red-team -p 'heldout-static-recordings.tar.gz'
tar -xzf heldout-static-recordings.tar.gz
uv run python -I review/M4/heldout-static/rebuild_from_release.py heldout-static-recordings
Re-score every trial (full set — re-runs the frozen oracle over all 2,240 trials, confirms every verdict matches the per-cell records, and rebuilds the aggregate):
gh release download heldout-static-recordings-v1 -R SrihaasaK/mcp-red-team -p 'heldout-static-recordings-full.tar.gz'
tar -xzf heldout-static-recordings-full.tar.gz
uv run python -I review/M4/heldout-static/rescore_from_release.py heldout-static-recordings-full
Both reconstruct review/M4/heldout-static/aggregate.json and fail unless it is byte-identical to the
committed file. Then render the held-out headline offline from the committed aggregate, with zero
live model calls:
uv run mcpr report
uv run pytest additionally replays the recorded sessions with zero live calls and asserts replay
equality — identical tool-call sequence, identical tool arguments, identical oracle verdict (SPEC §9.1).
Prior art
Synthetic agent-injection benchmarks (AgentDojo, InjecAgent) measure attacks inside purpose-built environments; this harness attacks real agent frameworks over live MCP traffic, in both MCP protocol eras. Static MCP scanners (mcp-scan, now Snyk Agent Scan; Cisco mcp-scanner) inspect tool metadata at install time; this harness measures runtime behavior. Out-of-band defenses such as CaMeL, FIDES and Progent enforce information-flow or policy constraints outside the model; our taint gateway belongs to this family, and we report its utility cost. Nasr, Carlini et al. ("The Attacker Moves Second", 2025) show that adaptive attackers break most published prompt-injection defenses, which is why our rule-based adaptive results are reported as a lower bound.
14. Results
Headline
We ran a pre-registered evaluation of 8 classes of indirect prompt injection against MCP-connected agents, across 2 agent frameworks (OpenAI Agents SDK, LangGraph) and 4 models (Claude Opus 5.5, Sonnet 5.5, Haiku 5.5; Qwen3-8B open-weight): ~2,800 held-out static trials plus a rule-based adaptive search.
Findings
- Data exfiltration failed. 0 of 978 held-out exfiltration trials moved the planted secret to any sink. The only exfiltration in the project came from adaptive search against the unmitigated open-weight model (1 of 3 targets).
- Hijacking did not fail. At baseline, injected instructions hijacked the answer or persisted in 3.1% [1.3, 7.1] of Sonnet 5.5 trials and 2.5% [1.0, 6.4] of Opus 5.5 trials (intervals overlap).
- The framework mattered (EXPLORATORY, not pre-registered). At baseline, every frontier success occurred under the OpenAI Agents SDK (5.1% [2.7, 9.4]); none under LangGraph (0.0% [0.0, 2.7]), with comparable payload exposure. Across all seven arms the gap persists: OpenAI Agents SDK 5.6% [4.5, 7.0], LangGraph 0.6% [0.3, 1.3], with 70 of 76 successes under the OpenAI Agents SDK. LangGraph's 6 successes all occurred in mitigation arms.
- Susceptibility did not track model size (OpenAI Agents SDK only) (EXPLORATORY). Haiku 5.5: 16.7% [10.4, 25.7]; Qwen3-8B: 2.2% [0.6, 7.7]. A weaker model may also fail to carry out injected instructions.
- Provider safeguards did most of the defending. Anthropic's content filter terminated 58.1% of frontier held-out trials vs 1.3% model refusals. These results include provider safeguards and may not transfer to deployments without equivalent filtering.
- Defenses trade off. Fine-grained taint tracking costs 15.6–18.9 points of benign task success. Coarse taint and the combined set cost ~0, but no taint variant addresses hijacking or persistence, the only attacks that succeeded.
Takeaways for agent builders
- Test your scaffold, not just your model.
- Count provider-level filtering as part of your security posture, and verify it on your stack.
- Coarse data-flow control is free but blind to attacks along allowed paths; closing that gap needs per-task policies (planned v2).
Limitations
- The adaptive attacker is rule-based, not LLM-driven: measured ASR is a lower bound, and mitigation effectiveness an upper bound.
- Results include Anthropic provider safeguards.
- Two frameworks; Claude Agent SDK excluded from held-out (cli_cwd leak, D74); LangGraph class 3 N/A.
- qwen3:8b adaptive ran 1 seed instead of the pre-registered 3 (hardware; deviation D99).
- Answer-hijack counts include 2 refusal-quote cases (22 without them).
- Live runs are nondeterministic; reproducibility rests on replay.
- Era comparison (SPEC §4.3): the stateless-fallback was not triggered (two scaffolds speak the 2026-07-28 era, D7), but the held-out headline scaffolds are OpenAI Agents SDK (stateless) and LangGraph (stateful-only), so protocol era is confounded with scaffold; no isolated era comparison is claimed.
Metadata
Release files for mcp-redteam-harness 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcp_redteam_harness-0.1.0.tar.gz | 85.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_redteam_harness-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 190.8 kB
Release files / mcp_redteam_harness-0.1.0.tar.gz
| Download URL | mcp_redteam_harness-0.1.0.tar.gz |
|---|---|
| Size | 85.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b493a91e7238ffd5713ab4e9d21db1e3b7b7601e68246c31b4b83fcde06d66d8
|
|
BLAKE2b-256 checksum How to use checksums |
21f1a0cedad842b8f6f71d6649baca50491f52a37498fbac9cc5e3bb66cc4aa1
|
| 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 logRelease files / mcp_redteam_harness-0.1.0-py3-none-any.whl
| Download URL | mcp_redteam_harness-0.1.0-py3-none-any.whl |
|---|---|
| Size | 105.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
27ef3ac7d1008e99681a2bb595e3dbab848fe6780356bcb50d681883678e9e99
|
|
BLAKE2b-256 checksum How to use checksums |
8e1517edfef68b2b17650d7c78ba62236ccf4840cb051d709ea9171020c48200
|
| 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