← Blog
July 31, 2026 · 15 min

MCP deprecates sampling: the end of borrowed inference

Sampling was MCP's most elegant idea: a server could borrow the client's model and never hold an API key. The specification finalized three days ago rebuilt that mechanism from scratch, then deprecated it in the same document. The migration path is one line long, and it moves the inference bill.

The Model Context Protocol's 2026-07-28 revision is the largest reshaping of the protocol since it launched, and we have covered it from two angles already: the stateless core when the release candidate locked, and the Tasks extension on the day the spec went final. Both pieces circled the same structural change without naming its sharpest consequence.

Here it is. Before this revision, an MCP server could reach back through the connection and ask the client for three things: the filesystem roots it should operate on, a log line to display, and — the interesting one — a completion from the client's language model. All three of those channels have now been closed, reopened in a different shape, and marked for deletion. The reopening is SEP-2322, Multi Round-Trip Requests. The deletion is SEP-2577. Reading them together is the only way the release makes sense.

The problem MRTR was built to solve

Start with the operational failure that forced the redesign. SEP-2322, authored by Mark D. Roth, Caitie McCaffrey and Gabriel Zimmerman and created on February 3, 2026, opens by dividing MCP tools into two categories. Ephemeral tools accumulate no server-side state — a weather lookup, an email fetch. If they need more information, they can start over from scratch once they have it. Persistent tools accumulate state: they may compute for a long time before needing input, and they may need to keep working in the background while they wait.

The SEP's judgment is blunt: "the vast majority of MCP tools will be ephemeral," and they are typically deployed horizontally scaled behind a load balancer. That deployment is where the old model broke.

Consider a tool call that needs to ask the user a question mid-execution. The client's tools/call lands on server instance A. Instance A opens an SSE stream and pushes an elicitation request down it. The user answers, and the client sends that answer as a separate HTTP request — which the load balancer routes independently, landing it on instance B. Now instance A is holding an in-memory tool invocation waiting for data that arrived at instance B.

There were exactly two ways out, and the SEP dismantles both. You could deploy a storage layer shared across instances — Postgres, Redis, DynamoDB — which the authors describe as "extremely expensive," a single point of failure requiring high availability and replication, a bottleneck on horizontal scaling, and a garbage-collection problem where cleaning up aggressively cuts storage cost but caps how long a user has to answer. Or you could make load balancing sticky with cookies, which breaks even load distribution, requires client cooperation, and is not fault tolerant: if the instance dies, the call restarts.

Both approaches also depend on a long-lived SSE stream, which many environments will not sustain, and both pin a tool instance in memory for an unbounded wait. As the SEP notes about elicitation specifically, the answer "may not come from the user for an unbounded amount of time — it could be days or months, or maybe even never."

InputRequiredResult: the continuation token becomes the protocol

The fix inverts the direction of control. Instead of the server pushing a request down a stream it holds open, the server ends the call and returns an incomplete result describing what it still needs. The client gathers the answers and issues a brand-new request carrying them.

Two type families implement it. InputRequests is a map from server-assigned string keys to request objects. InputResponses is a map with identical keys carrying the client's results. The map — rather than a list with embedded ids — was chosen deliberately: it "structurally guarantees the uniqueness of keys," removing the need for conflict checks in every SDK.

The envelope is InputRequiredResult, discriminated by a new resultType field on every Result:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "github_login": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Please provide your GitHub username",
          "requestedSchema": {
            "type": "object",
            "properties": { "name": { "type": "string" } },
            "required": ["name"]
          }
        }
      }
    },
    // opaque to the client; the server's entire memory of this call
    "requestState": "AEAD-protected blob"
  }
}

The client answers by re-issuing the original call with the responses attached and the state echoed back verbatim:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "update_work_item",
    "arguments": { "workItemId": 4522 },
    "inputResponses": {
      "github_login": {
        "action": "accept",
        "content": { "name": "octocat" }
      }
    },
    "requestState": "AEAD-protected blob"
  }
}

resultType takes "complete" or "input_required", is open to extension values, and defaults to "complete" when absent — which is how backward compatibility is preserved. Clients SHOULD treat unrecognized values as invalid protocol responses.

The scope is deliberately narrow. Servers MAY return an InputRequiredResult on exactly three client requests — prompts/get, resources/read and tools/call — and MUST NOT return one on anything else. The values inside inputRequests MUST be one of ElicitRequest, CreateMessageRequest or ListRootsRequest. That list is worth holding onto: two of those three types are, as of this same revision, deprecated.

The spec is explicit that this is not additive. Servers MUST send server-to-client requests using MRTR; "the previous pattern of server-initiated requests is no longer supported. This is a breaking change."

requestState is attacker-controlled, and the spec says so

The whole design rests on a blob of server state making a round trip through an untrusted intermediary. The normative text in the final specification is considerably harder than the draft SEP, and it is the most security-dense passage in the release.

Servers MUST treat requestState as attacker-controlled input. If it influences authorization, resource access, or business logic, servers MUST protect its integrity — the spec names HMAC or AEAD — and MUST reject state that fails verification. Integrity protection MAY be omitted only when tampering can cause nothing worse than request failure.

Replay gets its own set of requirements. Servers SHOULD include three things inside the integrity-protected payload and verify each on receipt: the authenticated principal, rejecting state presented by a different principal; a short expiry, rejecting state presented after it lapses; and an identifier for the originating request — the spec suggests the method name plus a digest of its salient parameters — rejecting state presented on a request that does not match.

The honest caveat — the spec then admits the limit of its own advice: these measures "bound the replay window and prevent cross-user and cross-request reuse, but do not by themselves guarantee single-use." Servers where a given requestState must be consumed at most once — one-time redemptions, anything that moves money — MUST enforce that invariant server-side.

That is the second time in two months the protocol has handed out a self-authenticating artifact and then told implementers to bind it themselves. The taskId in the Tasks extension has the same shape: entropy is mandated, identity binding is not. A gateway sitting in front of these servers inherits the job both times.

The remaining rules are shorter but consequential. Every InputRequiredResult MUST carry at least one of inputRequests or requestState. Servers MUST NOT include an input request type the client has not declared support for. Servers MUST NOT assume the client will ever fulfill the requests or retry at all. On the client side: requestState MUST be echoed exactly and MUST NOT be inspected, parsed or modified; if none was supplied, none may be sent; and the JSON-RPC id MUST differ between the original request and the retry, because they are independent requests.

Error handling follows the same philosophy. If the client sends parameters the server did not ask for, the server SHOULD ignore them. If the client omits something required, the server SHOULD respond with a fresh InputRequiredResult rather than an error — the authors considered a dedicated error code and rejected it, reasoning that the client may not have enough information to recover, whereas re-asking always works.

State without storage: two uses beyond asking questions

The subtle part of MRTR is that inputRequests is optional. A server can return an InputRequiredResult carrying only requestState, in which case the client MAY retry immediately without prompting anyone. That unlocks two patterns that have nothing to do with user input.

The first is rolling upgrades. Suppose the old build of a tool asked for github_login and google_login, and the new build asks for github_login and microsoft_login. If the first round hits an old instance and the retry hits a new one, the new instance sees an answer it needs and an answer it does not, and still lacks one. It can issue a new request for the missing piece while folding the already-collected github_login answer into requestState — so the user is never asked the same question twice across a deploy.

The second is load shedding. An overloaded instance that has already done significant work on a call can serialize its accumulated progress into requestState, return it with no input requests, and let the client's immediate retry land on a different instance that resumes where the first left off. The protocol has effectively given servers a way to migrate an in-flight computation through the client.

Where Tasks picks up

MRTR optimizes the ephemeral case; it does not pretend to cover the persistent one. When a server genuinely needs to keep working in the background while it waits, the workflow hands off to the Tasks extension, using the same data structures. The task enters the input_required status, the client discovers this by polling tasks/get, retrieves the inputRequests, and answers through a dedicated method rather than by retrying the original call. Because the task holds state on the server, the original operation is never terminated — the server simply resumes.

One transition rule is worth internalizing: a tool can start ephemeral and become a task, gathering the input it needs through MRTR before committing to any server-side storage. It cannot go the other way. "Once a tool implementation returns a task, it has committed to storing state on the server side for the duration of the task, and there is no way to transition back to the ephemeral model."

Underneath both sits SEP-2260, from the MCP Transports Working Group, which upgraded a SHOULD to a MUST: server-to-client requests must be associated with an originating client request. Standalone server-initiated sampling, elicitation or roots requests on an independent stream MUST NOT be implemented, with ping the sole exception. Clients receiving one anyway SHOULD answer with -32602. The user-facing consequence is worth stating plainly: a server can no longer interrupt you out of nowhere. Every prompt traces back to something you or your agent initiated.

Rebuilt and buried in the same revision

Now the strange part. Open the sampling page of the 2026-07-28 specification and you will find it fully rewritten for MRTR — every example reframed as an input request delivered inside InputRequiredResult, complete with a multi-turn tool loop, toolChoice modes, and detailed cross-provider compatibility rules for Claude, OpenAI and Gemini message shapes. Directly above all of that sits a deprecation warning.

SEP-2577, created April 14, 2026 by Kurtis Van Gent, deprecates roots, sampling and logging together. The stated criterion is features with "the weakest adoption-to-complexity ratio." For sampling specifically: correct implementation demands human-in-the-loop approval, model selection logic, security handling and — since SEP-1577 — tool loop support, and the feature support matrix shows few clients ever adopted it despite availability since the November 2024 spec.

The maintainers explicitly considered moving these features to extensions instead, and rejected it. Under the extensions framework, implementations must behave as if an absent extension does not exist; retrofitting that logic into existing SDKs across multiple protocol versions was judged "complex and error-prone." Deprecation followed by removal was the less disruptive path.

Nothing breaks yet. No types are removed, capability negotiation is unchanged, and wire behavior is identical — the change is @deprecated annotations in the schema and warning blocks in the docs. The deprecated features registry, a new artifact of the SEP-2596 lifecycle policy, states the deadline precisely: roots, sampling, logging and Dynamic Client Registration all become eligible for removal in the first revision released on or after 2027-07-28.

The registry also states the migration path for each. Roots: pass directories via tool parameters, resource URIs, or server configuration. Logging: stderr for stdio transports, OpenTelemetry for observability. Sampling gets six words — "Integrate directly with LLM provider APIs."

Six words that move the bill

Read the sampling page's own description of what the feature was for: it let clients "maintain control over model access, selection, and permissions while enabling servers to leverage AI capabilities — with no server API keys necessary."

That clause is the entire economic content of sampling. A server that wanted a model did not need a provider account, did not need a credit card, did not need to meter anything. It borrowed the client's model, and the client's user paid — usually without seeing a line item, since the tokens disappeared into their existing subscription.

Deprecating sampling reverses that, and the official migration path says so directly. Every MCP server that wants inference must now hold its own provider credentials and fund its own token spend. The security argument for the change is sound — SEP-2577 calls sampling "the most security-sensitive of the three," creating attack surface for prompt injection and data exfiltration, and it is hard to argue that letting an arbitrary server drive your model through your credentials was ever comfortable. But the cost does not vanish. It relocates, from the client's subscription to the server's balance sheet.

Elicitation is the survivor, and URL mode is where money moves

Of the three request types MRTR can carry, only elicitation is not deprecated. It is now the protocol's sole sanctioned channel for a server to ask for something mid-call — and it has two modes with very different trust properties.

Form mode collects structured data through the client, constrained to flat objects with primitive properties. It carries a hard prohibition: servers MUST NOT use form mode to request passwords, API keys, access tokens or payment credentials, and MUST use URL mode for anything in that class.

URL mode sends the user out of band to a page the server controls. The spec's framing of its purpose is unusually direct: the same request "could direct the user into an OAuth authorization flow, or a payment flow. The only difference is the URL and the message." Data other than the URL is never exposed to the client, which means credentials never transit the LLM context, the MCP client, or any intermediate server. An action: "accept" response means the user consented to open the link — not that the interaction finished. The server determines completion from the echoed requestState or its own storage, and returns either the final result or another InputRequiredResult.

The security requirements around it are strict, and mostly about a specific attack. Because the URL is attacker-forwardable, a malicious user can trigger an elicitation, then trick a second user of the same server into completing the flow — binding the victim's third-party tokens to the attacker's identity, an account takeover. The server therefore MUST verify that the user who opens the URL is the user for whom the elicitation was generated. Clients, for their part, MUST NOT pre-fetch the URL, MUST NOT open it without explicit consent, MUST show the full URL, and MUST open it in a way that prevents the client or the LLM from inspecting the content or the user's input.

What it means for LLM4Agents

The deprecation of sampling is, read economically, a demand signal for exactly the layer we operate.

An MCP server that needs a model now needs an account, a key, a budget, and a rotation story — per provider. For a human-run SaaS that is a Tuesday afternoon of paperwork. For an autonomous server, or for the long tail of small servers in the registry, it is a real barrier: signup flows assume a human with a card. This is precisely the gap x402 walk-up closes. An OpenAI-compatible endpoint that accepts a stablecoin payment per request lets an MCP server buy inference with no account and no onboarding — the decision tree we published earlier now has a much larger population on the walk-up branch than it did a month ago.

MRTR also changes what a billable unit is. A logical operation that used to be one tools/call is now potentially three independent HTTP requests with three different JSON-RPC ids, separated by an unbounded human pause. Any gateway metering "per tool call" will mis-bill in both directions: it will undercount, because rounds two and three each consume verification, decoding and partial compute; and it will overcharge callers who expect one price per operation. The correct model is to meter per round trip and treat an input_required response as a completed, billable unit of work — which maps cleanly onto the reserve-then-settle cycle, with the reserve released and the settle written at each round rather than held open across a wait that may last months.

Third, requestState lands on our side of the trust boundary. A gateway that fronts MCP servers is the natural place to enforce what the spec merely recommends: principal binding, short TTLs, and at-most-once consumption for any state that gates a payment. The spec's own admission — that its replay guidance does not guarantee single-use — is an invitation to put that invariant somewhere durable.

Finally, URL mode elicitation is a spec-blessed payment approval surface. When a tool call needs a human to authorize spend, the protocol now says: do not put it in a form, send them to a URL, and make sure the person who arrives is the person who asked. That is a payment confirmation page, described in normative language, sitting alongside the interface layer MCP Apps opened last week.

Staying on the frontier

Concretely, and in order:

Ship an MRTR-aware meter first. Before anything else, billing must count round trips rather than logical operations, and record an input_required response as settled work. Everything downstream depends on the accounting being right.

Target servers migrating off sampling. The population is identifiable today: any MCP server declaring the sampling capability has a July 2027 deadline and a one-line migration instruction pointing at a provider API. A documented path from sampling/createMessage to an OpenAI-compatible call — same message shapes, same tool loop, same toolChoice semantics the spec already documents for cross-provider compatibility — is a short, high-value integration guide.

Harden requestState handling in our own MCP server. AEAD over the blob, the authenticated principal inside it, a TTL measured in minutes, a digest of the originating method and parameters, and a server-side single-use ledger for any state that authorizes spend. Treat it with the same discipline as an EIP-3009 nonce, because it does the same job.

Wire URL mode elicitation to x402 walk-up approval. When a call requires human sign-off on a payment, return a URL mode request pointing at a consent page bound to the authenticated session, and verify identity at the far end before accepting anything.

Test against the betas now, not in 2027. The Tier 1 SDKs went to beta on June 29, 2026 — Python mcp v2.0.0b1, TypeScript v2, Go v1.7.0-pre.1, C# v2.0.0-preview.1 — and the stable releases followed with the specification, with Rust in beta. The breaking change is already in the SDKs; a conformance suite that exercises multi-round flows against each is cheap insurance.

Then plan for removal, not just deprecation. July 28, 2027 is a real date on a published registry. Any code path in the platform that reads roots, emits protocol log messages, or proxies a sampling request should be inventoried now and scheduled, so the removal revision is a non-event.

The protocol spent this revision making servers cheaper to run and harder to trust with your model. Both halves point the same direction: servers get their own inference, their own budget, and their own bill. That bill needs somewhere to land.

Inference your MCP server can pay for itself

OpenAI-compatible gateway, per-request stablecoin settlement, no account required.

Register an agent