# MCP Guard **Stop your AI agents from doing dangerous things.** [MCP](https://modelcontextprotocol.io) (Model Context Protocol) is how AI agents call tools — like APIs, databases, or deployments. MCP has no built-in access control. MCP Guard adds that layer. Define what's allowed, blocked, or requires approval — in a single YAML file. MCP Guard sits between your MCP client and server, enforces the rules, and logs every decision as an audit receipt. Without MCP Guard, your agent can call any tool. With it, every action is checked. **MCP Guard does not just log — it blocks execution before it happens.** → **Blocks** unsafe tool calls before they execute → **Holds** sensitive actions for human approval → **Logs** every decision as an immutable receipt → **Observe mode** — audit what would be blocked before enforcing

MCP Guard Demo

## Get Started (2 minutes) ```bash # Install npm install @permission-protocol/mcp-guard # Create a policy file cat > pp.config.yaml << 'EOF' default_action: allow rules: - id: block-delete tool: delete_user_data action: block - id: hold-deploy tool: deploy_production action: require_approval EOF # Run your MCP server through the guard mcp-guard --config pp.config.yaml -- node my-mcp-server.js ``` That's it. Your agent can no longer delete user data. Production deploys require approval. Everything is logged to `pp-receipts.jsonl`, and each decision gets a shareable receipt URL. ## 60-Second Demo (no MCP server needed) See MCP Guard block dangerous tool calls in real-time, no setup required: ```bash npx @permission-protocol/mcp-guard demo ``` ## Architecture ``` ┌────────────┐ stdio ┌─────────────┐ stdio ┌────────────┐ │ MCP Client│ ──────────────▶│ MCP Guard │──────────────▶ │ MCP Server │ │ (Claude, │ │ (proxy) │ │ (your app) │ │ Cursor) │ ◀──────────────│ │◀────────────── │ │ └────────────┘ responses └─────────────┘ responses └────────────┘ │ ▼ pp-receipts.jsonl (audit trail) ``` MCP Guard intercepts JSON-RPC messages on stdin/stdout. When it sees a `tools/call` request: 1. Looks up the tool name in the config rules 2. If **allowed** → forwards to the real server 3. If **blocked** → returns a JSON-RPC error (`-32001`) directly 4. If **held for approval** → returns a JSON-RPC error (`-32002`) directly 5. Emits a receipt for every decision (stderr + jsonl file + viewer URL) All other JSON-RPC methods pass through transparently. ## Config Reference ```yaml # pp.config.yaml default_action: allow # "allow" or "block" — applies when no rule matches mode: enforce # "enforce" (default) or "observe" (log only, never block) rules: - id: unique-rule-id # Human-readable identifier tool: tool_name # Exact match on MCP tool name action: allow # allow | block | require_approval ``` ### Modes | Mode | Behavior | |------|----------| | `enforce` | Block/hold tool calls per rules (default) | | `observe` | Log decisions + emit receipts, but always forward (dry-run) | Use `observe` mode to audit what *would* be blocked before turning enforcement on. Switch via config or CLI: `--mode observe`. ### Actions | Action | Behavior | JSON-RPC Error Code | |--------|----------|-------------------| | `allow` | Forward request to server | — | | `block` | Reject immediately | `-32001` | | `require_approval` | Reject with hold status | `-32002` | ## Receipts Every `tools/call` decision generates an immutable receipt: ```json { "receipt_id": "rcpt_dg_mba0m6u7_4e2d7dfbf913", "status": "DENIED", "action": "delete_user_data", "actor": "my-agent", "policy": "block-dangerous-delete", "risk_tier": "critical", "summary": "AI summary: MCP Guard blocked \"delete_user_data\" because matched rule \"block-dangerous-delete\".", "timestamp": "2026-03-20T15:30:00.000Z", "agent_id": "my-agent", "tool_name": "delete_user_data", "decision": "blocked", "reason": "Matched rule \"block-dangerous-delete\"", "rule_id": "block-dangerous-delete", "request_payload_hash": "sha256-hex-string", "target_server": "node my-mcp-server.js", "mode": "enforce", "viewer_url": "https://app.permissionprotocol.com/r/rcpt_dg_mba0m6u7_4e2d7dfbf913" } ``` Receipts are written to: - **stderr** — for real-time monitoring - **pp-receipts.jsonl** — append-only audit file - **`$PP_SHARED_RECEIPTS_PATH`** — optional shared JSONL file for PP/backend ingestion Optional publishing: - Set `PP_RECEIPT_ENDPOINT` to POST each receipt to a backend that stores public receipts - Set `PP_RECEIPT_TOKEN` if that endpoint expects bearer auth - Set `PP_VIEWER_BASE_URL` to override the default `https://app.permissionprotocol.com/r` The `request_payload_hash` is a SHA-256 of the full request params, so you can verify what was sent without storing sensitive arguments. The emitted receipt now carries PP-style viewer fields (`summary`, `risk_tier`, `enrichmentSnapshot`, `diff`, `policy_details`) so the same JSON object can be stored or forwarded for the hosted receipt viewer. Each decision also logs a direct link like `https://app.permissionprotocol.com/r/` so the user can inspect the decision in the Permission Protocol viewer once the receipt is available to the backend. ## CLI ``` mcp-guard [options] -- Options: --config Path to config file (default: ./pp.config.yaml) --agent-id Agent identifier for receipts (default: "unknown") --mode enforce or observe (overrides config file) --approval-port Enable approval UI on this port (e.g. 3100) -h, --help Show help ``` ### Example: Claude Desktop In your Claude Desktop MCP config, replace: ```json { "mcpServers": { "my-server": { "command": "node", "args": ["my-mcp-server.js"] } } } ``` With: ```json { "mcpServers": { "my-server": { "command": "mcp-guard", "args": ["--config", "pp.config.yaml", "--agent-id", "claude-desktop", "--", "node", "my-mcp-server.js"] } } } ``` ## Approval Flow (v0.1) When a tool call matches a `require_approval` rule, MCP Guard can hold the request open and wait for human approval via a web UI — instead of rejecting immediately. ```bash # Start with approval UI on port 3100 mcp-guard --config pp.config.yaml --approval-port 3100 -- node my-mcp-server.js # Open http://localhost:3100 to see pending approvals ``` **How it works:** 1. Agent calls a tool that requires approval 2. MCP Guard holds the JSON-RPC response open (does NOT return an error) 3. The tool call appears in the approval UI at `http://localhost:` 4. Human clicks **Approve** → request is forwarded to the server, response returned to the agent 5. Human clicks **Deny** → JSON-RPC error (-32002) returned to the agent When `--approval-port` is **not** set, `require_approval` tools return an error immediately (original behavior, no change). The approval UI auto-refreshes every 3 seconds and shows: - Pending tool calls with collapsible arguments - Recent decision history (last 20) ## Why not just use X? | Approach | Why MCP Guard is different | |----------|---------------------------| | API gateway | MCP operates at the tool-call layer, not HTTP. MCP Guard understands tool names and MCP's JSON-RPC protocol. | | RBAC / IAM | Those gate *users*. MCP Guard gates *agent actions* — the agent is already authenticated, the question is what it's allowed to *do*. | | "Just code it" | You could. MCP Guard gives you a declarative config, audit receipts, observe mode, and an approval UI — without changing your server code. | ## What This Is NOT - **Not a dashboard.** It's a proxy. It sits in the data path and enforces rules. - **Not a scanner.** It doesn't analyze your code or model outputs. It gates tool calls. - **Not compliance theater.** Every decision has a cryptographic receipt. - **Not a replacement for good architecture.** It's one layer in a defense-in-depth strategy. Agents shouldn't be able to execute arbitrary actions without an explicit decision point. ## Part of Permission Protocol MCP Guard is a building block of the [Permission Protocol](https://github.com/permission-protocol) — explicit authority for autonomous AI systems. ## License MIT