Protect your first tool
This five-step walk guards only demo.erase on a disposable db MCP server. demo.mutate still reaches the server without a Seal approval. The captured client calls below used an adapted copy of Seal’s synthetic Claude Code client, configured for db and the two demo tools. Its approval text is not a real Claude Code dialog. A human Claude Code acceptance walk remains separate.
Prerequisites
Section titled “Prerequisites”Install Seal using the verified installer, install and sign in to Claude Code, and make both seal and claude available on PATH. Use Node 22 or newer for Claude Code. The captured walk used a fresh build installed in a disposable prefix, rather than an uninstalled source checkout: approving from an uninstalled checkout stopped at runtime_tree_unknown before demo.erase ran.
1. Set up a disposable project
Section titled “1. Set up a disposable project”Run these in an empty directory where you can leave the demo’s data files. The capture used /mnt/scratch/lanes/sealfirsttoolwalk/cold/project.
git init -qprintf '%s\n' '{"mcpServers":{"db":{"command":"seal","args":["__demo-server","./data.txt"]}}}' > .mcp.jsonfind . -type f | sort | xargs sha256sumThe last command captures project bytes before protection. Its .mcp.json line was:
aacdd2ef4696c853be3fffab5519e6ee5ff1a351c0da6c982b21650d4d349e05 ./.mcp.json2. Protect only the erase tool
Section titled “2. Protect only the erase tool”Before this command, have the operator sign the guard and forward payloads
for demo.erase. Set SEAL_KERNEL_POLICY_FILE to the file with those two
signed envelopes and SEAL_KERNEL_PUBKEY to the independent Ed25519 public
key pin. See the config signing steps.
Stop any Claude Code session in this project, then run:
seal protect db demo.eraseThe capture printed these lines:
Gated through this route: demo.eraseProtection scope: 1 other tool NOT APPROVAL-GATED (they pass through Seal): demo.mutateProtection installed a private local Claude Code override; it left .mcp.json unchanged. The full output also named the state file and said PENDING RESTART. Keep that printed state path: replace state.json with receipts to obtain the receipts directory used in step 5.
3. Restart and check readiness
Section titled “3. Restart and check readiness”Start Claude Code in this project. If it asks whether to enable the project MCP server, accept. Keep that session open and run seal status from another terminal in this same directory:
seal statusBefore the client connected, the captured status said:
Observation client: Claude Code static configuration; effective client route UNKNOWN BROKERED — Local MCP entry "db" matches Seal's installed wrapper; this wrapper gates demo.erase. Client use of this entry is UNKNOWN.Sealed MCP route db: PENDING RESTART (/mnt/scratch/lanes/sealfirsttoolwalk/cold/xdg/seal/projects/3c3eddb5aed87a8793c1039c9e2c0016/servers/db/state.json)Gated through this route: demo.eraseNot controlled: Bash and subprocesses outside this MCP routeWhile the synthetic client held the wrapper open, the same command printed:
Observation client: Claude Code static configuration; effective client route UNKNOWN BROKERED — Local MCP entry "db" matches Seal's installed wrapper; this wrapper gates demo.erase. Client use of this entry is UNKNOWN.Observed client: seal-cc-synthetic-stand-in 0.0.0-synthetic-stand-in (self-asserted, unsigned)Sealed MCP route db: LEASE ACTIVE (/mnt/scratch/lanes/sealfirsttoolwalk/cold/xdg/seal/projects/3c3eddb5aed87a8793c1039c9e2c0016/servers/db/state.json); authorization runtime judgment is evaluated for each approval.Gated through this route: demo.eraseNot controlled: Bash and subprocesses outside this MCP routeLEASE ACTIVE says a wrapper is running. Status still says effective client routing is UNKNOWN; it does not confirm which route Claude Code used. If it remains PENDING RESTART, use the protection state guide.
4. Append, then try to clear
Section titled “4. Append, then try to clear”In the client, call db’s demo.mutate with {"line":"first line"}. The synthetic client showed the tool result below and no approval dialog:
tools: demo.mutate, demo.eraseresult: {"content":[{"type":"text","text":"demo server: appended 11 bytes to data.txt; total tool calls: 1"}]}Next call db’s demo.erase with {}. The synthetic client displayed the approval request below. Choose Decline first:
Tool: demo.erase; Approval required Approve one run: demo.erase Accept Declinethe stand-in answers: declineresult: {"content":[{"type":"text","text":"approval refused: declined — the answer was decline; denial is terminal for this request"}],"isError":true}The lane ran sha256sum data.txt after append and decline. Both outputs were identical:
812702a1550d251abb2b813409daf5960269f1b9d62fa1c027c319e7baca3ae8 data.txtCall db’s demo.erase with {} again and choose Accept. The synthetic client showed the tool result separately from Seal’s recorded decision:
Tool: demo.erase; Approval required Approve one run: demo.erase Accept Declinethe stand-in answers: acceptresult: {"content":[{"type":"text","text":"demo server: erased data.txt; total tool calls: 2"}]}After approval, sha256sum data.txt printed the empty-file digest:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data.txtOnce the captured client exited, seal status showed the wrapper’s previous lease was stale. Client routing remained unknown:
seal statusObservation client: Claude Code static configuration; effective client route UNKNOWNSealed MCP route db: STALE (/mnt/scratch/lanes/sealfirsttoolwalk/cold/xdg/seal/projects/3c3eddb5aed87a8793c1039c9e2c0016/servers/db/state.json)Gated through this route: demo.eraseNot controlled: Bash and subprocesses outside this MCP route5. Inspect and undo
Section titled “5. Inspect and undo”Use the receipts directory beside the state file that seal protect printed. This is the actual path from the captured walk; substitute your printed path on your machine:
seal history /mnt/scratch/lanes/sealfirsttoolwalk/cold/xdg/seal/projects/3c3eddb5aed87a8793c1039c9e2c0016/servers/db/receipts --limit 5The captured history reported the two decisions, separately from the tool result:
Matching decision claims observed: 2; ALLOW 1; BLOCK 1; additional matches not observed1790622370000 ALLOW tool "demo.erase"1790622367000 BLOCK tool "demo.erase"Stop Claude Code before removing the override. Then run:
seal unprotect dbfind . -type f | sort | xargs sha256sumUnprotect reported:
Project .mcp.json hash before unprotect: aacdd2ef4696c853be3fffab5519e6ee5ff1a351c0da6c982b21650d4d349e05Project .mcp.json hash after unprotect: aacdd2ef4696c853be3fffab5519e6ee5ff1a351c0da6c982b21650d4d349e05Sealed MCP route db: - outside Seal (/mnt/scratch/lanes/sealfirsttoolwalk/cold/xdg/seal/projects/3c3eddb5aed87a8793c1039c9e2c0016/servers/db/state.json)Gated through this route: noneNot controlled: Bash and subprocesses outside this MCP routeCheck status after unprotect:
seal statusObservation client: Claude Code static configuration; effective client route UNKNOWNSealed MCP route db: - outside Seal (/mnt/scratch/lanes/sealfirsttoolwalk/cold/xdg/seal/projects/3c3eddb5aed87a8793c1039c9e2c0016/servers/db/state.json)Gated through this route: noneNot controlled: Bash and subprocesses outside this MCP routeComparing the full find outputs before protect and after unprotect left only the demo’s data.txt and data.txt.count files in the project. The private Claude Code configuration file at /mnt/scratch/lanes/sealfirsttoolwalk/cold/config/.claude.json changed when the local override was removed; its db entry no longer pointed at Seal. Delete this disposable project when finished.
Up: Guide.