{
  "markdown": "# protect-mcp\n\nFail-closed Cedar policy gate plus signed receipts for AI agent tool calls.\n\n[![npm version](https://img.shields.io/npm/v/protect-mcp)](https://www.npmjs.com/package/protect-mcp)\n[![downloads](https://img.shields.io/npm/dm/protect-mcp)](https://www.npmjs.com/package/protect-mcp)\n[![license](https://img.shields.io/npm/l/protect-mcp)](https://www.npmjs.com/package/protect-mcp)\n[![node](https://img.shields.io/node/v/protect-mcp)](https://www.npmjs.com/package/protect-mcp)\n\n`protect-mcp` is a gate that sits in front of an AI agent's tool calls. It evaluates\neach call against a [Cedar](https://www.cedarpolicy.com/) policy (the same language\nAWS uses for IAM), blocks what breaks the rules before it runs, and signs an\noffline-verifiable Ed25519 receipt of every decision. It runs locally, sends no\ntelemetry of your decisions anywhere, and is MIT licensed.\n\n## Why it is different\n\n- **Fail-closed by default.** On any policy error, a missing engine, or an\n  evaluation failure, the decision is DENY. The gate never silently allows. An\n  observe mode exists for shadow rollout, but even there a call that would be\n  blocked is flagged `would_deny: true`, so a failure is never silent.\n- **It proves its own restraint.** `serve --enforce` and `doctor` run a startup\n  self-test and refuse to arm the gate unless they can show that a known-forbidden\n  action is actually denied. A gate that cannot prove it denies does not start.\n- **Every decision is a receipt anyone can verify.** Decisions are Ed25519-signed\n  and verifiable offline with [`@veritasacta/verify`](https://www.npmjs.com/package/@veritasacta/verify).\n  No vendor trust required: the math does not care who runs it.\n\n## Quickstart: install to first useful proof\n\n```bash\n# 1. Generate an Ed25519 keypair, config template, and sample policy.\nnpx protect-mcp init\n\n# 2. Wrap any MCP server in shadow mode. Nothing is blocked yet; calls are logged.\nnpx protect-mcp wrap -- node your-mcp-server.js\n\n# 3. Inspect the local-only dashboard: tool inventory, risk, approvals, receipts.\nnpx protect-mcp dashboard --open\n\n# 4. Draft a reviewable policy from observed calls.\nnpx protect-mcp recommend --write\n\n# 5. When reviewed, restart the wrapper in enforce mode with that policy.\nnpx protect-mcp --policy protect-mcp.recommended.json --enforce -- node your-mcp-server.js\n```\n\nFor Claude Desktop, run a dry-run config patch first, then apply it:\n\n```bash\nnpx protect-mcp wrap --claude-desktop\nnpx protect-mcp wrap --claude-desktop --write\nnpx protect-mcp dashboard --open\n```\n\nThe dashboard binds to `127.0.0.1`, reads only local log/receipt files, and does\nnot upload anything. Use `npx protect-mcp connect` only if you explicitly want a\nhosted ScopeBlind dashboard.\n\n## The gate as an MCP server\n\nIf you would rather call the gate as tools than wire the Claude Code hooks, run\nit as an MCP server:\n\n```bash\nnpx protect-mcp mcp\n```\n\nIt speaks MCP over stdio and exposes four read-only tools, the whole loop:\n\n- **`evaluate_action`**: decide a proposed tool call against an inline Cedar policy, fail-closed (any policy error is DENY). Returns `{ allowed, decision, reason, policy_digest }`.\n- **`sign_decision`**: turn a decision into an Ed25519 signed receipt (a denial signs a `gateway_restraint`, an allow a `decision_receipt`). Returns the receipt and its public key; generates an ephemeral key if you do not supply one.\n- **`verify_receipt`**: verify a signed receipt offline against a public key. Returns `{ valid, error, type, kid, issuer }`.\n- **`self_test`**: prove it, no inputs. A known-forbidden action is denied, then a signed receipt round-trips and a tampered copy fails.\n\nPoint any MCP host at it, for example Claude Desktop:\n\n```json\n{\n  \"mcpServers\": {\n    \"protect-mcp\": { \"command\": \"npx\", \"args\": [\"-y\", \"protect-mcp\", \"mcp\"] }\n  }\n}\n```\n\nReceipts are byte-compatible with the ones the gate signs at runtime, so a\nreceipt minted here verifies with [`@veritasacta/verify`](https://www.npmjs.com/package/@veritasacta/verify)\nand the browser verifier just the same.\n\n### Local Action Dashboard\n\n`protect-mcp dashboard` is the operator view for moving from visibility to\nenforcement:\n\n- **Tool inventory:** every observed tool, call count, high/medium/low risk, and\n  whether the active policy has an exact rule, a wildcard fallback, or no rule.\n- **Policy coverage:** one-click local policy edits for `Require approval`,\n  `Block`, or `Observe`. Restart the wrapper after reviewing changes.\n- **Exact-action approval queue:** the exact tool, action, destination, redacted\n  payload preview, payload hash, policy basis, and reason capture before a human\n  approves, denies, edits, or takes over.\n- **Receipt chain:** request ids correlated with signed receipt hashes, so an\n  audit reviewer can see which decisions have cryptographic proof.\n- **Audit export:** downloads the offline-verifiable audit bundle when signed\n  receipts exist. If only unsigned local logs exist, the dashboard explains that\n  signing must be enabled first.\n\nFor live desktop fallback approvals, start the dashboard with the local gateway\napproval endpoint and nonce printed by the wrapper:\n\n```bash\nnpx protect-mcp dashboard --open \\\n  --approval-endpoint http://127.0.0.1:9876 \\\n  --approval-nonce \"$PROTECT_MCP_APPROVAL_NONCE\"\n```\n\n`Approve` forwards to the live local gateway when those flags are present.\n`Deny`, `Edit`, and `Take over` are recorded locally as approval-resolution\nrecords; use them as the operator instruction and rerun the tool when needed.\n\n### Paid Boundary MVP: digest anchoring, not data upload\n\nLocal self-signed receipts stay free and offline-verifiable. The paid boundary is\nindependent evidence that ScopeBlind saw a receipt digest at a time, under an org\nidentity, without receiving the raw prompt, tool payload, output, private key, or\nraw receipt.\n\n```bash\n# Create or refresh a local org identity and public-key directory.\nnpx protect-mcp registry init --org \"Meridian Global Macro\" --billing-account acct_meridian\n\n# Local preview: writes a digest registry and shareable static verifier page.\nnpx protect-mcp registry anchor\n\n# Hosted mode: uploads receipt digests only for independent anchoring.\nSCOPEBLIND_TOKEN=... npx protect-mcp registry anchor \\\n  --hosted \\\n  --endpoint https://api.scopeblind.com \\\n  --verifier-base https://legate.scopeblind.com\n```\n\nThe local preview is deliberately labeled `local-preview-not-independent`.\nHosted mode anchors only receipt hashes, request ids, org public keys, and\nbilling metadata. It does not upload raw receipts or sensitive context.\n\n### Killer Demo: shadow to policy to proof\n\n`protect-mcp killer-demo` generates a complete three-minute sales/demo pack:\n\n```bash\nnpx protect-mcp killer-demo --dir ./scopeblind-demo\n```\n\nIt creates mock filesystem, GitHub, email, and PMS activity; shows risky calls in\nshadow mode; applies a policy pack; requires approval for a sensitive PMS booking;\nexecutes through the gateway; writes a signed receipt; proves the original\nreceipt verifies; proves a tampered receipt fails; and creates a selective\ndisclosure package that hides sensitive context while showing the minimum proof.\n\nOpen the generated `DEMO-RUNBOOK.md` first. Then run the printed dashboard\ncommand to walk a customer through the exact sequence.\n\n### Selective Disclosure v0\n\nCommitment-mode receipts can carry a `committed_fields_root` instead of exposing\nevery field in cleartext. Later, the holder can disclose selected fields only:\n\n```bash\nnpx protect-mcp verify-disclosure \\\n  --receipt ./receipts/selective-disclosure.receipt.json \\\n  --disclosure ./receipts/selective-disclosure.tool-only.json\n```\n\nThe verifier checks the parent receipt hash, Ed25519 signature, commitment root,\nand each disclosed field's Merkle proof. It then explains which fields were\ndisclosed and which committed fields remain hidden. This is salted commitment\ndisclosure, not full zero-knowledge, but it makes the privacy claim concrete:\nauditors can verify selected facts without receiving the full tool payload or\nsensitive desk context.\n\n### Prove a claim over the record (position-blind attestations)\n\nYou can prove a CLAIM over your record without revealing it. Mint a signed,\nposition-blind attestation over the whole record that discloses only per-decision\ncategories (a receipt digest, the verdict, capability tags), never your tool\ninputs, outputs, or data:\n\n```bash\n# \"No action reached the network across the record\":\nnpx protect-mcp claim --no net.egress\n\n# other predicates:\n#   --only fs.read,fs.write     all actions were confined to these capabilities\n#   --no-verdict blocked        no action was blocked\n#   --count blocked             how many were blocked\n```\n\nAnyone verifies it offline, seeing only the categories, never the content:\n\n```bash\nnpx protect-mcp verify-claim claim-<id>.json\n```\n\nThe verifier recomputes a Merkle root over the disclosed set and recomputes the\npredicate independently, so the issuer cannot lie about the claim given the\ndisclosure. Add `--anchor` to record the claim's digest in the public,\nappend-only ScopeBlind transparency log, so a counterparty who does not trust you\ncan confirm the disclosed set is complete and was not quietly re-cut (only the\nhash is sent; the record stays local):\n\n```bash\nnpx protect-mcp claim --no net.egress --anchor\n```\n\nThis is an accountable, position-blind attestation, not full zero-knowledge: it\nreveals the shape, not the content.\n\n## Try it in 60 seconds (no agent required)\n\n[![Watch the two-minute demo film](https://legate.scopeblind.com/media/scopeblind-demo-poster.jpg)](https://legate.scopeblind.com/record)\n\nWatch the two-minute film at [legate.scopeblind.com/record](https://legate.scopeblind.com/record), then replay it against your own copy:\n\n```bash\nnpx protect-mcp sample     # seed a labeled sample record (8 decisions: 1 blocked, 2 payments)\nnpx protect-mcp record     # open it: signatures verified in your browser\n\nnpx protect-mcp claim --payment-under 100 --anchor --output payments-under-100.json\nnpx protect-mcp verify-claim payments-under-100.json\nnpx protect-mcp anchor-record\n```\n\nDrop the generated `demo-tampered.jsonl` into the record page to watch a\npost-signing edit get caught. `sample` refuses to touch an existing record, so\nrun it in an empty folder. When you are ready for the real thing, wire the gate\nbelow and the same commands run against your agent's own record.\n\n## Claude Code hook quickstart\n\n```bash\n# Generate hook config and a sample Cedar policy.\nnpx protect-mcp init-hooks\n\n# Serve the Claude Code hook gate in enforce mode. It runs a restraint self-test\n# first and refuses to start if it cannot prove it denies a forbidden vector.\nnpx protect-mcp serve --enforce --cedar ./cedar\n```\n\nOne-shot evaluation, the way a PreToolUse hook calls it. Exit code 2 means deny\n(the tool is blocked); exit 0 means allow:\n\n```bash\nnpx protect-mcp evaluate --cedar ./cedar --tool Bash --input '{\"command\":\"rm\"}'\necho $?   # 2  -> denied, fail-closed\n\nnpx protect-mcp evaluate --cedar ./cedar --tool Read --input '{\"path\":\"README.md\"}'\necho $?   # 0  -> allowed\n```\n\nA missing or unloadable policy denies (exit 2) unless you explicitly pass\n`--fail-on-missing-policy false`.\n\n## Claude Code hooks\n\n`protect-mcp init-hooks` writes a `.claude/settings.json` for you. To wire the\ngate by hand, the two verbs you need are `evaluate` (PreToolUse, blocks on exit 2)\nand `sign` (PostToolUse, records a receipt). Pin the version so a Claude Code\nsession always runs the gate you tested:\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"npx protect-mcp@0.9.1 evaluate --cedar ./cedar --tool \\\"$TOOL_NAME\\\" --input \\\"$TOOL_INPUT\\\"\"\n          }\n        ]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"npx protect-mcp@0.9.1 sign --tool \\\"$TOOL_NAME\\\" --receipts ./receipts --key ./keys/gateway.json\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n`evaluate` exits 2 on deny so Claude Code blocks the tool call, and 0 on allow.\n`sign` is best-effort: it appends an Ed25519-signed receipt when a key is\nconfigured, and if no signer is available it records an honest unsigned line\n(`\"signed\": false`) rather than failing the tool.\n\n## Use it in other agents (Codex, Cursor, Gemini, Hermes)\n\nThe same fail-closed gate runs as a tool hook in any agent that supports them. Add\n`--format <host>` so the verb reads that host's hook payload from stdin and denies\nin its contract:\n\n```bash\n# the PreToolUse / before-tool command for each host\nnpx -y protect-mcp@latest evaluate --format codex  --cedar ./cedar   # OpenAI Codex\nnpx -y protect-mcp@latest evaluate --format gemini --cedar ./cedar   # Gemini CLI BeforeTool\nnpx -y protect-mcp@latest evaluate --format cursor --cedar ./cedar   # Cursor beforeShellExecution\nnpx -y protect-mcp@latest evaluate --format hermes --cedar ./cedar   # Hermes pre_tool_call\n```\n\nPair each with `sign --format <host>` on the post-tool event for receipts. The\nimportant case is **Hermes**, which ignores hook exit codes and reads the verdict\nfrom stdout, so `--format hermes` denies via `{\"decision\":\"block\"}` rather than\nexit 2 (a raw exit-2 would silently fail open there). Without `--format`, the\nverbs read `--tool`/`--input` flags exactly as in the Claude Code section above.\n\n## Write a policy\n\nCedar policies live in a directory you point at with `--cedar`. A `forbid` rule\ndenies, a `permit` rule allows. To match against a value in the tool input, use\nthe `.contains()` idiom:\n\n```cedar\n// Allow read-only tools.\npermit(\n  principal,\n  action == Action::\"MCP::Tool::call\",\n  resource == Tool::\"Read\"\n);\n\n// Deny dangerous shell commands by matching the command against a list.\nforbid(\n  principal,\n  action == Action::\"MCP::Tool::call\",\n  resource == Tool::\"Bash\"\n) when {\n  [\"rm\", \"dd\", \"mkfs\"].contains(context.command)\n};\n\n// Block destructive tools outright.\nforbid(\n  principal,\n  action == Action::\"MCP::Tool::call\",\n  resource == Tool::\"delete_file\"\n);\n```\n\n> **Hazard:** do NOT write `context.command in [\"rm\", \"dd\"]` to match a string\n> against a list. `in` is for entity hierarchies, not string membership. Cedar\n> treats the expression as a type error and silently discards the whole `forbid`\n> rule, which (under a fail-open gate) leaves a residual `permit` standing. This\n> is the exact defect behind the advisory below. Use `[...].contains(context.command)`\n> instead. From 0.7.0 the gate denies on that error rather than permitting, and a\n> CI tripwire test fails the build if the pattern is reintroduced into a shipped\n> policy. See [GHSA-hm46-7j72-rpv9](https://github.com/ScopeBlind/scopeblind-gateway/security/advisories/GHSA-hm46-7j72-rpv9).\n\n### Starter policy packs\n\nMost teams should not write Cedar from scratch on day one. Install a starter\npack, run in shadow mode, inspect receipts, then tighten or enforce:\n\n```bash\nnpx protect-mcp policy-packs list\nnpx protect-mcp policy-packs show secrets-safe\nnpx protect-mcp policy-packs install filesystem-safe --dir ./cedar\nnpx protect-mcp policy-packs install all --dir ./cedar\nnpx protect-mcp serve --cedar ./cedar\n```\n\nBuilt-in packs:\n\n- `filesystem-safe`: destructive file actions and secret-like path reads.\n- `git-safe`: force pushes, hard resets, destructive cleanup, repo deletion.\n- `email-safe`: allow drafting, block unattended sends.\n- `database-safe`: read-oriented DB posture, block write/admin SQL.\n- `cloud-spend-safe`: obvious cloud spend creation and infrastructure destruction.\n- `secrets-safe`: common file, env, shell, and cloud secret exfiltration.\n- `finance-mandate-safe`: restricted-list and concentration breaches in booking flows.\n\n## Verify a receipt\n\nReceipts are signed and verifiable offline by anyone with the public key. No\nnetwork, no vendor, no trust in ScopeBlind:\n\n```bash\nnpx @veritasacta/verify ./receipts/receipts.jsonl --format jsonl\n# Exit 0 = valid, non-zero = tampered or malformed\n```\n\n`npx protect-mcp bundle --output audit.json` exports a self-contained,\noffline-verifiable audit bundle of your receipts plus the public signing key.\n\n## Security\n\n`protect-mcp` 0.7.0 fails closed by design. On any policy-evaluation error, a\nmissing engine, or a policy that errored at evaluation, the decision is DENY,\nnot allow. `serve --enforce` and `doctor` run a boot self-test that proves the\ngate denies a known-forbidden vector before it is trusted, and refuse to arm if\nit cannot.\n\n**Affected versions: 0.5.x and 0.6.x.** Those lines fail open (they return ALLOW\non evaluation error) and do not evaluate Cedar correctly against the pinned\nengine, so a `forbid` rule could fail to block. **Upgrade to >= 0.7.0.**\n\nDetails and remediation: [GHSA-hm46-7j72-rpv9](https://github.com/ScopeBlind/scopeblind-gateway/security/advisories/GHSA-hm46-7j72-rpv9).\nTo report a vulnerability, see [SECURITY.md](./SECURITY.md).\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `serve` | Start the HTTP hook server for Claude Code (port 9377). `--enforce` runs the restraint self-test first; `--cedar <dir>` and `--policy <path>` select the policy. |\n| `init` | Generate an Ed25519 keypair (`keys/gateway.json`), a config template, and a sample policy. |\n| `sample` | Seed a clearly-labeled sample record (8 decisions: one blocked call, two payments; kid `sample-demo`) plus a tampered copy, so `record`, `claim`, `verify-claim`, and `anchor-record` are replayable from scratch before wiring an agent. Refuses to touch an existing record; `--force` overrides. |\n| `policy` | See and change the Cedar policy from the terminal: `policy list` (permit / forbid / default-deny per tool, with how often the gate allowed or denied it), `policy show`, `policy allow <tool>`, `policy deny <tool>`, `policy path`. A running `serve` hot-reloads on the change. |\n| `wrap` | Print a protected MCP command or patch Claude Desktop MCP servers. Dry-run by default; use `--write` to update Claude Desktop config. |\n| `dashboard` | Start a local-only dashboard on `127.0.0.1` showing tool inventory, risk, policy coverage, exact-action approvals, receipt chains, and audit export. |\n| `recommend` | Draft a reviewable JSON policy from observed local calls. Dry-run by default; use `--write` to create `protect-mcp.recommended.json`. |\n| `registry` | Create an org identity, anchor receipt digests, and write a static verifier page. Hosted mode uploads digests only. |\n| `record` | Open a local, searchable viewer over your receipts (`--live` streams as the agent runs): Ed25519 signatures verified in your browser against your gateway key, capability tags, a provenance tree, and one-click signed export. All local, nothing uploaded. |\n| `claim` | Mint a signed, position-blind attestation of a predicate over the record (`--no <cap>` incl. `--no payment`, `--only <c1,c2>`, `--no-verdict <verdict>`, `--count <verdict>`, `--payment-under <cap>`), disclosing only decision categories. Add `--anchor` to record the claim digest in the public transparency log; enrolled keys anchor as a named org. |\n| `anchor-record` | Checkpoint the record's Merkle root + count + time range into the public log (heartbeat-friendly: skips when unchanged). A later claim whose commitment matches an anchored checkpoint is provably over the complete record as of that checkpoint. |\n| `verify-claim` | Verify a claim pack offline: signature, recomputed Merkle root, independently recomputed predicate, and the anchor sidecar when present (binds the anchored envelope to this exact claim, then confirms the public log holds it). `--check-anchor` requires the anchor; `--offline` skips the log hop. |\n| `killer-demo` | Generate a complete shadow-mode to policy to approval to signed-receipt demo pack. |\n| `verify-disclosure` | Verify a `scopeblind.selective_disclosure.v0` package and explain disclosed versus hidden fields. |\n| `policy-packs` | List, inspect, and install starter Cedar policy packs. |\n| `evaluate` | Evaluate one tool call against a Cedar policy (PreToolUse gate). Exit 2 = deny (fail-closed), exit 0 = allow. |\n| `sign` | Sign one tool call into a receipt (PostToolUse). Best-effort: records an honest unsigned line if no key. |\n| `simulate` | Dry-run a policy against a recorded decision log to see what it would have blocked. |\n| `demo` | Start a built-in demo server wrapped with the gate, to see receipts instantly. |\n| `doctor` | Check your setup (keys, policies, Cedar engine, verifier) and run the restraint self-test. |\n| `bundle` | Export an offline-verifiable audit bundle of receipts plus the public key. |\n| `report` | Generate a compliance report (Markdown or JSON) from the decision log and receipts. |\n\nRun `npx protect-mcp --help` for the full flag reference.\n\n## Links\n\n- Protocol (IETF): [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)\n- [CHANGELOG](./CHANGELOG.md)\n- [npm](https://www.npmjs.com/package/protect-mcp)\n- [scopeblind.com](https://scopeblind.com)\n\nMIT licensed. Built by [ScopeBlind](https://scopeblind.com).\n",
  "bytes": 21180,
  "sha": "ebf4cc8c3a2c1f9a7052147b9c6d2286920d4290db6578e4c9a92a175652b039",
  "repo_slug": "scopeblind/scopeblind-gateway",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_tomjwxf_protect_mcp_d3773886/readme"
}