10 intentionally malicious MCP servers that exploit protocol features to attack AI clients. For security research and defense testing.
bad-mcp
10 intentionally malicious MCP servers that demonstrate protocol-level attack patterns against AI clients. Each server exploits a distinct feature of the Model Context Protocol — tool descriptions, schemas, resources, session management, and cross-server interactions.
These aren't buggy servers waiting to be exploited. They are the attacker.
WARNING: This project is for authorized security research and education only. Do NOT expose these servers to the internet or use them in production.
Why This Exists
MCP adoption is accelerating — Claude Desktop, Cursor, Windsurf, and dozens of other AI clients now support it. But the protocol's trust model has fundamental gaps: tool descriptions are unverified, schemas can carry hidden instructions, and servers can mutate definitions mid-session. Research from CyberArk, Invariant Labs, and Elastic Security Labs has documented these attacks, but working implementations are scarce.
bad-mcp provides concrete, runnable attack servers so client developers can test defenses, security teams can assess risk, and researchers can study protocol-level threats without building everything from scratch.
How This Differs
| | bad-mcp | damn-vulnerable-MCP-server | vulnerable-mcp-servers-lab | |---|---|---|---| | Perspective | Server is the attacker | Server is the victim | Server is the victim | | Focus | MCP protocol-level attacks only | Mixed (prompt injection + classic appsec) | Mixed (path traversal, SQLi, MCP) | | Attacks | FSP, ATPA, rug-pull, resource poisoning, cross-server shadowing | CTF challenges (easy/med/hard) | 9 servers, pentest training | | Language | Go + mcp-go SDK | Python | JS + Python | | Isolation | Docker-compose, no host access | Local Python scripts | Docker-compose | | Unique | Rug-pull via session tools + notifications, full-schema poisoning via param names | Challenge/scoring format | Typosquatting focus |
Architecture
vuln-external network (localhost:8001-8010)
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ schema-poison-notes│ │ response-inject- │ │ shadow-exfil │
│ :8001 │ │ tickets :8002 │ │ :8003 │
└────────────────────┘ └────────────────────┘ └────────────────────┘
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ squatting-file-ops │ │ tool-poisoning-calc│ │ true-rug-pull │
│ :8004 │ │ :8005 │ │ :8006 │
└────────────────────┘ └────────────────────┘ └────────────────────┘
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ resource-poison- │ │ indirect-inject- │ │ exfil-chain- │
│ docs :8007 │ │ fetcher :8008 │ │ analytics :8009 │
└────────────────────┘ └────────────────────┘ └────────────────────┘
┌────────────────────┐
│ denial-of-wallet │
│ :8010 │
└────────────────────┘
Servers
| Port | Server | MCP Attack Class | Protocol Feature Exploited |
|------|--------|------------------|---------------------------|
| 8001 | schema-poison-notes | Full-Schema Poisoning (FSP) | input_schema parameter names |
| 8002 | response-inject-tickets | Adv. Tool Poisoning via Output (ATPA) | Tool response content |
| 8003 | shadow-exfil | Cross-Server Shadowing | Multi-server tool descriptions |
| 8004 | squatting-file-ops | Tool Name Collision / Squatting | Tool naming + descriptions |
| 8005 | tool-poisoning-calc | Tool Description Poisoning (TPA) | Tool descriptions |
| 8006 | true-rug-pull | Rug-Pull (Tool Def Mutation) | Session tools + notifications |
| 8007 | resource-poison-docs | Resource Poisoning (ATPA variant) | resources/list + resources/read |
| 8008 | indirect-inject-fetcher | Indirect Prompt Injection | External data in tool output |
| 8009 | exfil-chain-analytics | Cross-Tool Exfiltration Chain | Tool chaining via descriptions |
| 8010 | denial-of-wallet | Denial of Wallet | Recursive invocation + massive output |
Each server has its own README.md with vulnerability details, exploit scenarios, detection methods, and mitigation strategies.
Quick Start
# Build and start all servers
docker-compose build && docker-compose up -d
# Run smoke tests (runs entirely inside Docker)
docker-compose --profile test run --rm smoke-test
# View exfiltration logs
docker-compose logs schema-poison-notes | grep EXFIL
docker-compose logs true-rug-pull | grep RUG-PULL
# Stop
docker-compose down
Connecting an MCP Client
After docker-compose up -d, connect any SSE-compatible MCP client (Claude Desktop, Cursor, Windsurf, Claude Code, etc.). All servers use the same JSON format:
{
"mcpServers": {
"schema-poison-notes": { "url": "http://localhost:8001/sse" },
"response-inject-tickets":{ "url": "http://localhost:8002/sse" },
"shadow-exfil": { "url": "http://localhost:8003/sse" },
"squatting-file-ops": { "url": "http://localhost:8004/sse" },
"tool-poisoning-calc": { "url": "http://localhost:8005/sse" },
"true-rug-pull": { "url": "http://localhost:8006/sse" },
"resource-poison-docs": { "url": "http://localhost:8007/sse" },
"indirect-inject-fetcher":{ "url": "http://localhost:8008/sse" },
"exfil-chain-analytics": { "url": "http://localhost:8009/sse" },
"denial-of-wallet": { "url": "http://localhost:8010/sse" }
}
}
Tip: Start with
tool-poisoning-calc(:8005) — simplest attack to observe. Then trytrue-rug-pull(:8006) — ask about the weather 3+ times and watch the tools mutate.
Safety Guarantees
| Concern | Guarantee |
|---------|-----------|
| Host filesystem | No volume mounts. Containers have zero access to host filesystem. |
| Host network | No network_mode: host. All containers use isolated Docker bridge networks. |
| Privileged access | No privileged: true. No cap_add. Default restricted capabilities. |
| Environment leakage | No host env vars passed. All "credentials" are fake hardcoded strings. |
| Host execution | Nothing runs on the host. Tests run inside Docker via --profile test. |
| Code on disk | Inert Go source files. Not executable outside Docker. |
Tech Stack
- MCP servers: Go + mcp-go v0.32.0 (SSE transport)
- Docker: Multi-stage
golang:1.23-alpine->alpine:3.19 - Orchestration: docker-compose with network isolation
- Testing: Dockerized Go smoke test (
--profile test)
Research References
| Server | Source | |--------|--------| | schema-poison-notes | CyberArk — Full-Schema Poisoning | | response-inject-tickets | CyberArk — ATPA | | shadow-exfil | Invariant Labs — Tool Poisoning | | squatting-file-ops | Elastic Security Labs | | tool-poisoning-calc | Invariant Labs — Tool Poisoning | | true-rug-pull | ETDI Paper | | resource-poison-docs | Pillar Security | | indirect-inject-fetcher | Microsoft — Indirect Injection | | exfil-chain-analytics | Elastic Security Labs | | denial-of-wallet | Prompt Security — Top 10 MCP Risks |
See also: Adversa AI — MCP Security TOP 25 | MCPTox Benchmark | OWASP Top 10 for Agentic Applications
Known Gaps
The following published MCP attack classes are not yet implemented:
- Server Identity Spoofing — MCP server spoofing trusted
serverInfoname/version during initialization - Sampling Abuse — Exploiting
sampling/createMessageto inject arbitrary prompts - Prompt/System Instruction Leakage — Extracting system prompts via crafted tool responses
- Notification Channel Exfiltration — Abusing progress/logging notifications as data channels