mcp-shield
Security primitives for the Model Context Protocol.
The Blackwell Systems 2026-06 audit scanned 54 production MCP servers and found 20 bugs in 9 of them. The 2026-03-05 MCP roadmap named audit trails, SSO, and machine-readable server discovery as the four blockers keeping MCP out of regulated enterprises. mcp-shield is the missing security layer — five drop-in primitives that address every bug class from the audit, MIT-licensed, stdlib-only, no SaaS dependency.
pip install mcp-shield
Why this exists
If you're running an MCP server — yours or someone else's — you have four problems mcp-shield solves:
- Tool errors return as success.
except E: return _format_error(e)leaves FastMCP'sisErrorflag unset, so the agent retries forever. MCPTox benchmark (arXiv:2508.14925) flags this across 26+ Python MCPs. - No audit trail. SOC2 procurement requires per-call records. Building one yourself is a Friday you'll never get back.
- Tool output can hijack the agent. OWASP #1 LLM vulnerability (Context Poisoning, >100K sites hit in 2025). Tool descriptions and outputs are the attack surface.
- No input validation. Malformed calls from the agent crash your handler with
KeyError,TypeError, etc. — not a typed error the model can correct.
The five primitives
| Primitive | Bug class | Drop-in usage |
|---|---|---|
shield_tool decorator |
1 — isError-compliance | @shield_tool(name="my_tool", schema={...}) |
audit_tool_call |
3 — JSONL audit | with audit_tool_call("my_tool", args): ... |
validate_input |
4-adj — JSON-schema input | validate_input(args, schema={...}) |
sanitize_output |
2 — prompt-injection scrub | sanitize_output(text, strict=True) |
require_scope |
4 — OAuth 2.1 scope | require_scope("admin") |
Plus: a standalone MCP server exposing shield_audit_tail, shield_audit_summary, shield_red_team, shield_scan_input, shield_patterns_list, shield_sanitize_preview, shield_version — register it alongside any other MCP to inspect what's happening.
Quick start (decorator)
from mcp.server.fastmcp import FastMCP
from mcp_shield import shield_tool
mcp = FastMCP("my-server")
@mcp.tool()
@shield_tool(
name="search_things",
schema={
"type": "object",
"required": ["query"],
"properties": {"query": {"type": "string", "minLength": 1, "maxLength": 200}},
"additionalProperties": False,
},
sanitize_strict=False, # redact matched patterns; True raises
required_scopes="read",
)
async def search_things(query: str) -> str:
result = await upstream.search(query)
return json.dumps(result)
That's it. You now have:
- JSON-schema validation (rejects malformed calls with typed errors)
- JSONL audit log per call (stderr default, override with
MCP_SHIELD_AUDIT_LOG) - Output sanitization with 15 high-precision patterns from MCPTox + OWASP
- isError-compliance (FastMCP sets
isError: trueautomatically) - OAuth scope check
Quick start (operational MCP server)
# Audit logs go to stderr by default
mcp-shield
# Or to a file
MCP_SHIELD_AUDIT_LOG=/var/log/mcp/audit.jsonl mcp-shield
# Or disable audit entirely (for unit tests)
MCP_SHIELD_AUDIT=0 mcp-shield
Then ask your MCP client: "what was the error rate over the last hour?" or "run the red-team suite against my MCP."
What's in the box
src/mcp_shield/
├── __init__.py # public surface
├── audit.py # JSONL audit log (stdlib-only, fail-open, secret-redacting)
├── exceptions.py # typed exception hierarchy (ShieldError, ShieldAuthError, ...)
├── sanitize.py # prompt-injection scrubber (15 curated patterns)
├── server.py # standalone MCP server exposing ops tools
├── shield.py # @shield_tool decorator + ShieldedTool class + require_scope
└── validate.py # JSON-schema input validation (Draft 7 subset)
Engineering standards
Every line of code in this repo follows patterns documented in the engineering playbook — the same playbook used by hawksoft-mcp, ezyvet-mcp, practicepanther-mcp, and the rest of the sanjibani MCP portfolio.
- ruff: full rule set (E/F/W/I/N/UP/B/A/C4/DTZ/T20/PT/Q/RET/SIM/TID/ARG/PTH/ERA/PL/RUF)
- mypy --strict: clean
- pytest: 53 tests, all passing, covering every primitive + edge cases
- py.typed: ships the marker for downstream
mypyto type-check us - stdlib-only: audit + sanitize + validate need no extra deps; only
mcp[cli],httpx,pydantic,structlogfor the wrapper - fail-open: a broken audit sink never breaks the tool (SOC2 baseline)
- JSON-RPC safe: audit goes to stderr (or file), never stdout
Cross-promo footer
If you ship an MCP server, drop the shield on it in 5 minutes. If you don't ship one and just need to harden the ones you use, add the operational MCP server to your fleet.
If you'd rather have someone build a custom MCP server (vertical SaaS, internal tool, anything with a REST/GraphQL/SOAP API), see the engagement page — $25K / $60K / $120K+ tiers.
Other vertical MCPs in this portfolio:
- hawksoft-mcp — independent insurance AMS
- open-dental-mcp — dental practice
- ezyvet-mcp — veterinary practice
- jobber-mcp — home service trades
- practicepanther-mcp — legal practice mgmt
Maintainer
Sanjibani Choudhury [email protected] · github.com/sanjibani