MCP Handles: How to Keep Work Across Calls Without Repeats
Your import spans several tool calls, and a broken stream re-sends one write. How does the server keep the work and not write it twice?

IN 30 SECONDS
A handle is an id the MCP server mints for work that lasts across calls, like an import, and the client passes it back as a tool argument. In my design, the server checks who owns it, and an idempotency key stops a re-sent write from running twice.
KEY TAKEAWAYS · 3
- The 2026-07-28 changelog moves cross-call state to “explicit, server-minted handles passed as ordinary tool arguments”.
- My proposal: a handle is hard to guess, has an owner and an expiry, and is refused when anyone else presents it.
- A broken stream comes back as a new request with a new id. In my design, the idempotency key, not that id, spots the repeat.
On this page
Cross-call state becomes a handle
IN SHORT In my design, anything that must outlive one call lives in your store, and the client sends back its handle on each call.
Anything marked as a proposal is my opinion, not the standard. Checks on the caller assume an HTTP server that authenticates each call. The spec makes authorization optional.
WORDS TO KNOW
- Handle
- An id the server mints for something that lasts across calls, like an import or a job.
- Principal
- The user or service a request acts for, shown by its access token.
- Idempotency key
- A value that names one piece of work, so the server can spot a repeat and not do it twice.
- JSON-RPC
- The message format MCP uses. Each request names a method, its parameters and an id.
A multi-step import, a draft built over several tools, a long job: each needs what the changelog calls “explicit, server-minted handles passed as ordinary tool arguments”.
Keyed by session
Keyed by handle
My rules for a Handle An id the server mints for something that lasts across calls, like an import or a job. See the glossary, which are a proposal: the server mints it, and it should be impossible to guess. The store keeps its owner, the Principal The user or service a request acts for, shown by its access token. See the glossary who started it, and refuses anyone else. It has an expiry.
THE OWNER CHECK
Step 1. A call arrives. user-7 sends a call with imp_7KQ2M9, a valid handle that user-42 started.
- A call arrives user-7 sends a call with imp_7KQ2M9, a valid handle that user-42 started.
- Who is calling? The access token says user-7.
- Not the owner: refused The store names user-42 as the owner, so the server rejects the call. A valid handle isn’t a permission.
- The owner When user-42 sends it, ownership matches and the handle hasn’t expired.
- Run The tool runs against the stored import.
Treat a handle like any other untrusted argument: the model on the client side can paste one where it doesn’t belong. It’s the rule from least privilege for agents that move money: check at the server’s door.
A worked example: one write, sent twice
IN SHORT In my design, the handle and the idempotency key, not the JSON-RPC id, are what let a server spot the same write arriving twice.
Version 2026-07-28 drops resumable streams. If the response stream breaks, the spec says the client “MUST re-issue it as a new request with a new request ID”.
ADAM says: So a tool that changes data can see the same call twice. Without an idempotency key, one write can become two.
In my design, every tool that writes gets an Idempotency key A value that names one piece of work, so the server can spot a repeat and not do it twice. See the glossary.
A WORKED EXAMPLE
Illustrative numbers
PASSuser-42 appends rows to an import
INPUT
- Call
- append_rows, id 42
- Handle
- imp_7KQ2M9
- Idempotency key
- b1c0e6
- Access token
- user-42
WHAT HAPPENS
- The token identifies the caller: user-42.
- The store says imp_7KQ2M9 belongs to user-42 and hasn’t expired.
- Key b1c0e6 is new for this handle, so the rows are appended.
OUTPUT
- Result
- rows added
NOT WRITTEN TWICEThe stream breaks, and the client re-issues the call
INPUT
- Call
- same call, new id 43
- Idempotency key
- b1c0e6 (unchanged)
WHAT HAPPENS
- The new JSON-RPC id can’t reveal the repeat: the spec requires a new one.
- The server finds b1c0e6 already recorded for imp_7KQ2M9 and returns the stored result.
OUTPUT
- Rows added
- none, the first write stands
IN PLAIN WORDS
The JSON-RPC The message format MCP uses. Each request names a method, its parameters and an id. See the glossary id still matters for matching a reply to its request. The tool, the handle and the key are my design, with made-up values.
In my design, the key is recorded per handle, next to the result of the first write.
ITEM GET
The key spots the repeat
Sources: changelog, Streamable HTTP, both read on 7 October 2026.
STAGE CLEAR
What you now know
- Why a valid handle still isn’t a permission in my design: the server checks who owns it.
- Why the JSON-RPC id can’t spot a re-sent write, and what does.
Check yourself
Tap or click a question to see the answer.
A client retries a write with the same idempotency key but a different handle. Should the server treat it as a repeat?
No. In my design the key is recorded per handle, so a new handle means new work, checked for ownership like any other call.
Former CTO of Integra. Named author (1 of 5) of ERC-3643, first author of ERC-6960, co-author of ERC-7410, and co-author of ERC-8203, which is still a draft. Building production AI and regulated Web3 systems.
Have a project to ship or a role to fill?
Discuss your project or roleFollow the engineering notes
Get new articles on production AI and smart contracts by email.

