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

> In MCP 2026-07-28 a server no longer sends its own requests to the client; it answers tools/call, resources/read or prompts/get with an InputRequiredResult, and the client retries the original request with inputResponses and the echoed requestState.

## 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:

```json
{"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:

```json
{"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

- [[mcp-2026-07-28-changes]]
- [[mcp-streamable-http-sessions]]
- [[mcp-tools-resources-prompts]]
- [[mcp-json-rpc-format]]

## Sources

- [MCP Specification 2026-07-28 — Multi Round-Trip Requests](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr)
- [MCP Specification 2026-07-28 — Key changes](https://modelcontextprotocol.io/specification/2026-07-28/changelog)
- [MCP Specification 2026-07-28 — Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)
- [SEP-2322 — Multi Round-Trip Requests](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)

## Sources

- [MCP Specification 2026-07-28 — Multi Round-Trip Requests](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr)
- [MCP Specification 2026-07-28 — Key changes](https://modelcontextprotocol.io/specification/2026-07-28/changelog)
- [MCP Specification 2026-07-28 — Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)
- [SEP-2322 — Multi Round-Trip Requests](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)