SKIP TO CONTENT
AI AGENTS5 minAgents in Production / Ep. 11 · Stage 11-3

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?

XIN
Pixel-art night scene of a row of identical server cabinets with no luggage beside them. Each glowing envelope arriving on a conveyor carries its own small backpack, and a teal lockbox key hangs from one envelope.

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

A per-process map from session id to an import. It breaks when the retry lands on another instance, the process restarts, or two conversations share one stdio connection. State can then mix or vanish silently, and the logs may not say which.

Keyed by handle

start_import returns imp_7KQ2M9. The row sits in a shared store with its owner and an expiry. Each later call passes the handle, the server checks the caller owns it, and every log line names it.

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

Flow diagram: 5 stepsFlow diagram: 5 steps. A call arrives with a handle, then Who is calling?. Who is calling?, then Does the caller own it?. Does the caller own it?, then Still valid?. Still valid?, then Run the tool.1A call arrives with ahandle2Who is calling?From the access token3Does the caller own it?The store names the owner4Still valid?Not expired5Run the tool

Step 1. A call arrives. user-7 sends a call with imp_7KQ2M9, a valid handle that user-42 started.

Pick a step to see it move
  1. A call arrives user-7 sends a call with imp_7KQ2M9, a valid handle that user-42 started.
  2. Who is calling? The access token says user-7.
  3. 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.
  4. The owner When user-42 sends it, ownership matches and the handle hasn’t expired.
  5. Run The tool runs against the stored import.
My proposed checks for a handle, with made-up users. The owner check is what stops user-7.

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

  1. The token identifies the caller: user-42.
  2. The store says imp_7KQ2M9 belongs to user-42 and hasn’t expired.
  3. 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

  1. The new JSON-RPC id can’t reveal the repeat: the spec requires a new one.
  2. 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

A re-issued call has a new JSON-RPC id, so in my design only the idempotency key tells the server it already did this write.

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.

Next stage:What should a stateless MCP server log on every request, and how do you plan the cutover?6 min
Adam Boudjemaa
Adam Boudjemaa

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 role
Follow the engineering notes

Get new articles on production AI and smart contracts by email.