What stateless MCP changes for agents that manage links

The 2026-07-28 revision drops sessions and the handshake, adds routing headers and retry-based confirmations. What that means for bulk link jobs, deletes, gateways and OAuth.

A single sealed card travels along a line that forks into three paths leading to three identical server blocks; one path is highlighted, showing any block can take the request.

As of the 2026-07-28 revision, the Model Context Protocol has no sessions and no handshake. Every request carries its own protocol version and client capabilities, any server instance can answer it, and a server that needs a yes from the user no longer holds a stream open to ask: it returns a result that says "input required" and waits for the client to retry.

For agents that create, edit and delete links, that changes three practical things. Bulk jobs scale behind a plain load balancer, but a dropped connection now means a resend, so creates have to be safe to repeat. A "this will overwrite a live destination" confirmation becomes a second request. And gateways can finally tell delete_link from list_links without parsing JSON. This piece walks through the changes that matter if you build or connect agents to a link API. For what a link should return to an agent, see Links for LLMs; this one is about the protocol.

What changed, in one table

The release post by lead maintainers David Soria Parra and Den Delimarsky says the TypeScript, Python, Go and C# SDKs support the revision, with Rust in beta. Microsoft's C# SDK v2.0, released the same day, runs its HTTP transport statelessly by default. The changes that matter for link tooling:

Area2025-11-252026-07-28
SessionsMcp-Session-Id headerRemoved; cross-call state is a server-minted handle passed as a tool argument
Handshakeinitialize, then notifications/initializedRemoved; version and capabilities ride in every request's _meta
DiscoveryPart of initializeserver/discover: servers must implement it, clients may call it
Server asks the userServer-initiated elicitation/create on an open streamMulti Round-Trip Requests: resultType: "input_required", client retries
Change notificationsHTTP GET stream, resources/subscribesubscriptions/listen
RoutingParse the bodyRequired Mcp-Method and Mcp-Name headers
List cachingNoneRequired ttlMs and cacheScope on list results
Broken streamsResumable via Last-Event-IDNot resumable; re-issue with a new request ID

The two sources read slightly differently on server/discover. The release post calls it not required; the versioning page is precise: servers must implement it, and clients may call it or simply send any request and handle UnsupportedProtocolVersionError (now code -32022).

The release post puts it plainly: "any request can land on any instance behind a plain round-robin load balancer." For a job that creates 500 campaign links, that means no sticky sessions and no shared session store. Each tools/call is a self-describing POST.

POST /mcp HTTP/1.1
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2025-11-25","capabilities":{},
           "clientInfo":{"name":"campaign-bot","version":"1.0.0"}}}

# server replies with Mcp-Session-Id: 7c1e...; client sends
# notifications/initialized, then every call carries that header

POST /mcp HTTP/1.1
Mcp-Session-Id: 7c1e...
MCP-Protocol-Version: 2025-11-25

{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"create_link","arguments":{"url":"https://example.com/fall"}}}

Two consequences are easy to miss.

State becomes explicit. The changelog says servers that need cross-call state use "explicit, server-minted handles passed as ordinary tool arguments." A bulk import that used to live in a session becomes a start_import tool that returns an import_id, and every later call passes it. That is easier to log, retry and hand from one agent to another.

Retries become your problem. The Streamable HTTP transport dropped resumability: "Resumable SSE streams via Last-Event-ID are not supported." A broken response stream loses the in-flight request, and the client must re-issue it with a new ID. Closing the stream also counts as cancellation, but a server that already wrote the link to its database cannot un-write it. So a create_link that timed out may or may not have created the link, and the resend must not mint a second one. Two patterns work: let the caller choose the slug, so a repeat collides with the first attempt instead of creating a random duplicate, or accept an idempotency key as a tool argument and return the existing link on repeat.

The revision also asks servers to return tools/list in a deterministic order and requires ttlMs (a freshness hint in milliseconds) and cacheScope ("public" or "private") on list results. A link server whose tools do not change per tenant can let clients cache the tool list instead of polling it before every batch.

Confirmations become retries

Deleting a link that is printed on 40,000 flyers deserves a confirmation. Under the old revision, the server sent elicitation/create while holding the tools/call stream open, which tied the conversation to one server instance. Multi Round-Trip Requests replace that: the server ends the request with an InputRequiredResult, and the client retries the original call with the answer in params.inputResponses, echoing params.requestState exactly.

{"jsonrpc":"2.0","id":7,
 "result":{
   "resultType":"input_required",
   "inputRequests":{
     "confirm_delete":{
       "method":"elicitation/create",
       "params":{
         "mode":"form",
         "message":"go.example.com/fall-26 has live traffic. Delete it?",
         "requestedSchema":{
           "type":"object",
           "properties":{"confirm":{"type":"boolean"}},
           "required":["confirm"]}}}},
   "requestState":"opaque, integrity-protected by the server"}}

The retry needs a new JSON-RPC id, and any instance can process it. The elicitationId field and the notifications/elicitation/complete notification from 2025-11-25 are gone; a server that needs to correlate across retries puts its own identifier in requestState.

One more rule shapes the design: servers must not send an elicitation to a client that did not declare the capability, and must not assume the client will retry at all. A link server should therefore keep a fallback for headless agents, such as an explicit confirm: true argument on destructive tools, or refuse the change and say why.

Headers give gateways something to work with

Every POST now carries Mcp-Method, and tools/call, resources/read and prompts/get also carry Mcp-Name. Servers can mirror chosen tool parameters into Mcp-Param-{Name} headers with the x-mcp-header schema annotation. For link platforms that means a stock gateway can rate-limit create_link per key, demand a stronger scope for delete_link, or reject a tool no policy allows, all before reading the body.

The headers are a claim, not proof. The server must compare them with the body and reject a mismatch with 400 and HeaderMismatch (renumbered to -32020). Tigera's Alister Baroi sums it up: "The header is a claim the server will eventually verify; the body remains the truth." Use headers to deny early and route cheaply; for allowing a sensitive operation, keep checking the body. The spec adds that intermediaries enforcing policy on mirrored headers should reject requests whose MCP-Protocol-Version predates header validation.

Auth: check the issuer, register by URL

Three authorization changes affect any client that connects to a link platform over OAuth:

  • Issuer validation. Authorization servers should send the iss parameter from RFC 9207, and clients must check it against the recorded issuer before redeeming the code. That closes the mix-up attack, where a client talking to several servers is tricked into sending a code to the wrong one.
  • Credentials belong to one issuer. Clients must key stored credentials by issuer, must not reuse them with another authorization server, and must re-register when it changes.
  • Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents: the client_id is an HTTPS URL pointing to a JSON document with at least client_id, client_name and redirect_uris. Servers advertise support with client_id_metadata_document_supported. DCR keeps working for servers that lack it, and clients that still use it must now send application_type.

Token-in-header servers sit outside most of this. Bitly's MCP server, announced in September 2025, takes an API token from account settings in a header, so the OAuth changes do not touch it; the transport changes still do.

Deprecations have a clock now. The new lifecycle policy guarantees at least twelve months before removal, and the deprecated list includes Roots, Sampling, Logging, the old HTTP+SSE transport and DCR.

rii.link runs a remote MCP server at https://mcp.rii.link. Per its docs, any client that speaks Streamable HTTP uses that URL, interactive agents sign in with OAuth 2.1 (PKCE S256, one-hour access tokens), and automation sends a workspace API key as a Bearer header, where "a read-only key yields a read-only agent." The tools are create_link, list_links, update_link, delete_link, list_domains, render_qr and link_stats.

The docs do not yet state which protocol revision the server speaks, or whether create_link deduplicates retries, so we will not claim either here. A 2026-07-28 client can ask the server itself with server/discover. Until retry behaviour is documented, pass your own slug on creates in bulk jobs, so a resend after a dropped stream cannot quietly produce a second, randomly named link.

Migration checklist

If you run an MCP server for links:

  • Implement server/discover and answer unknown versions with UnsupportedProtocolVersionError, listing what you support.
  • Move session state into explicit handles returned by tools.
  • Make every create safe to repeat: caller-chosen slugs or an idempotency argument.
  • Replace server-initiated elicitation with InputRequiredResult, protect requestState with an HMAC or AEAD, and keep a fallback for clients without elicitation.
  • Validate Mcp-Method, Mcp-Name and any Mcp-Param-* headers against the body.
  • Return ttlMs and cacheScope on list results, and list tools in a stable order.
  • Update error codes: -32020, -32021, -32022, and -32602 for a missing resource.

If you build an agent or client:

  • Send MCP-Protocol-Version, Mcp-Method, Mcp-Name and the _meta fields on every request.
  • On a broken stream, re-issue with a new ID, and only for tools you know are safe to repeat.
  • Handle resultType: "input_required" by collecting answers and retrying with a new ID.
  • Validate iss, key credentials by issuer, and prefer Client ID Metadata Documents over DCR.
  • Keep a legacy fallback for servers that still need initialize: on a 400 without a modern JSON-RPC error body, fall back.

Written by

Florian

Florian builds rii.link, the routing layer behind short links: redirects, dynamic responses and links that answer AI agents. He writes The Redirect about what happens between a click and its destination.

On this page