toolcall() ← all concepts

// concept · idempotency

Agents retry. Make that safe.

An agent loop can re-fire the same tool call — and if that call charges a card, the charge happens twice. Idempotency is the one property that turns a retry into a no-op.

// 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 agent loop, on a flaky call chargeCard($49) # succeeded server-side... ↳ network blip → handler errors ↳ agent re-plans, re-calls chargeCard($49) → second charge 💥

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.

# same key on every retry of THIS charge POST /v1/charges Idempotency-Key: a1b2c3d4-uuid-v4 amount=4900 currency=usd customer=cus_123 # retry → server returns the FIRST result, no 2nd charge → many calls, one effect
Keys are for writes only. GET, DELETE and PUT are already idempotent in HTTP — Stripe says keys have no effect on them. Generate a V4 UUID per logical operation; keys expire out of the system after about 24 hours.

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."

readOnlyHint # doesn't modify → retry freely destructiveHint # modifying change is destructive idempotentHint # "safe to retry on failure"
The hint is not the fix. All MCP annotations are hints, not guarantees — they aren't promised to describe real behavior, and clients shouldn't trust them from untrusted servers. Setting idempotentHint: true does NOT make a tool idempotent. You still have to build the dedup (the key + stored result) in the handler. The flag advertises the property; your code has to actually have it.

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