Find the product. Drop it in a basket. Add a second item. Check out, which requires a card only the person at the keyboard can hand over. Then wait on a refund that might not surface for a day.
Nothing exotic about any of that. It is the most ordinary multi-step transaction in commerce, and it happens to lie directly across the seam that the 2026-07-28 revision opened. The protocol shrank. The basket, which used to ride along inside a connection, is now something you hold in your own hands.
The first bill arrives before the basket exists. The agent has to establish who it is to the merchant's server, and Dynamic Client Registration has been deprecated in favor of Client ID Metadata Documents: your client_id becomes an HTTPS URL, and the authorization server fetches it and confirms that the document names itself. Two things vanish, a registration write and a per-server database record, and what appears in their place is a document you are now responsible for keeping reachable, accurate, and stable, indefinitely, across every merchant you will ever touch. The work didn't shrink. It changed custodians.
Then the calls. There is no initialize handshake anymore, so a client cannot ask once what it is speaking to and coast on the answer. Every request restates io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities inside _meta. Omit them and you get -32602. Name a version the server does not speak and it hands back the list it does. On the page this reads as pure overhead. What the overhead buys is that no server in the pool needs to have met this conversation before; any of them can answer cold. The price is that nothing is remembered, which raises the obvious question of what holds four calls together as one story. The answer sits in the same _meta envelope: traceparent, tracestate, and baggage, the OpenTelemetry propagation keys, reserved unprefixed next to the namespaced ones.
That leaves the basket somewhere odd. In the specification's own example, create_basket returns {"basket_id": "bsk_a1b2c3"}, and add_item accepts that string as an ordinary argument, parked beside the SKU like any other parameter. The tools page is admirably direct about what that arrangement is. Guidance, not machinery.
MCP "has no concept of a state handle."
There is no reserved field for it, no lifecycle, no expiry.
A session was a room that remembered you had walked into it. A handle is a numbered tag: it names a coat and proves nothing whatsoever about whose coat. Hence the guidance that servers re-verify the caller's authorization every time one shows up. Worth naming, and then worth walking past.
Checkout is where the application stops pretending and admits it is a state machine. The server answers with resultType: "input_required", a URL elicitation for payment, and an opaque requestState. You collect the human part, then retry the same operation under a fresh JSON-RPC ID, restating the business arguments and echoing requestState back untouched. Both ride alongside arguments, not inside it. Two identifiers in flight now, doing entirely unrelated work: one names a basket, the other names a paused interaction. And accept means something narrower than it looks, reporting that the person finished the interaction rather than that the payment cleared. The distance between those two facts is yours to model, because nothing upstream models it for you.
Then the refund, which Shopify's order documentation puts at up to 24 hours. Durable execution has moved out of the base protocol into the Tasks extension, and a server that needs it from a client that has not declared it must fail with -32021 rather than improvise. Relocated, not deleted. Skip the extension and you have two options: finish inside the request, or build job handles yourself.
A May 2026 measurement study counted 7,973 live remote servers, noting in its own words that its scanners "may not capture MCP servers deployed behind CDNs, firewalls, or private networks." Deployment trails specification here, as it always does.
Back in issue 37, this publication anatomized a tool call down to its parts: a method, a name, arguments, with authorization arriving through the connection wrapped around all of it. That wrapper has been withdrawn. What occupies the space it left is an object in your repository, named in your domain's vocabulary, governed by rules you write: what the handle binds to, when it dies, which transitions you allow.
Most teams were already keeping that state. In a variable, under a name nobody chose carefully, with rules nobody wrote down.
-
The identity layer is provisional: MCP now leans on Client ID Metadata Documents, but the underlying OAuth Client ID Metadata Document specification was still an IETF working draft rather than a final RFC as of this writing, so the mechanism you are building against can still move.
-
What Tasks actually promises: If your slow path needs durable execution, the Tasks extension proposal is the document that defines the lifecycle — task IDs, status polling, cancellation, and the
-32021 MissingCapabilityrejection a server owes a client that never declared support. -
Handles in shipped commerce interfaces: The pattern of cart ID into dependent operations is already live in vendor documentation, including Salesforce's agentic commerce shopper tools, which authenticates with a JWT, calls
create_basket, then threadsbasketIdthrough everything downstream. -
Who pays when the agent gets it wrong: Agent-initiated purchases are live at European merchants, but reporting on the launch notes that dispute rules still turn on consumer intent and merchant processing, leaving errors introduced by the agent itself without a settled home.

