Explainer

How does an MCP server ask the client for input now? Multi Round-Trip Requests

The 2026-07-28 MCP spec is stateless, so a server can no longer hold a stream open to ask the client a question mid-call. Instead a tool returns an InputRequiredResult carrying inputRequests and an opaque requestState, and the client re-issues the same call with inputResponses. That pattern is Multi Round-Trip Requests (SEP-2322).

Mark

Head of Marketing, MCPOrbit

Published
Updated
· Updated
Read time
· 7 min read
A tool call returning an InputRequiredResult with an elicitation request and requestState, then the client re-issuing the same call with inputResponses.

In the 2026-07-28 MCP spec a server can no longer reach back through an open connection to ask the client a question. When a tool needs more input — a confirmation, a missing field, an LLM completion — the call returns an InputRequiredResult carrying inputRequests and an opaque requestState. The client collects the answers and re-issues the same call with inputResponses. That round-trip pattern is Multi Round-Trip Requests (SEP-2322), and it is how elicitation and sampling work now that the protocol core is stateless.

If you built a server that pauses mid-tool to prompt the user or ask for an LLM completion, this changes the mechanism you rely on. The behavior survives; the transport under it does not. Here is what replaced it and how to move a server across.

Why the old server-to-client request model had to go

Before the 2026-07-28 revision, a server that needed input mid-call issued a request back down a persistent connection. Elicitation (ask the user) and sampling (ask the client's LLM) both traveled that way, over a bidirectional stream the client had to keep open for the life of the call. That coupling is exactly what blocked MCP from scaling: every long-running call pinned a live SSE stream, and a stateless or serverless deployment — a Cloudflare Worker, a load-balanced fleet — has nowhere to keep that stream.

The 2026-07-28 spec removed protocol-level sessions and the Mcp-Session-Id header from the Streamable HTTP transport, turning the core into a request/response protocol. Once there is no guaranteed session, there is no channel for a server to push a request into. Server-initiated calls needed a new home that carries its own continuity. That home is Multi Round-Trip Requests.

What an InputRequiredResult looks like

Instead of pushing a request to the client, the tool call returns early with a result that says: I need input before I can finish. The result carries two things — inputRequests, a map of the server-initiated requests the client must fulfill (each one a full elicitation or sampling request), and requestState, an opaque string that means something only to the server. The shape below is the spec-level wire form, not SDK-specific code:

// Server returns this from a tool call that needs input
{
  "inputRequests": {
    "confirm-delete": {
      "type": "elicitation",
      "message": "Delete 42 records? This cannot be undone.",
      "requestedSchema": {
        "type": "object",
        "properties": { "confirm": { "type": "boolean" } },
        "required": ["confirm"]
      }
    }
  },
  "requestState": "eyJzdGVwIjoiYXdhaXQtY29uZmlybSIsImJhdGNoIjo0Mn0="
}

requestState is deliberately opaque to the client. The server can encode anything in it — plain JSON, base64-encoded JSON, an encrypted JWT, serialized binary — because the client never reads it. It only echoes it back. That is the trick that makes the whole exchange stateless: all the continuity the server needs to resume the call travels in the payload, not in a session on the server.

How the client completes the round trip

The client satisfies each input request the way that request demands — prompting the user for an elicitation, calling its LLM for a sampling request, listing roots when asked. Then it re-issues the same tool call it made the first time, now with an inputResponses map keyed by the same ids, plus the requestState it was handed, echoed back untouched:

// Client re-issues the ORIGINAL call, now carrying the answers
{
  "name": "delete_records",
  "arguments": { "query": "status = 'archived'" },
  "inputResponses": {
    "confirm-delete": { "confirm": true }
  },
  "requestState": "eyJzdGVwIjoiYXdhaXQtY29uZmlybSIsImJhdGNoIjo0Mn0="
}

The server decodes requestState, sees it was waiting on the confirmation, reads inputResponses, and continues. If it needs more input, it returns another InputRequiredResult and the loop runs again. A single logical tool call can take several round trips, and none of them require a session — which is the entire point.

What this means for elicitation and sampling

Elicitation is not going away. It is now an input request type carried inside inputRequests: a message plus a requestedSchema the client renders to gather structured input from the user. What changed is only how it is delivered — a return value the client answers and resends, rather than a request pushed down a live stream.

Sampling is a different story. The 2026-07-28 spec deprecates Sampling (alongside Roots and Logging) under SEP-2577. Under the new lifecycle policy (SEP-2596) a deprecated feature keeps working for at least twelve months, so nothing breaks today — but the guidance is to stop building new servers on sampling and to call your LLM provider's API directly instead. If your server genuinely needs the client's model, MRTR is the transport; if it can call a model itself, do that.

Migrating a server that used server-initiated elicitation

  • Find every place your server issued a request back to the client mid-tool — elicitation prompts and sampling calls are the common ones.
  • Replace the pushed request with an early return: build an InputRequiredResult whose inputRequests holds the same elicitation or sampling payload.
  • Move the state you were holding in memory between the request and its answer into requestState, encoded however you like, since the client never reads it.
  • On the re-issued call, decode requestState, read inputResponses by id, and resume from where you paused.
  • For sampling specifically, decide whether you still need the client's model at all — the deprecation guidance is to call a provider API directly where you can.

If you are building the client side of this, the loop lives in your call path: detect an InputRequiredResult, fulfill each input request, and resend. Our client walkthrough covers the request/response call flow this plugs into: https://mcporbit.com/blog/build-an-mcp-client . For the surrounding stateless model — why sessions went away in the first place — see https://mcporbit.com/blog/deploy-stateless-mcp-server-cloudflare-workers . And for how these results differ from an actual failure, our error-handling guide draws the line: https://mcporbit.com/blog/handle-errors-in-mcp-server .

Frequently asked questions

What are Multi Round-Trip Requests in MCP?
A pattern introduced in the 2026-07-28 spec (SEP-2322) where a server that needs input returns an InputRequiredResult carrying inputRequests and an opaque requestState, and the client re-issues the same call with inputResponses. It replaces server-initiated requests that used to travel over an open bidirectional stream.
Is elicitation deprecated in the 2026-07-28 MCP spec?
No. Elicitation still exists as an input request type inside inputRequests. Only its delivery changed: it is now a return value the client answers and resends, not a request pushed down a live stream. Sampling, Roots, and Logging are the features that were deprecated.
What is requestState and why is it opaque?
requestState is a string the server uses to remember where it paused a call. It is opaque to the client, which only echoes it back untouched. Because all the server's continuity travels in that payload, the exchange needs no session — which is what lets it run on stateless and serverless deployments.
Does a Multi Round-Trip Request need a session?
No. That is the entire point. The 2026-07-28 spec removed protocol-level sessions and the Mcp-Session-Id header, and MRTR carries continuity in requestState instead of in server-side session state, so it works with no session at all and can repeat across as many rounds as the call needs.
How do I migrate a server that used sampling?
Sampling is deprecated under SEP-2577 and will keep working for at least twelve months. New servers should call an LLM provider's API directly where possible. If you genuinely need the client's model, carry the sampling request inside an InputRequiredResult using Multi Round-Trip Requests rather than the old server-initiated call.

About the author

Mark

Head of Marketing, MCPOrbit

Writes the MCPOrbit build-it series from a working MCP client.

Share this post

MCPOrbit

Test an MCP server in 60 seconds.

Download MCPOrbit for free — no account, no telemetry. Hear about a server and test it before the curiosity wears off.

macOS 14+ · Apple Silicon & Intel · No account needed