Pin and Verify MCP Tool Description Hashes
The industry-standard rug-pull mitigation as of late 2026 is hashing the full tool manifest — name, description, and input schema — at approval time and re-verifying on every reconnect, because MCP has no built-in manifest signing. The tool-list-drift hook above only notices a brand-new tool name; it would miss a server that keeps every name identical and just appends a hidden instruction to an existing tool's description. This hashes the whole manifest, not just the name, against a local snapshot you export from your MCP client (e.g. `claude mcp list --json`) into .claude/mcp_tool_snapshot.json.
1. Save this as the hook script
#!/usr/bin/env python3
"""SessionStart hook — hashes each configured MCP server's full tool
manifest (name + description + inputSchema, not just the tool name) and
compares against a baseline. Requires a local snapshot of each server's
current tool manifest at .claude/mcp_tool_snapshot.json (export it from
your MCP client's own tool listing before relying on this — this hook
only compares what's in that file, it doesn't fetch the live manifest
itself)."""
import json, sys, hashlib
from pathlib import Path
cwd = Path(json.load(sys.stdin).get("cwd", "."))
snapshot_path = cwd / ".claude" / "mcp_tool_snapshot.json"
baseline_path = cwd / ".claude" / "mcp_tool_hashes.json"
if not snapshot_path.exists():
sys.exit(0) # nothing to check yet — snapshot not wired up
snapshot = json.loads(snapshot_path.read_text())
baseline = json.loads(baseline_path.read_text()) if baseline_path.exists() else {}
changed = []
for tool_name, schema in snapshot.items():
digest = hashlib.sha256(json.dumps(schema, sort_keys=True).encode()).hexdigest()
if tool_name in baseline and baseline[tool_name] != digest:
changed.append(tool_name)
baseline[tool_name] = digest
baseline_path.parent.mkdir(parents=True, exist_ok=True)
baseline_path.write_text(json.dumps(baseline, indent=2))
if changed:
print(f"WARNING: {len(changed)} MCP tool description/schema changed "
f"since last session, same name but different content — "
f"re-review before trusting them: {', '.join(changed)}", file=sys.stderr)
sys.exit(1)
sys.exit(0)
Save to a path inside your project, e.g. .claude/hooks/pin-mcp-tool-description-hashes.sh, then chmod +x it.
2. Wire it into .claude/settings.json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{"type": "command", "command": "python3 .claude/hooks/pin-mcp-tool-description-hashes.py"}
]
}
]
}
}
Before you use this
A hook runs with your local shell's permissions — review the script above before adding it, the same way you'd review any script you didn't write yourself. See our guide on hook security risks for the specific CVEs this category of feature has already produced.
Related hooks