mcp-proxy
MCP proxy with action receipts, policy engine, and intent tracking

Audit, govern, and sign every AI agent action.
SDK • Spec • agentreceipts.ai
What it does
mcp-proxy sits between an MCP client (Claude, etc.) and an MCP server, transparently intercepting every tool call. For each call it:
- Classifies the operation (read/write/delete/execute) and scores risk (0-100)
- Evaluates policy rules (pass/flag/pause/block) with approval workflows
- Groups related calls by temporal proximity (intent tracking)
- Signs a cryptographic receipt (Ed25519, hash-chained, W3C Verifiable Credential)
- Redacts sensitive data (JSON-aware + pattern-based) before storage
- Stores everything in a local SQLite audit trail
Single binary. No external dependencies. Drop-in for any MCP server.
Install
Homebrew (macOS, Linux)
brew install agent-receipts/tap/mcp-proxy
Prebuilt binaries
Download from the releases page (darwin and linux, amd64 and arm64).
From source
go install github.com/agent-receipts/ar/mcp-proxy/cmd/mcp-proxy@latest
Usage
As MCP proxy
# Wrap any MCP server
mcp-proxy node /path/to/mcp-server.js
# With options
mcp-proxy \
--name github \
--key private.pem \
--taxonomy taxonomy.json \
--rules rules.yaml \
--issuer did:agent:my-proxy \
--principal did:user:alice \
node /path/to/github-mcp-server.js
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"github-audited": {
"command": "mcp-proxy",
"args": [
"--name", "github",
"node", "/path/to/github-mcp-server.js"
]
}
}
}
Check version
mcp-proxy -version
Persistent signing key
By default, mcp-proxy generates an ephemeral key pair on each startup. To use a
persistent key whose receipts can be verified offline, generate one with init:
mcp-proxy init -key ~/.agent-receipts/signing.pem
# writes ~/.agent-receipts/signing.pem (0600 — owner read/write only)
# writes ~/.agent-receipts/signing.pem.pub (0644 — public, shareable)
Pass the key to the proxy:
mcp-proxy --key ~/.agent-receipts/signing.pem node /path/to/mcp-server.js
Enable strict permission enforcement to make loose file permissions a fatal error:
mcp-proxy --key ~/.agent-receipts/signing.pem --strict-permissions node /path/to/mcp-server.js
If you generated a key with another tool (e.g. openssl genpkey), restrict
access manually before use:
chmod 600 private.pem
CLI subcommands
mcp-proxy init -key <path> # Generate a persistent Ed25519 key pair
mcp-proxy list # Latest 50 receipts, newest first
mcp-proxy list --risk high # Filter by risk
mcp-proxy inspect <receipt-id> # Show receipt details
mcp-proxy verify --key pub.pem <chain> # Verify chain integrity
mcp-proxy export <chain-id> # Export chain as JSON
mcp-proxy stats # Show statistics
mcp-proxy timing # Show per-tool timing breakdown
mcp-proxy timing --json # JSON output for dashboards
Policy engine
Define rules in YAML:
rules:
- name: block_destructive_ops
description: Block high-risk delete operations
enabled: true
tool_pattern: "delete_*"
min_risk_score: 70
action: block
- name: pause_high_risk
description: Pause for approval when risk >= 50
enabled: true
min_risk_score: 50
action: pause
Actions: pass (log only), flag (log + highlight), pause (wait for approval), block (reject).
When a tool call is paused, approve or deny via HTTP. The listener is off by default — pass -http 127.0.0.1:PORT to enable it (without that flag, paused calls fail fast with JSON-RPC code -32003). The approval URL and bearer token are logged to stderr at startup. Copy the token from the startup line and export it before running the curls:
export APPROVAL_TOKEN=<token-from-stderr>
curl -X POST http://127.0.0.1:PORT/api/tool-calls/{id}/approve \
-H "Authorization: Bearer $APPROVAL_TOKEN"
curl -X POST http://127.0.0.1:PORT/api/tool-calls/{id}/deny \
-H "Authorization: Bearer $APPROVAL_TOKEN"
Paused calls auto-deny after 60 seconds (fail-safe).
Encryption
Set BEACON_ENCRYPTION_KEY to enable AES-256-GCM encryption at rest for sensitive audit data.
Secret redaction
The proxy redacts secrets before writing to the audit database in two passes:
- JSON-key pass — any value whose JSON key matches a sensitive name (e.g.
password, token, api_key) is replaced with [REDACTED].
- Pattern pass — 12 built-in regular expressions catch common token formats regardless of key name.
Built-in patterns:
| Name |
Matches |
github-pat-classic |
ghp_… GitHub personal access tokens |
github-pat-finegrained |
github_pat_… fine-grained PATs |
github-oauth |
gho_… OAuth tokens |
github-app-installation |
ghs_… GitHub App installation tokens |
github-user-to-server |
ghu_… user-to-server tokens |
github-installation-legacy |
v1.<40+ hex chars> legacy installation tokens |
openai-anthropic-key |
sk-… OpenAI/Anthropic secret keys |
aws-access-key |
AKIA… AWS access key IDs |
bearer-token |
Bearer <token> HTTP Authorization headers |
slack-token |
xoxb-/xoxp-/xoxr-/xoxa-/xoxs- Slack tokens |
pem-private-key |
PEM -----BEGIN … PRIVATE KEY----- blocks |
url-param-token |
?token=…, ?access_token=…, ?key=… etc. (key name preserved) |
Custom patterns
Add organisation-specific patterns with a YAML file:
# custom_redact.yaml
patterns:
- name: slack-webhook
pattern: 'https://hooks\.slack\.com/services/[A-Z0-9/]+'
- name: stripe-live
pattern: 'sk_live_[A-Za-z0-9]{24,}'
Pass it at startup:
mcp-proxy -redact-patterns custom_redact.yaml -- npx -y @modelcontextprotocol/server-filesystem /
An example file is at configs/example_redact_patterns.yaml.
Auditing existing databases
The audit-secrets subcommand scans an existing audit database for values that match any built-in or custom pattern — useful after upgrading the proxy or adding new patterns:
mcp-proxy audit-secrets -db ~/.agent-receipts/audit.db
mcp-proxy audit-secrets -db ~/.agent-receipts/audit.db -redact-patterns custom_redact.yaml
If the database is encrypted, set BEACON_ENCRYPTION_KEY before running.
Exit codes: 0 = no matches found; 1 = one or more matches found; 2 = error.
The scanner runs two passes per row:
- Regex pass — checks the value against all built-in and custom named patterns. Output line:
<table> col=<column> row=<id> pattern=<name>.
- JSON-key pass — parses the value as JSON and reports any value stored under a sensitive key (e.g.
password, token, api_key) that is non-empty and not already [REDACTED]. Catches leaks that do not match any regex pattern. Output line: <table> col=<column> row=<id> json-key=<path>.
If decryption fails for a row (invalid ciphertext), it is reported as <table> col=<column> row=<id> decrypt-error and counts as a hit — operators must investigate.
If hits are reported, the raw token values are already in the database. Because the audit log is append-only, the recommended action is to rotate the secret and consider the old value compromised. You can then drop or redact the affected rows manually if needed.
License
Apache 2.0