Skip to content
Wiki

MCP multi round-trip requests let a stateless server ask for input by returning input_required and waiting for a retry

DrFritzi · Reviewed · Updated 28 Sept 2026 · Markdown

Answer

A multi round-trip request (MRTR) is how an MCP server asks the client for more input under revision 2026-07-28. The server does not send a request of its own. It answers the client's request with an InputRequiredResult (resultType: "input_required") that lists inputRequests. The client collects the answers and sends the original request again, with inputResponses added. The server can finish without any stored state. This replaces roots/list, sampling/createMessage and elicitation/create as server-initiated requests. The change is breaking.

Details

Which requests can be interrupted

Only tools/call, resources/read and prompts/get. Servers MUST NOT return InputRequiredResult on other requests.

The fields

Field Direction Meaning
inputRequests server to client Map from a server-chosen key to an ElicitRequest, CreateMessageRequest or ListRootsRequest. Keys must be unique within the request
requestState server to client, echoed back Opaque string. The client MUST NOT inspect or change it, and MUST echo it exactly
inputResponses client to server Map with the same keys, holding each result

Every InputRequiredResult MUST contain inputRequests, requestState, or both. A server MUST NOT ask for a feature the client did not declare, for example elicitation.

Example exchange

Interim result to the first tools/call (id: 1), abridged from the specification's example:

{"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"]}}}},
  "requestState":"AEAD-protected blob"}}

The retry is a new request with a different id. It repeats the original name and arguments, and adds:

{"inputResponses":{"github_login":{"action":"accept","content":{"name":"octocat"}}},
 "requestState":"AEAD-protected blob"}

The JSON-RPC id MUST differ between the first request and the retry, because they are independent requests. If the interim result had no requestState, the retry MUST NOT include one. If the client leaves out needed answers, the server SHOULD send another InputRequiredResult instead of an error.

Why it exists

Each retry contains everything the server needs. Any instance behind a load balancer can finish the call, and no shared store or sticky routing is required. That fits the removal of protocol sessions and of server-to-client requests on the response stream in 2026-07-28. See mcp-2026-07-28-changes.

Pitfalls

  • requestState is attacker-controlled. It travels through the client. If it influences authorization, resource access or business logic, the server MUST protect its integrity (the spec names HMAC or AEAD) and reject state that fails verification.
  • Bind the state. The spec says servers SHOULD put the authenticated principal, a short expiry and an identifier of the originating request inside the protected payload. These limit replay but do not make state single-use. A one-time redemption MUST be enforced by the server itself.
  • Retries run the tool again. The retry is a fresh tools/call. The spec does not say how to make side effects safe, so treat this as a design rule: do the side-effecting work only after all input has arrived, or keep it idempotent.
  • The client may never retry. Servers MUST NOT assume the request will be fulfilled or retried.
  • State is per retry. inputRequests and requestState apply only to that retry, not to parallel requests.
  • Elicitation completion signals are gone. notifications/elicitation/complete and elicitationId were removed. A server that must correlate an elicitation across retries puts its own identifier in requestState.

See also

Sources