Zero codeZero dependenciesWorks with every MCP client

mcp-guard

A firewall for your AI agent's tools. Your assistant's GitHub server can delete repos; your database server can drop tables. The model is polite — but politeness is not a security boundary. mcp-guard wraps any MCP server with hard limits, human approval gates, and a full audit log.

One config change. The wrapped server needs no modifications and never knows the guard is there.

An agent tries to call delete_repo; mcp-guard blocks it before the GitHub server sees the call; the assistant recovers gracefully and offers to archive instead; the block is recorded in the JSONL audit log

60-second setup

In your MCP client config (Claude Desktop, Cursor, Windsurf, Cline, Zed — anything that speaks MCP over stdio), wrap the server command:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@aroha-sdk/mcp-guard",
               "--block", "delete_*",
               "--gate",  "merge_*,create_repository",
               "--limit", "create_*:10",
               "--",
               "npx", "-y", "@modelcontextprotocol/server-github"]
    }
  }
}

Everything after -- is your original server command, unchanged.

Four rules, fail-closed

--block <globs>Always deny

Matching tools are refused every time. The model gets a clear denial message; the wrapped server never sees the call.

--block "delete_*,drop_*,execute_sql"
--limit <glob>:<n>Session budget

At most n calls per session across all tools matching the glob. The n+1th call is denied with an explicit do-not-retry message the model understands.

--limit "create_*:10,send_email:1"
--gate <globs>Human approval

Each call pauses and asks you first — your MCP client shows an approve/deny prompt with the tool name and arguments (via MCP elicitation). Timeout or unsupported client = deny. Fails closed, always.

--gate "merge_*,deploy_*,publish_*"
(always on)Audit log

Every decision is appended to ~/.aroha/mcp-guard.jsonl with a session receipt on exit — what ran, what was denied, what you approved. Disable with --no-log.

--name "github" --log ./github-audit.jsonl

Every ambiguous situation resolves to deny — approval timeouts, clients without elicitation support, malformed rules at startup. A guard that fails open is decoration.

Why a proxy, not harness config

Because permission logic implemented inside an agent framework scatters. The Harness Handbook (Wang et al., arXiv:2607.13285) traced one rule — “confirm before file deletion” — through a production harness and found it implemented across 2,267 files. Scattered rules drift, fork, and silently break on refactor; auditing them is archaeology.

A boundary proxy inverts that: one rule, one enforcement point, one audit log — uniform across every server you wrap, immune to prompt injection against the server (it can't be talked out of a proxy it doesn't know exists), and unchanged when the harness behind it is rewritten. Guardrails as architecture, not as documentation. Each rule's enforcement site and proving test are listed in the guarantee-evidence table.

Recipes

Read-only database

--block "insert_*,update_*,delete_*,drop_*,execute_*"

Filesystem with a write budget

--limit "write_file:20" --block "delete_*" --gate "move_*"

Anything irreversible needs a human

--gate "send_*,delete_*,merge_*,deploy_*,publish_*"

Observe before you restrict

--name "github"   # audit log only, no rules

When you outgrow globs

mcp-guard rules are session-scoped and local. When you need authority that is signed, verifiable, delegable across agents, and auditable across organisations — spending mandates, task mandates, receipts — the same concepts become Ed25519-signed mandates in the full Aroha Protocol.