// the problem
Agents loop. The model emits a tool_use block, your handler runs it, and the loop continues from what comes back. On a network blip, a non-200, or bad JSON, the loop can re-call the exact same tool before it ever saw the first success. If that tool is a write — chargeCard, createOrder, sendEmail — the side effect fires again. Two charges. The agent doesn't even know it did it.
The fix
Make write tools idempotent: calling them N times has the same effect as calling them once (RFC 9110). The standard mechanism (Stripe's): the client sends a unique Idempotency-Key. The server stores the first response under that key; any retry with the same key returns the stored result instead of doing the work again. The retry becomes a no-op.
Which tools need it
Read tools (search, get, list) are safe to retry forever — skip the key. Idempotency is a concern only for write and destructive tools. MCP encodes this directly in ToolAnnotations: readOnlyHint, destructiveHint, idempotentHint — the last meaning "safe to call again with the same arguments."
Sources: Stripe — Idempotent requests · MCP — Tool annotations · MCP — Schema (ToolAnnotations) · Anthropic — Tool use overview · MDN — Idempotency-Key
One concept a week. Free.
The deeper, copy-paste version of each ToolCall short — in your inbox.
// total: 0.00 · spam: void · unsubscribe: one click
