toolcall() ← all concepts

// concept · mcp · spec 2026-07-28

MCP deleted sessions. Your cart ID is not a password.

Protocol-level sessions are gone, which is a good change. What replaced them is an ID your server mints and the client sends back on every call — and that ID is just an argument anyone can fill in.

// what changed, and when

The current MCP protocol version is 2026-07-28, and the version string is itself the news: MCP names releases "following the format YYYY-MM-DD, to indicate the last date backwards incompatible changes were made." Something broke on that date, by design.

What broke is sessions. From the release notes:

The initialize/initialized handshake is removed.
The Mcp-Session-Id header and the protocol-level session that came with it are also removed.

This is a good change and it is worth saying so plainly. A session pinned a client to one server instance; without it, "any MCP request can land on any server instance", so sticky routing and a shared session store stop being requirements. The release notes are also explicit that "removing the protocol-level session does not mean your application has to be stateless."

But something moved onto you

The specification states the consequence directly:

State that needs to span multiple requests (e.g., long-running tasks, application-level handles) MUST be referenced by an explicit identifier the client passes on each request.

So a shopping cart, a workflow, a long-running job — anything that outlives a single call — is now referenced by an ID your server mints and hands back, which the client returns as an ordinary tool argument every time.

# your server, now cart_create() → { cart_id: "1042" } cart_add(cart_id: "1042", item: …) cart_checkout(cart_id: "1042") # cart_id is just an argument. Anyone can put a value there.

Which means every MCP server with state is now issuing identifiers, whether or not anyone on the team stopped to decide what an identifier is.

The trap has a name: state handle hijacking

The security guidance describes it in four plain steps:

1. The MCP server mints a state handle for an authenticated user and returns it in a tool result.
2. The attacker obtains or guesses the handle.
3. The attacker calls the MCP server's tools with the handle as an argument.
4. The MCP server does not check whether the handle belongs to the caller and operates on the original user's state.

Read "obtains or guesses" carefully, because it is two different attacks. Guessing is defeated by unpredictable IDs. Obtaining is not — a handle that leaked through a log line, a screenshot, a shared trace or a support ticket is exactly as valid as one you guessed, and no amount of entropy helps.

That is why the common advice — "use a UUID" — is half a fix, and the more dangerous half to stop at, because it feels complete.

The rule, in RFC 2119 language

MCP servers that implement authorization MUST verify all inbound requests. MCP servers MUST NOT treat possession of a state handle as authentication.

Having the number is not the same as being allowed. And the mitigation is unusually concrete for a security document:

MCP servers SHOULD bind handles server-side to the authenticated user, for example by keying stored state as <user_id>:<handle> where the user ID is derived from the verified token rather than supplied by the client, and reject a handle presented by any other principal.
# wrong — the handle IS the lookup state = store.get(cart_id) # right — the caller is half of the key, and the caller # comes from the verified token, never from the payload user = verify(request.token).subject state = store.get(f"{user}:{cart_id}") ← miss = reject

Then, as defence in depth rather than as the control: "use secure, non-deterministic handles generated with secure random number generators. Avoid predictable or sequential identifiers... Expiring handles can also reduce the risk."

Sources: MCP Versioning · Specification — Statelessness · Security Best Practices — State Handle Hijacking · the 2026-07-28 release post. All loaded 2026-08-17; MCP revises, so check the version banner. Related: tokens issued for somebody else · never pass a token onward.

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