Skip to main content

Production MCP gateway: JWT auth, rate limiting, spending controls, audit log. mcp-guard serve --config mcp-guard.yaml

Project description

mcp-guard

CI PyPI License Python

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 (name mcp-guard was taken). CLI is still mcp-guard.

Demo

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bonanza_mcp_guard-0.1.4.tar.gz (35.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bonanza_mcp_guard-0.1.4-py3-none-any.whl (33.8 kB view details)

Uploaded Python 3

File details

Details for the file bonanza_mcp_guard-0.1.4.tar.gz.

File metadata

  • Download URL: bonanza_mcp_guard-0.1.4.tar.gz
  • Upload date:
  • Size: 35.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for bonanza_mcp_guard-0.1.4.tar.gz
Algorithm Hash digest
SHA256 db23214bacf4152fe90af57be2da0f7b112f4ccc78fa06cbe46d759c4468aa13
MD5 cc3f57fc36be959e58f2bd78f8cb3bf5
BLAKE2b-256 b5df6b19318e4acbdaaeaf10f9b955fd29e1569592bfc7ea2b2d7440cdb68f9f

See more details on using hashes here.

File details

Details for the file bonanza_mcp_guard-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for bonanza_mcp_guard-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 9bbe1ec32f0debdbfc131115e3d4b11f1aa3238e1b81711926a752b14503771d
MD5 203035fafc8976634b14c69919aba214
BLAKE2b-256 87a904b3f7416d5cbc428c4420bb9fdd4e457e28ac488d356237a6939c17ef13

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page