Protect MCP
Skill 130 of 185
Configure Cedar policy enforcement and Ed25519 signed receipts for Claude Code tool calls.
3 minutes · 553 words · 12 sections
Install
npx skills add wshobson/agents --skill protect-mcp-setupnpx skills add wshobson/agents/plugin marketplace add wshobson/agentsThe first command installs just this skill, by the name in its SKILL.md; the second installs the whole repository.
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.
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:
npx @veritasacta/verify. No server, no account,
no trust in the operator.AI agents make decisions that affect money, safety, and rights. The Claude Code session log records what happened, but the log is:
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.
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.32.4 init --dir "$d" && mv "$d/keys/gateway.json" ./protect-mcp.key
fi
chmod 600 ./protect-mcp.key
echo "/protect-mcp.key" >> .gitignore
# 4. Use Claude Code normally. Every tool call is now policy-evaluated
# and produces a signed receipt in ./receipts/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.
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.
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.
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 readThe 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 20Each receipt is one line of ./receipts/receipts.jsonl. See
references/receipt-format.md (opens in a new tab) for a sample.
signature--key| 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 |
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.
The verbatim description from this skill’s front matter — the string an agent matches on to decide whether to load it.
main, last pushed 9 November 2026.SKILL.md, not by matching a directory convention. 52 distinct layouts observed: plugins/accessibility-compliance/skills/*/SKILL.md, plugins/agent-teams/skills/*/SKILL.md, plugins/api-scaffolding/skills/*/SKILL.md, plugins/avoid-ai-writing/skills/*/SKILL.md, plugins/backend-development/skills/*/SKILL.md, plugins/before-you-build/skills/*/SKILL.md, plugins/block-no-verify/skills/*/SKILL.md, plugins/blockchain-web3/skills/*/SKILL.md, plugins/brand-landingpage/skills/*/SKILL.md, plugins/business-analytics/skills/*/SKILL.md, plugins/cicd-automation/skills/*/SKILL.md, plugins/cloud-infrastructure/skills/*/SKILL.md, plugins/conductor/skills/*/SKILL.md, plugins/content-marketing/skills/*/SKILL.md, plugins/data-engineering/skills/*/SKILL.md, plugins/database-design/skills/*/SKILL.md, plugins/developer-essentials/skills/*/SKILL.md, plugins/dgx-spark-ops/skills/*/SKILL.md, plugins/documentation-generation/skills/*/SKILL.md, plugins/documentation-standards/skills/*/SKILL.md, plugins/dotnet-contribution/skills/*/SKILL.md, plugins/file-conversion/skills/*/SKILL.md, plugins/framework-migration/skills/*/SKILL.md, plugins/frontend-mobile-development/skills/*/SKILL.md, plugins/game-development/skills/*/SKILL.md, plugins/hermes-tweet/skills/*/SKILL.md, plugins/hr-legal-compliance/skills/*/SKILL.md, plugins/incident-response/skills/*/SKILL.md, plugins/javascript-typescript/skills/*/SKILL.md, plugins/kubernetes-operations/skills/*/SKILL.md, plugins/llm-application-dev/skills/*/SKILL.md, plugins/llm-finetuning/skills/*/SKILL.md, plugins/machine-learning-ops/skills/*/SKILL.md, plugins/observability-monitoring/skills/*/SKILL.md, plugins/payment-processing/skills/*/SKILL.md, plugins/plugin-eval/skills/*/SKILL.md, plugins/pptx-deck-creation/skills/*/SKILL.md, plugins/protect-mcp/skills/*/SKILL.md, plugins/python-development/skills/*/SKILL.md, plugins/quantitative-trading/skills/*/SKILL.md, plugins/reverse-engineering/skills/*/SKILL.md, plugins/review-agent-governance/skills/*/SKILL.md, plugins/security-scanning/skills/*/SKILL.md, plugins/shell-scripting/skills/*/SKILL.md, plugins/ship-mate/skills/*/SKILL.md, plugins/signed-audit-trails/skills/*/SKILL.md, plugins/skill-forge-essentials/skills/*/SKILL.md, plugins/social-publishing/skills/*/SKILL.md, plugins/startup-business-analyst/skills/*/SKILL.md, plugins/superself/skills/*/SKILL.md, plugins/systems-programming/skills/*/SKILL.md, plugins/ui-design/skills/*/SKILL.md.h1 and no skipped levels:.claude-plugin/marketplace.json by Seth Hobson, declaring 94 plugins. It is read for editorial metadata only — never as the skill index, which is always the repository tree./wshobson/agents.md, and each skill at its own .md URL.1 file · 2 KB
Everything this skill ships beside its prose. All of it is set here, as a subchapter of skill 130.
Documentation the agent loads on demand, rather than up front.