mcp-guard
Put auth, rate limits, spend caps, and audit logs in front of any MCP server — without changing the server.
pip install "bonanza-mcp-guard[yaml]"
mcp-guard scan
mcp-guard serve --config mcp-guard.yaml
PyPI: package
bonanza-mcp-guard(namemcp-guardwas taken). CLI is stillmcp-guard.
Sits in front of any MCP server over stdio. Zero required dependencies.
Complements static scanners like mcp-scan with a runtime stdio gateway (auth, limits, audit).
Why mcp-guard?
| Without mcp-guard | With mcp-guard |
|---|---|
| Any agent calls any tool | Agent must authenticate (API key / JWT) |
| No spend ceiling | Per-session spend caps, per-hour rate limits |
| No audit trail | Every request logged to JSONL |
| Server exposed directly | Gateway wraps server — zero code changes |
Complements static scanners like mcp-scan with a runtime stdio gateway (auth, limits, audit).
The problem
Security research in 2026 reported 1,800+ internet-exposed MCP endpoints with no authentication. Any MCP client can invoke tools with no identity, no spend ceiling, and no audit trail.
mcp-guard adds a gateway layer — Claude Desktop, Cursor, Windsurf, or custom agents talk to mcp-guard; it talks to your real MCP server.
30-second try
pip install "bonanza-mcp-guard[yaml]"
mcp-guard scan
Example output:
📁 ~/Library/Application Support/Claude/claude_desktop_config.json
ℹ️ No mcpServers defined
Summary: 0 critical, 0 warnings
Then add a config and run the gateway:
mcp-guard serve --config mcp-guard.yaml
Wire into Claude Desktop:
{
"mcpServers": {
"guarded": {
"command": "mcp-guard",
"args": ["serve", "--config", "/path/to/mcp-guard.yaml"]
}
}
}
Quickstart
1. Install
pip install "bonanza-mcp-guard[yaml]"
1b. Audit your machine (no server needed)
mcp-guard scan
2. Configure (mcp-guard.yaml)
auth:
mode: api_key
keys:
- "sk-agent-1"
- "sk-agent-2"
servers:
filesystem:
command: npx @modelcontextprotocol/server-filesystem /data
policies:
max_spend_per_session: 10.00
audit_log: /var/log/mcp-guard.jsonl
rate_limit:
requests_per_minute: 100
spend_per_hour_usd: 50.00
3. Agents include auth in requests
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"_meta": { "api_key": "sk-agent-1" },
"name": "wallet_request",
"arguments": { "amount": 1.50 }
}
}
Architecture
MCP Client (Claude Desktop / Cursor / custom agent)
│
▼ JSON-RPC over stdio
┌──────────────┐
│ mcp-guard │ auth → rate limit → spend check → audit
└──────────────┘
│
▼ forwards allowed requests
Your MCP Server (filesystem, wallet, fetch, anything)
1,168 lines of Python. Zero required dependencies.
Config reference
| Key | Type | Default | Description |
|---|---|---|---|
auth.mode |
none | api_key | jwt |
none |
Authentication mode |
auth.keys |
list[str] |
[] |
Valid API keys (mode=api_key) |
auth.jwt_secret |
str |
— | HMAC-SHA256 secret (mode=jwt) |
policies.rate_limit.requests_per_minute |
int |
∞ |
Max requests per agent per minute |
policies.rate_limit.spend_per_hour_usd |
float |
∞ |
Max spend per agent per hour |
policies.max_spend_per_session |
float |
∞ |
Session spend cap |
policies.require_approval_above |
float |
∞ |
Threshold for manual approval |
policies.audit_log |
str |
— | JSONL audit log path |
servers |
map |
{} |
Backend MCP servers to proxy to |
Authentication modes
API Key (simplest)
auth:
mode: api_key
keys:
- "sk-agent-1"
Keys: _meta.api_key, _meta.token, Authorization: Bearer, or X-API-Key.
JWT (multi-agent)
auth:
mode: jwt
jwt_secret: "${JWT_SECRET}"
from mcp_guard.auth import create_jwt
token = create_jwt(secret="my-secret", agent_id="agent-007", ttl_seconds=3600)
None (dev/local)
auth:
mode: none
Rate limiting & spending
policies:
rate_limit:
requests_per_minute: 60
spend_per_hour_usd: 25.00
max_spend_per_session: 10.00
require_approval_above: 2.00
Spending tools: wallet_request, wallet_pay, x402_pay, etc. Blocked payments: JSON-RPC -32002.
Use with agent-budget (pip install bonanza-labs-agent-budget) for @budget on LLM calls.
Audit log
policies:
audit_log: /var/log/mcp-guard.jsonl
Programmatic use
from mcp_guard import MCPProxy, GuardConfig
config = GuardConfig.from_dict({
"auth": {"mode": "api_key", "keys": ["sk-abc"]},
"policies": {"max_spend_per_session": 25.0},
})
proxy = MCPProxy.from_config(config)
Related (Bonanza Labs)
| Project | Role |
|---|---|
| agent-budget | @budget(max_usd=…) for LLM + x402 |
| bonanza-labs-fork-doctor | Repo health before you ship |
GitHub Action — scan MCP configs on PRs
Automatically scan MCP config files in pull requests for security issues (missing auth, exposed remote URLs, no guard wrapper).
# .github/workflows/mcp-scan.yml — already included in this repo
# Triggers on: **/claude_desktop_config.json, **/mcp.json, **/*mcp*.json/yaml
Add it to your repo:
uses: c6zks4gssn-droid/mcp-guard/.github/workflows/mcp-scan.yml@main
Or copy the workflow file directly. Fails PRs with critical findings and posts a comment with the full report.
Roadmap
v0.1.4 (current)
- Docker image (
docker run mcp-guard) - HTTP/SSE transport (
mcp-guard serve-http) - Prometheus metrics endpoint (
GET /metrics) - Approval queue — hold tool calls above threshold for human approval
- Multi-server routing (one gateway, many backends)
- OAuth2 provider for agent-to-agent auth (PKCE + Device flow)
- SQLite persistent approval queue (CLI: list/approve/deny)
Future
- Tool allowlist/denylist per agent
- Standalone GitHub Action (reusable workflow)
- Web dashboard for audit log visualization
- Plugin system for custom policy checks
Launch: LAUNCH.md
Approval queue
When a spending tool call exceeds require_approval_above, the proxy holds it and returns error -32004 with the approval_id. A human reviews and approves/denies via the CLI:
policies:
require_approval_above: 5.00
# List pending
mcp-guard approvals list --db ~/.mcp-guard/approvals.db
# Approve (short ID works)
mcp-guard approvals approve abc12345 --by admin
# Deny with reason
mcp-guard approvals deny abc12345 --by admin --reason "vendor untrusted"
Pending requests are persisted in SQLite — survive restarts.
HTTP/SSE transport
For remote agents that connect over HTTP instead of stdio:
mcp-guard serve-http --config mcp-guard.yaml --port 8080
Endpoints:
| Method | Path | Description |
|---|---|---|
POST |
/rpc |
JSON-RPC request → response |
GET |
/health |
Health check |
GET |
/metrics |
Prometheus metrics |
Auth over HTTP: X-API-Key header or Authorization: Bearer header.
curl -X POST http://localhost:8080/rpc \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-agent-1" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Docker
# Build
DOCKER_BUILDKIT=1 docker build -t mcp-guard .
# Run with config
DOCKER_BUILDKIT=1 docker run --rm -i \
-v $(pwd)/mcp-guard.yaml:/etc/mcp-guard/config.yaml:ro \
-v $(pwd)/logs:/var/log/mcp-guard \
mcp-guard serve --config /etc/mcp-guard/config.yaml
Or with docker compose:
docker compose up
CI publishes to ghcr.io/c6zks4gssn-droid/mcp-guard on every tag.
Multi-server routing
One mcp-guard gateway can sit in front of multiple MCP backends. Clients connect once; the router fans out requests to the right backend based on tool name, method prefix, or explicit override.
servers:
bonanza:
command: bonanza-mcp serve
filesystem:
command: npx server-filesystem /data
search:
command: python -m search_server
routes:
- mode: tool
tool_name: wallet_pay
target: bonanza
- mode: tool
tool_name: read_file
target: filesystem
- mode: method
method_prefix: notifications/
target: filesystem
- mode: default
target: search
Or override per request:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"_meta": { "target_server": "bonanza" },
"name": "wallet_pay",
"arguments": {"amount": 5}
}
}
If no rule matches and target_server is unknown, the gateway returns -32005 no_route_matched.
OAuth2
Agents can authenticate with OAuth2 instead of static API keys. Two flows:
Authorization Code + PKCE (for interactive agents):
auth:
mode: oauth2
client_id: mcp-agent
from mcp_guard.oauth import OAuth2Provider, generate_pkce_pair
oauth = OAuth2Provider()
verifier, challenge = generate_pkce_pair()
ac = oauth.authorize("mcp-agent", "agent-1", challenge, "S256")
tr = oauth.token_exchange("authorization_code", ac.code, verifier, "mcp-agent")
# Use tr.access_token in Authorization: Bearer ...
Device Authorization Grant (for headless agents):
device = oauth.device_authorize("mcp-agent")
print(f"Visit {device.verification_uri} and enter code {device.user_code}")
# User approves via POST /oauth/device/approve
# Agent polls: oauth.device_poll(device.device_code, "mcp-agent")
Refresh tokens rotate, introspection checks validity, revocation kills tokens. All in-memory by default — plug a Redis/SQLite backend for multi-replica deployments.
Contributing
PRs welcome. The codebase is 1,168 lines across 8 modules — small enough to hold in your head.
git clone https://github.com/c6zks4gssn-droid/mcp-guard
cd mcp-guard
pip install -e ".[yaml,dev]"
pytest
Project structure
mcp_guard/
├── __main__.py # CLI entry point (serve, scan)
├── config.py # GuardConfig + YAML/JSON loader
├── auth.py # API key + JWT providers
├── proxy.py # MCPProxy — the intercept engine
├── ratelimit.py # Per-agent rate limiting
├── audit.py # JSONL audit logger
└── scan.py # Local config scanner
License
Apache 2.0 · Bonanza Labs
Metadata
Release files for bonanza-mcp-guard 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| bonanza_mcp_guard-0.1.4.tar.gz | 35.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bonanza_mcp_guard-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.3 kB
Release files / bonanza_mcp_guard-0.1.4.tar.gz
| Download URL | bonanza_mcp_guard-0.1.4.tar.gz |
|---|---|
| Size | 35.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
db23214bacf4152fe90af57be2da0f7b112f4ccc78fa06cbe46d759c4468aa13
|
|
BLAKE2b-256 checksum How to use checksums |
b5df6b19318e4acbdaaeaf10f9b955fd29e1569592bfc7ea2b2d7440cdb68f9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / bonanza_mcp_guard-0.1.4-py3-none-any.whl
| Download URL | bonanza_mcp_guard-0.1.4-py3-none-any.whl |
|---|---|
| Size | 33.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9bbe1ec32f0debdbfc131115e3d4b11f1aa3238e1b81711926a752b14503771d
|
|
BLAKE2b-256 checksum How to use checksums |
87a904b3f7416d5cbc428c4420bb9fdd4e457e28ac488d356237a6939c17ef13
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Security for bonanza-mcp-guard 0.1.4
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.