Recipe: Claude#
Connect Claude Code or Claude Desktop to the Torii Wallet door and walk the mandate path. English only. Testnet (Base Sepolia, chain 84532).
Verified on 2026-10-12 against API 8e8504b63 (branch feat/torii-wallet): the Claude Code CLI (2.1.296) connected to a locally running API with TW_DOOR_ENABLED=1 over POST /mcp?profile=full and called mandate_offers (it answered offers=0). The full path below was then walked over the same wire by scripts/tw_recipe_claude_probe.py, and tests/torii_wallet/test_recipe_claude.py repeats it on every test run. Not verified: the Claude Desktop GUI and the mcp-remote bridge themselves (the config block is the one in Connect; no Desktop install was driven).
What the bot needs#
- The API host, for example
https://<api-host>. The door must be open there:GET /torii/dooranswers402. A404means the host has not opened it. - An agent key.
POST /auth/agent/connectwith{"agent_name":"my-bot"}returns anapi_key. It goes in the headerX-NW-User-Key. Keep it in an environment variable (TORII_AGENT_KEY); never paste it into a prompt or a repository. - Your agent id:
GET /torii/agents/mewith that key. The owner needs it for the owner step below. - No exchange key and no wallet key. The vault enforces the mandate; you can never withdraw.
Claude Code#
One line:
claude mcp add --transport http torii-wallet "https://<api-host>/mcp?profile=full" --header "X-NW-User-Key: $TORII_AGENT_KEY"
claude mcp list
Or .mcp.json in the project:
{
"mcpServers": {
"torii-wallet": {
"type": "http",
"url": "https://<api-host>/mcp?profile=full",
"headers": {
"X-NW-User-Key": "<your agent key>"
}
}
}
}
The tools then appear as mcp__torii-wallet__mandate_propose and so on. Read-only tools (mandate_list, mandate_get, mandate_offers) are safe to auto-allow; the write tools are idempotent and safe to repeat.
Claude Desktop#
claude_desktop_config.json reaches a remote server through the mcp-remote bridge, which needs Node:
{
"mcpServers": {
"torii-wallet": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://<api-host>/mcp?profile=full",
"--header",
"X-NW-User-Key:${TORII_AGENT_KEY}"
],
"env": {
"TORII_AGENT_KEY": "<your agent key>"
}
}
}
}
Restart Claude Desktop after saving the file.
The first three tool calls#
mandate_proposewithagent_id,titleandgoal(budget_usdc,horizon_days,stop_floor_bps,per_tx_cap_usdc,daily_cap_usdc). It writes a draft for your owner and moves no money. It names no signature and no other owner.mandate_offerswith no arguments. Lists the mandates your owner designated to you once they have signed.mandate_acceptwithmandate_id. Changes nothing else and is idempotent.
After that: mandate_report_event for what you did on chain, mandate_get to read the record back, mandate_report_refusal for a call the vault reverted. Details: Offers and accept, Reporting events.
What the owner does#
The owner opens /tw/start?agent=<your agent id> on their side, sets the limits, reviews the terms and signs. The owner's session and signature are theirs; the bot's key cannot do this step. The owner must have opted the agent in first (accepts_mandates: true, see Offers and accept). The owner can stop the mandate at any time; the bot's next write is then refused with wrong_status.
When a call is refused#
Branch on reason. The catalogue is Refusals; the vault's own reverts are listed in Connect.
Verified transcript#
Compact output of python scripts/tw_recipe_claude_probe.py --claude-cli (fixture owner and agent; in-memory store; fake chain receipt; no secrets):
CLAUDE CLI exit=0 reply='offers=0'
GET /torii/door -> 402, X-Torii-Door: mandate, 7 steps, accepts=[]
MCP initialize -> nightwatch (protocol 2025-06-18)
MCP tools/list -> 73 tools, mandate_*: mandate_accept, mandate_execute_call, mandate_get, mandate_list, mandate_offers, mandate_propose, mandate_report_event, mandate_report_refusal, mandate_stop, mandate_unwind_call
CALL mandate_propose -> draft m_..., owner id 11
OWNER edit + EIP-712 sign -> signed
CALL mandate_offers -> 1 offer
CALL mandate_accept -> accepted
CALL mandate_report_event -> recorded (the agent's word, not yet on chain)
CALL mandate_get -> the reported event is in the record
CALL mandate_report_refusal -> recorded, source=chain, reason=OverPerTxCap
OWNER stop -> stopped
CALL mandate_report_event (after) -> REFUSED reason=wrong_status retry_safe=False
The probe's own MCP client is hand-rolled Streamable HTTP (JSON-RPC 2.0 over POST); the Python mcp package is not installed in the build environment. The real Claude Code client made the first line.