MCP server by raj8github
👁️ mcp-warden
MCP observability and control plane.
pip install mcp-warden
mcp-warden audit -- your-mcp-server [args...]
mcp-warden guard --max-calls-per-minute 30 --block-tool write_query -- any-mcp-server
mcp-warden both --max-calls-per-minute 60 --max-response-kb 512 -- any-mcp-server
mcp-warden log --tail 20
The problem
MCP clients like Cline and Claude Desktop can call tools on any connected server with no visibility and no throttle.
An agent in a loop can:
- Call
sql_queryhundreds of times a minute, burning your API budget - Pull back 50 MB of data in a single tool call
- Hit a tool you meant to leave off
- Fail repeatedly and nobody notices until the damage is done
mcp-warden fixes all of this — for any MCP server, without touching it.
How it works
MCP client (Cline / Claude Desktop)
│ stdin/stdout
▼
mcp-warden ◄──── audit + guard
│ subprocess stdin/stdout
▼
Upstream MCP server (any MCP server)
mcp-warden is a transparent JSON-RPC relay. The client never knows it's there. The upstream server never knows it's there. Every tools/call passes through the audit and guard layer in the middle.
Latency impact: ~0.1–0.5 ms per message — negligible against typical LLM tool call round trips of 100 ms to 10 s.
Install
pip install mcp-warden
Zero runtime dependencies. Pure Python 3.9+ stdlib.
Usage
Audit — log everything
mcp-warden audit \
--audit-log ~/.mcp_warden/audit.jsonl \
--session-id cline \
-- your-mcp-server [args...]
Each line of the JSONL log is a self-contained event:
{"ts":"2025-03-04T10:22:01.234Z","session_id":"cline","direction":"request","msg_id":"1","method":"tools/call","tool":"sql_query","args":{"sql":"SELECT TOP 10 * FROM Product"}}
{"ts":"2025-03-04T10:22:01.376Z","session_id":"cline","direction":"response","msg_id":"1","method":"tools/call","tool":"sql_query","duration_ms":142.3,"content_len":4821,"is_error":false,"error_msg":null}
Guard — rate limits + blocking
mcp-warden guard \
--max-calls-per-minute 30 \
--per-tool-limit sql_query:10 \
--block-tool write_query \
--max-response-kb 512 \
--max-errors 5 \
-- any-mcp-server
When a limit is hit, the agent gets a clear error:
Tool 'write_query' is in the blocked list.
Global rate limit reached (30 calls/min). Retry in ~42s.
Response too large (1024.0 KB > 512 KB limit). Consider narrowing your query.
Circuit breaker open — too many consecutive errors. Auto-reset in 58s.
Both — recommended
mcp-warden both \
--audit-log ~/.mcp_warden/audit.jsonl \
--max-calls-per-minute 60 \
--per-tool-limit sql_query:20 \
--block-tool write_query \
--max-response-kb 1024 \
--max-errors 3 \
-- your-mcp-server [args...]
View the log
mcp-warden log --tail 20
mcp-warden log --tail 50 --filter-tool sql_query
mcp-warden log --errors-only
mcp-warden log --json | jq 'select(.is_error == true)'
Claude Desktop / Cline setup
Create a wrapper script:
#!/bin/bash
# ~/.config/mcp_warden/mydb.sh
exec mcp-warden both \
--audit-log ~/.mcp_warden/mydb.jsonl \
--max-calls-per-minute 60 \
--max-response-kb 1024 \
--max-errors 3 \
-- /path/to/your-mcp-server.sh
Reference it in your MCP config:
{
"mcpServers": {
"mydb": { "command": "/Users/you/.config/mcp_warden/mydb.sh" }
}
}
Guard options
| Option | Default | Description |
|--------|---------|-------------|
| --max-calls-per-minute N | 0 (off) | Global cap across all tools per 60 s window |
| --per-tool-limit TOOL:N | — | Per-tool cap e.g. sql_query:20 (repeatable) |
| --block-tool TOOL | — | Block a tool entirely (repeatable) |
| --max-response-kb KB | 0 (off) | Reject responses larger than this |
| --max-errors N | 0 (off) | Trip circuit breaker after N consecutive errors |
| --circuit-cooldown SECS | 60 | Seconds before circuit auto-resets |
Audit log schema
| Field | Direction | Description |
|-------|-----------|-------------|
| ts | all | ISO-8601 UTC timestamp |
| session_id | all | Set via --session-id |
| direction | all | request, response, or blocked |
| msg_id | all | JSON-RPC message id |
| method | all | e.g. tools/call, tools/list |
| tool | all | Tool name (tools/call only) |
| args | request | Tool arguments (truncated at --max-arg-bytes) |
| duration_ms | response | Round-trip milliseconds |
| content_len | response | Response size in bytes |
| is_error | response | True if upstream returned an error |
| error_msg | response | Error message text |
| reason | blocked | Why the request was blocked |
Python API
from mcp_warden import MCPWarden, AuditLogger
from mcp_warden.guard import GuardConfig
proxy = MCPWarden(
cmd=["your-mcp-server", "--arg", "value"],
audit_logger=AuditLogger(path="./audit.jsonl", session_id="my-agent"),
guard_config=GuardConfig(
max_calls_per_minute=30,
per_tool_limits={"sql_query": 10},
blocked_tools=["write_query"],
max_response_kb=512,
max_errors_before_circuit_break=3,
),
)
proxy.run()
Roadmap
- [ ] Async audit write queue (decouple disk I/O from relay path)
- [ ] HTTP/SSE transport support
- [ ] Prometheus metrics endpoint
- [ ] Per-session budget and cost tracking
- [ ] Webhook alerts on circuit break / blocked calls
- [ ] Web dashboard for log exploration
Contributing
git clone https://github.com/raj8github/mcp-warden
cd mcp-warden
pip install -e ".[dev]"
pytest tests/ -v
See CONTRIBUTING.md.
License
MIT — see LICENSE