On this page
- The basic flow
- What changed from older MCP designs
- A conceptual input-required result
- The client retries the original request
- One logical operation can span several HTTP requests
- Give the logical operation its own ID
- requestState must be treated as untrusted
- Servers should protect important state themselves
- Bind requestState to the original operation
- Input responses need stable keys
- Validate every response before retrying
- MRTR can represent more than elicitation
- A server may require multiple rounds
- Set explicit limits
- Cancellation is part of the protocol workflow even if it is host-owned
- App restarts should not destroy the workflow accidentally
- Permission and MRTR state are separate
- Input responses may themselves be sensitive
- Retry safety still matters
- The server should not perform irreversible work before required input when avoidable
- MRTR fits stateless load balancing
- Explicit handles can complement requestState
- Trace each round
- Do not expose opaque requestState to the model by default
- MRTR response handling should be type-safe
- Version-gate MRTR behavior
- A practical state machine
- Handle duplicate UI submission
- Timeouts should pause appropriately while waiting for the user
- Test malformed MRTR payloads
- Test cross-instance continuation
- Test crash recovery
- Where BYOKchat fits
- Further reading
Multi Round-Trip Requests, or MRTR, are one of the most important changes in the 2026-07-28 MCP protocol generation.
They solve a problem created by the move to a stateless protocol core:
How can a server ask the client for more information while an operation is in progress without depending on a persistent server-to-client request channel?
The answer is to end the current request with an explicit input-required result, let the client collect what is needed, and then retry the original operation with the answers attached.
The basic flow
This keeps the protocol stateless while still supporting interactive workflows.
What changed from older MCP designs
Earlier MCP revisions had server-to-client requests such as elicitation or sampling that depended on a live bidirectional interaction model.
The 2026-07-28 design removes the general server-to-client JSON-RPC request channel from the modern core.
Instead, the server returns a structured result that says:
I cannot complete this request yet.
I need these inputs.
Here is continuation state to echo back.
The client then reissues the original operation.
A conceptual input-required result
A simplified example:
{
"resultType": "input_required",
"inputRequests": {
"confirm": {
"type": "elicitation",
"message": "Delete the selected files?",
"schema": {
"type": "boolean"
}
}
},
"requestState": "opaque-continuation"
}
The exact request types and schema depend on the protocol/extension in use.
The important pieces are:
resultTypetells the client this is not a terminal success;inputRequestsdescribes what is needed;requestStatelets the server carry continuation information through the client.
The client retries the original request
The next request is not a new arbitrary operation.
It is the same logical operation continued with additional input.
Conceptually:
{
"method": "tools/call",
"params": {
"name": "delete_files",
"arguments": {
"paths": ["a.txt", "b.txt"]
},
"inputResponses": {
"confirm": true
},
"requestState": "opaque-continuation"
}
}
A good host preserves the logical operation identity across these HTTP requests.
One logical operation can span several HTTP requests
This is the key architecture lesson.
Do not model:
HTTP request = tool operation
Model:
logical MCP operation
attempt 1 -> input_required
attempt 2 -> input_required
attempt 3 -> completed
This makes persistence, cancellation, tracing, and UI much clearer.
Give the logical operation its own ID
For example:
operation_id = op_abc123
Then record individual attempts:
op_abc123 / attempt 1
op_abc123 / attempt 2
op_abc123 / attempt 3
The protocol’s JSON-RPC request IDs can still identify wire exchanges, but your application should not depend on them as the durable workflow identity.
requestState must be treated as untrusted
The server may ask the client to echo requestState back.
The client should generally not interpret it.
Do not treat it as proof that:
- the user approved an action;
- a side effect occurred;
- the active account is correct;
- the server identity is unchanged;
- permission was granted.
Those are host-owned facts.
requestState is server-owned continuation data.
Servers should protect important state themselves
If a server encodes business-critical state inside requestState, it should protect that state appropriately.
For example, if tampering would be dangerous, the server can use an integrity-protected token rather than trusting arbitrary client modifications.
From the client perspective, the safest policy is:
store opaque
bound size
return only to same logical operation/server
never use as host authorization
Bind requestState to the original operation
A client should never accidentally attach continuation state from one workflow to another.
Persist metadata such as:
server connection ID
protocol revision
operation ID
original method
original name/URI/task ID
requestState
MRTR round number
Before retrying, verify that those still match.
Input responses need stable keys
If the server asks for:
confirm
workspace
then the client must return values under the corresponding identifiers.
Do not infer mappings from human-readable labels.
Use the structured keys.
Validate every response before retrying
A host should validate:
- expected field exists;
- type matches;
- enum value is allowed;
- ranges and lengths are valid;
- unsupported response types are rejected;
- sensitive-input policy permits collection.
The server will likely validate too, but client-side validation gives better UX and avoids avoidable retries.
MRTR can represent more than elicitation
Elicitation is the clearest example, but the modern design generalizes the pattern of an operation requiring additional input.
The host should avoid hard-coding MRTR as:
show one confirmation dialog
Instead, model:
operation paused for structured input
Then route supported input request types to the appropriate host behavior.
A server may require multiple rounds
Example:
round 1 -> choose account
round 2 -> choose project
round 3 -> confirm destructive action
round 4 -> final result
The client should maintain a bounded round counter.
Without limits, a broken or malicious server could create an infinite interaction loop.
Set explicit limits
Useful limits include:
max MRTR rounds
max total elapsed time
max total input payload
max requestState size
max number of input requests per round
Do not rely on UI patience as your only loop breaker.
Cancellation is part of the protocol workflow even if it is host-owned
If the user cancels while the operation is waiting for input, persist a terminal application state:
cancelled_by_user
Do not silently retry with empty values.
Do not assume the server interprets missing input the same way your UI does.
App restarts should not destroy the workflow accidentally
A mobile/desktop client may be terminated while waiting for input.
Persist enough non-secret state to recover:
operation ID
server identity
original request
current input-required payload
requestState
round number
collected responses
permission state
On relaunch, validate that the operation is still safe to resume.
Permission and MRTR state are separate
Suppose a tool is configured as Ask.
The host may need to approve the tool before the initial request.
Then the server may later return an input-required confirmation.
These are two distinct decisions:
Host permission: may delete_files execute?
Server workflow input: proceed with these exact 3 files?
Do not use one as a substitute for the other.
See How to Build an MCP Client Permission System.
Input responses may themselves be sensitive
A server might ask for:
- account identifiers;
- email addresses;
- file paths;
- business secrets;
- credentials.
Apply the same privacy policy you would to any outbound request.
Avoid persisting sensitive answers in normal analytics or conversation exports unless the user expects that behavior.
Retry safety still matters
MRTR explicitly requires another request, but that does not mean arbitrary retries are safe.
Consider:
attempt 2 reaches server
server performs side effect
network drops before response
The host sees an unknown outcome.
If it simply replays attempt 2, the side effect may happen twice.
Idempotency/reconciliation is still required for side-effecting operations.
See Idempotency for AI Tool Execution.
The server should not perform irreversible work before required input when avoidable
A well-designed server generally obtains needed confirmation/input before committing irreversible side effects.
For example:
prepare deletion plan
-> input_required confirmation
-> retry
-> perform deletion
rather than:
delete half the files
-> ask user what to do next
The protocol cannot force good business semantics, but clients should prefer tools with understandable transaction boundaries.
MRTR fits stateless load balancing
Because the continuation data and responses travel with the retry, the next request can land on a different server instance.
That is one of the main design goals.
If the server requires hidden process-local state from instance A, it defeats the stateless architecture.
Explicit handles can complement requestState
A server may also expose application state through normal tool arguments/results.
Example:
create_draft -> draft_id
edit_draft(draft_id, ...)
That state is model-visible and application-visible.
requestState is better viewed as continuation state for the current multi-round operation, not as a replacement for every business object handle.
Trace each round
A useful tool trace can show:
Round 1
request -> input_required(workspace)
Round 2
response(workspace=work)
request -> input_required(confirm)
Round 3
response(confirm=true)
request -> success
This is invaluable when debugging servers and user reports.
Do not expose opaque requestState to the model by default
The model generally does not need to see the opaque continuation token.
Keep it in protocol/application state.
Exposing it wastes context and may encourage the model to reason about something it should not interpret.
MRTR response handling should be type-safe
Instead of one generic dictionary everywhere, normalize supported request types into an internal model.
For example:
type InputRequest =
| { kind: "elicitation"; key: string; schema: JSONSchema; message: string }
| { kind: "unsupported"; rawType: string }
Unsupported types should fail explicitly rather than being silently ignored.
Version-gate MRTR behavior
Do not send modern inputResponses behavior to a legacy MCP path that expects the older server-to-client request lifecycle.
The protocol adapter should know which revision is active.
This is another reason to isolate version behavior below the conversation layer.
A practical state machine
created
-> sending
-> awaiting_result
-> input_required
-> collecting_input
-> ready_to_retry
-> sending
-> completed
Possible terminal/exception states:
cancelled
expired
permission_denied
validation_failed
auth_failed
protocol_failed
outcome_unknown
Persist state transitions that matter for recovery.
Handle duplicate UI submission
A user may tap Submit twice or the UI may restore after a crash.
Give each collected response set an internal identity and ensure the host sends at most one active retry unless deliberate retry policy says otherwise.
Do not let UI duplication become network duplication.
Timeouts should pause appropriately while waiting for the user
A 30-second HTTP timeout should not imply:
user has 30 seconds to answer
Separate:
- network-attempt timeout;
- user-input expiration/deadline;
- total logical operation deadline.
A user-facing workflow may reasonably wait minutes while each HTTP attempt remains short-lived.
Test malformed MRTR payloads
Include fixtures for:
missing resultType
empty inputRequests
unsupported request type
invalid schema
duplicate keys
huge requestState
round count exceeded
malformed inputResponses
server changes requested fields on retry
The host should fail safely and preserve diagnostic context.
Test cross-instance continuation
A strong integration test forces:
attempt 1 -> server instance A
attempt 2 -> server instance B
If the workflow only works on sticky routing, the server is relying on hidden state that modern MCP is designed to avoid.
Test crash recovery
Simulate:
input_required received
state persisted
app terminated
app relaunched
user submits input
retry succeeds
Then test the harder case:
retry sent
server may have executed
app terminated before response
The client should represent the outcome as unknown until it can reconcile.
Where BYOKchat fits
A provider-neutral MCP host can treat MRTR as part of its tool-round state machine rather than tying it to one model provider.
The application can persist:
- logical operation identity;
- server/tool identity;
- input-required rounds;
- requestState;
- user responses;
- approval state;
- final tool result.
That makes interactive MCP workflows recoverable and auditable while preserving the stateless protocol model.