/protect-mcp-setup
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.
Claude Code
$ npx -y skills add wshobson/agents --skill protect-mcp-setup --agent claude-code
Codex
$ npx skills add wshobson/agents --skill protect-mcp-setup -a codex -y
Cursor
$ npx skills add wshobson/agents --skill protect-mcp-setup -a cursor -y
Gemini CLI
$ npx skills add wshobson/agents --skill protect-mcp-setup -a gemini-cli -y
GitHub Copilot
$ npx skills add wshobson/agents --skill protect-mcp-setup -a github-copilot -y
OpenCode
$ npx skills add wshobson/agents --skill protect-mcp-setup -a opencode -y
Windsurf
$ npx skills add wshobson/agents --skill protect-mcp-setup -a windsurf -y
SKILL.md
protect-mcp-setup.SKILL.mdname: 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.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/
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 "*Read more
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.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/
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 "*Use one collection of plugins, agents, skills, and commands across Claude Code, OpenAI Codex CLI, Cursor, OpenCode, Antigravity CLI, GitHub Copilot, and Pi. A harness is the coding tool that loads and runs the components.
Other skills on agents.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about protect-mcp-setup.
- Which coding agents can use protect-mcp-setup?
- protect-mcp-setup installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

