Protect MCP Setup
Seth HobsonCommunityCreated by Tom FarleyDistributed by Seth Hobson
- Skill
- Tool integration
- Security
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
protect-mcp provides a framework for securing AI agent tool usage by integrating Cedar policy enforcement and cryptographic receipt generation. It ensures that every tool call made by Claude Code is evaluated against defined security policies before execution and recorded as a tamper-evident, Ed25519-signed receipt. This setup enables offline verification of agent actions, providing a robust audit trail suitable for compliance-sensitive environments.
What it does
- Enforces Cedar-based authorization policies on Claude Code tool calls.
- Blocks unauthorized tool executions based on pre-defined security rules.
- Generates Ed25519-signed receipts for every tool invocation.
- Provides an offline verification mechanism for audit logs.
- Maintains a tamper-evident record of agent activity in JSONL format.
- Supports project-specific policy definitions for fine-grained control.
When to use it
- When operating in regulated industries like finance or healthcare.
- When you need to provide verifiable evidence of AI agent actions.
- When you want to prevent unauthorized or risky tool usage by an AI agent.
- When you require a tamper-evident audit trail for agent-driven workflows.
Advantages
- Cryptographically verifiable proof of agent activity.
- Authoritative policy enforcement via the Cedar engine.
- Offline verification capability without external dependencies.
- Protects against mutable or unsigned logs.
- Standardized receipt format based on IETF drafts.
Usage examples
- “How do I set up cryptographic auditing for my Claude Code project?”
- The agent guides you through installing the plugin, initializing the signing key, and creating a protect.cedar policy file to govern tool access.
- “How can I verify that my agent's actions haven't been tampered with?”
- The agent explains how to use the verification CLI to check the receipts.jsonl file against your public key, ensuring the integrity of the recorded tool calls.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/wshobson/protect-mcp-setup/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip protect-mcp-setup.zip -d .agents/skills
Documentation by Tom Farley
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 [email protected] 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 "*/../*" || 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/[email protected] --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 20Receipt 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
signatureJCS canonicalization (RFC 8785) before signing
No public key in the receipt, so pass it with
--keyNo 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
Protocol: veritasacta.com
Integrations: Microsoft Agent Governance Toolkit (PR #667), AWS cedar-policy/cedar-for-agents (PR #64)
Files
- ATTRIBUTION.mdAdded by AutoGineer972 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md8.3 KiB
- references/receipt-format.md1.6 KiB
SKILL.md
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 [email protected] 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 "*/../*" || 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/[email protected] --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 20Receipt 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
signatureJCS canonicalization (RFC 8785) before signing
No public key in the receipt, so pass it with
--keyNo 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
Protocol: veritasacta.com
Integrations: Microsoft Agent Governance Toolkit (PR #667), AWS cedar-policy/cedar-for-agents (PR #64)
Requirements and compatibility
Written for: Claude Code
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Names Claude Code tools, Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Tom Farley
- Publisher
- Seth Hobson (Community)
- Distributed by
- Seth Hobson
- Source
- wshobson/agents · plugins/protect-mcp/skills/protect-mcp-setup · commit 4bde316
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Tom Farley or Seth Hobson.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download protect-mcp-setup.zip