protect-mcp-setup
wshobson/agents
Cryptographic policy enforcement and Ed25519-signed audit receipts for Claude Code tool calls.
What is protect-mcp-setup?
Adds Cedar policy evaluation and tamper-evident signed receipts to every Claude Code tool invocation. Use this when you need compliance-ready audit trails, policy-gated execution, or offline-verifiable proof of agent actions for regulated environments.
- Evaluate tool calls against Cedar authorization policies before execution
- Generate Ed25519-signed receipts for every tool invocation
- Block tool execution if Cedar policy denies the call
- Verify receipts offline without server or operator trust
- Record tool names in tamper-evident JSONL format with JCS canonicalization
How to install protect-mcp-setup
npx skills add https://github.com/wshobson/agents --skill protect-mcp-setup- Node.js and npx installed
- Claude Code editor with plugin support
- Ed25519 signing key (generated once via protect-mcp init)
- Cedar policy file (protect.cedar) at project root
How to use protect-mcp-setup
- 1.Run `claude plugin install wshobson/agents/protect-mcp` to add hooks to your project
- 2.Create a `./protect.cedar` file with your authorization rules (read-only tools, safe bash commands, write restrictions)
- 3.Generate the signing key once: `if [ ! -e ./protect-mcp.key ]; then d=$(mktemp -d) && npx protect-mcp@0.7.4 init --dir "$d" && mv "$d/keys/gateway.json" ./protect-mcp.key; fi`
- 4.Add `/protect-mcp.key` to `.gitignore`
- 5.Use Claude Code normally; every tool call is now evaluated against your policy and produces a signed receipt in `./receipts/receipts.jsonl`
- 6.Verify receipts offline: `PUB=$(node -p 'JSON.parse(require("fs").readFileSync("./protect-mcp.key")).publicKey') && npx @veritasacta/verify@0.9.2 --replay-chain ./receipts/receipts.jsonl --key "$PUB"`
Use cases
- Finance or healthcare projects requiring cryptographic audit trails of AI agent decisions
- Regulated research where third parties must verify agent actions without trusting the operator
- Compliance contexts needing tamper-evident evidence of what tools ran and when
- Projects restricting agent tool use to safe subsets (read-only, git status, npm scripts)
- Demonstrating policy-enforced governance in audits or regulatory reviews
- Compliance officers and audit teams
- Regulated industry engineers (finance, healthcare, research)
- Teams building AI agents for high-stakes decisions
- Security-conscious developers needing offline verification
- Organizations subject to third-party audits
protect-mcp-setup FAQ
The PreToolUse hook exits with code 2 and Claude Code blocks the tool call entirely. The tool never executes.
No. Ed25519 signatures lock the record at signing time. Any tampering causes verification to fail. Deleted lines go undetected unless you use a receipt chain with links to previous receipts.
No. Verification is entirely offline using `npx @veritasacta/verify` and the public key from your `protect-mcp.key` file. No trust in the operator required.
Version 0.7.4 records the tool name and Ed25519 signature. It does not record tool input or output; you can extend the policy or hooks to capture more context if needed.
Yes. Install the plugin, create a `protect.cedar` policy file, generate the signing key, and add it to `.gitignore`. Existing projects will start recording signed receipts immediately.
Full instructions (SKILL.md)
Source of truth, from wshobson/agents.
name: protect-mcp-setup description: Configure Cedar policy enforcement and Ed25519 signed receipts for Claude Code tool calls. Use when setting up projects that need cryptographic audit trails, policy-gated tool execution, or compliance-ready evidence of agent actions.
protect-mcp — Policy Enforcement + Signed Receipts
Cryptographic governance for every Claude Code tool call. Each invocation is evaluated against a Cedar policy and produces an Ed25519-signed receipt that anyone can verify offline.
Overview
Claude Code runs powerful tools: Bash, Edit, Write, WebFetch. By default
there is no audit trail, no policy enforcement, and no way to prove what was
decided after the fact. protect-mcp closes all three gaps:
- Cedar policies (AWS's open authorization engine) evaluate every tool call before execution. Cedar deny is authoritative.
- Ed25519 receipts record the name of each tool that ran, signed with your key.
- Offline verification via
npx @veritasacta/verify. No server, no account, no trust in the operator.
Problem
AI agents make decisions that affect money, safety, and rights. The Claude Code session log records what happened, but the log is:
- Mutable — anyone with access can edit it
- Unsigned — there is no way to prove integrity
- Operator-bound — verification requires trusting whoever holds the log
For compliance contexts (finance, healthcare, regulated research), this is not sufficient. You need tamper-evident evidence that can be verified by third parties without trusting you.
Solution
Add protect-mcp to your Claude Code project:
# 1. Install the plugin (adds hooks + skill to your project)
claude plugin install wshobson/agents/protect-mcp
# 2. Create ./protect.cedar (see below). The plugin installs the hooks.
# 3. Create the signing key once (protect-mcp 0.7.4 sign does not create it).
# An existing key is never replaced. See references/receipt-format.md to rotate.
if [ ! -e ./protect-mcp.key ]; then
d=$(mktemp -d) && npx protect-mcp@0.7.4 init --dir "$d" && mv "$d/keys/gateway.json" ./protect-mcp.key
fi
echo "/protect-mcp.key" >> .gitignore
# 4. Use Claude Code normally. Every tool call is now policy-evaluated
# and produces a signed receipt in ./receipts/
Hook Configuration
Installing the plugin adds both hooks from hooks/hooks.json. Each hook runs a
script bundled with the plugin:
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{ "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/evaluate.sh" }
]
}
],
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{ "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/sign.sh" }
]
}
]
}
}
Claude Code passes the hook event to the command as JSON on stdin and does not
set TOOL_NAME or TOOL_INPUT variables. evaluate.sh reads tool_name and
tool_input from that payload and passes them to protect-mcp as flags; sign.sh reads
tool_name only, because the 0.7.4 signer records nothing else. Set
PROTECT_MCP_POLICY, PROTECT_MCP_RECEIPTS, and PROTECT_MCP_KEY to change the
default paths. When the policy file is missing, the PreToolUse hook prints a
warning to stderr and allows the call.
What each hook does
PreToolUse — Runs BEFORE the tool executes. Evaluates the tool call against
your Cedar policy file. If Cedar returns deny, the hook exits with code 2 and
Claude Code blocks the tool call entirely.
PostToolUse runs AFTER the tool completes. It signs a receipt that names
the tool and appends it to ./receipts/receipts.jsonl. protect-mcp 0.7.4 does
not record the tool input or output.
Cedar Policy File
Create ./protect.cedar at the project root:
// Read-only tools: one rule can name several tools in `when`. Add WebFetch
// with your own URL rule.
permit (principal, action == Action::"MCP::Tool::call", resource) when {
resource == Tool::"Read" || resource == Tool::"Glob" || resource == Tool::"Grep"
};
// Safe commands only; git limited to read subcommands
permit (principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
context has input && context.input has command &&
(context.input.command like "git status*" || context.input.command like "git diff*" ||
context.input.command like "git log*" || context.input.command like "git show*" ||
context.input.command like "npm*" || context.input.command like "ls*" ||
context.input.command like "cat*" || context.input.command like "echo*" ||
context.input.command like "pwd*" || context.input.command like "test*")
};
// No chaining (`&` also denies `2>&1`), `$` expansion, redirection (`>` or
// `<`, which covers `<(`), file output (`git diff --output`), or rm -rf
forbid (principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
context has input && context.input has command &&
(context.input.command like "*;*" || context.input.command like "*&*" ||
context.input.command like "*|*" || context.input.command like "*$*" ||
context.input.command like "*`*" || context.input.command like "*>*" ||
context.input.command like "*<*" || context.input.command like "*\n*" ||
context.input.command like "*--output*" || context.input.command like "*rm -rf*")
};
// Writes only inside the project (paths are absolute), never via `..`
permit (principal, action == Action::"MCP::Tool::call", resource) when {
(resource == Tool::"Write" || resource == Tool::"Edit") &&
context has input && context.input has file_path &&
context.input.file_path like "/path/to/project/*"
};
forbid (principal, action == Action::"MCP::Tool::call", resource) when {
(resource == Tool::"Write" || resource == Tool::"Edit") &&
context has input && context.input has file_path &&
(context.input.file_path like "*/../*" || context.input.file_path like "*/..")
};
String matching is best-effort: like checks the raw string, not a
resolved path, and an npm* permit runs arbitrary code, so it is only as
safe as the project's scripts.
Verification
Verify every receipt against the public key in ./protect-mcp.key:
PUB=$(node -p 'JSON.parse(require("fs").readFileSync("./protect-mcp.key")).publicKey')
npx @veritasacta/verify@0.9.2 --replay-chain ./receipts/receipts.jsonl --key "$PUB"
# Exit 0 = every receipt verified
# Exit 1 = a receipt failed (tampered, wrong key, or malformed line)
# Exit 2 = the file could not be read
The plugin's slash commands do the same inside Claude Code. /verify-receipt
takes one receipt in its own file, e.g., from
tail -n 1 ./receipts/receipts.jsonl > receipt.json.
/verify-receipt receipt.json
/audit-chain --last 20
Receipt Format
Each receipt is one line of ./receipts/receipts.jsonl. See
references/receipt-format.md for a sample.
- Ed25519 signatures (RFC 8032) over all fields but
signature - JCS canonicalization (RFC 8785) before signing
- No public key in the receipt, so pass it with
--key - No link to the previous receipt, so a deleted line goes undetected
Why This Matters
| Before | After |
|---|---|
| "Trust me, the agent only read files" | Cryptographically provable: every Read logged and signed |
| "The log shows it happened" | The receipt proves it happened, and no one can edit it |
| "You'd have to audit our system" | Anyone can verify every receipt offline |
| "Logs might be different by now" | Ed25519 signatures lock the record at signing time |
Standards
- Ed25519 — RFC 8032 (digital signatures)
- JCS — RFC 8785 (deterministic JSON canonicalization)
- Cedar — AWS's open authorization policy language
- IETF draft — draft-farley-acta-signed-receipts
Related
- npm: protect-mcp
- Verify CLI: @veritasacta/verify
- Source: github.com/ScopeBlind/scopeblind-gateway
- Protocol: veritasacta.com
- Integrations: Microsoft Agent Governance Toolkit (PR #667), AWS cedar-policy/cedar-for-agents (PR #64)
Related skills
More from wshobson/agents and the wider catalog.

protocol-reverse-engineering
Capture, analyze, and document network protocols for security research and debugging.

python-anti-patterns
Checklist of common Python anti-patterns to catch before code review and deployment.

python-background-jobs
Async task queues and background job patterns for decoupling long-running work from request/response cycles.

python-code-style
Modern Python linting, formatting, type checking, and documentation standards with ruff, mypy, and Google-style docstrings.

python-configuration
Externalize Python config via environment variables and typed Pydantic settings with validation.

python-design-patterns
Build maintainable Python code with KISS, Single Responsibility, and composition patterns.