skip to content

How would you design an MCP tool whose call is retried after input_required?

level: principalimportance: should knowfreq 33%

answer

  1. every round is a separate request
  2. commit last, after the final input
  3. nobody tells you the user gave up
  4. expire and sign the continuation
  5. cap the rounds, ask for everything at once

basics

~20 s

Design each round to be safely re-executable: do no irreversible work before asking for input, hold no locks or open transactions across rounds, expire the continuation, bound how many rounds a call may take, and assume many clients will simply never come back.

solid answer

~50 s

The Multi Round-Trip pattern turns one logical operation into several independent, separately authorized requests, so the design question is retry safety. Put every side effect **after** the last input you need, or make the pre-input work idempotent — otherwise a client that retries (or a user who abandons and starts over) re-runs it. Hold nothing across rounds: no locks, no open transactions, no reserved capacity, because a client declines simply by never retrying and you will never learn that it did. Keep the continuation in `requestState` self-contained, integrity-protected, size-bounded and short-lived, so any node behind a load balancer can resume it and stale ones fail cleanly. Bound the number of rounds — a server that keeps returning `input_required` burns a user approval each time. And re-authorize every round rather than trusting possession of the handle.

go deeper

for a junior

Understand the core hazard: the same call can arrive more than once, so a tool must not do anything irreversible before it has all the input it needs.

for a middle

Explain concretely why nothing may be held between rounds — there is no session, no connection, and no signal when the client abandons the flow — and place side effects in the final round.

for a senior

Show operational judgment: expiring and integrity-protecting the continuation, resuming on any node behind a load balancer, re-checking authorization each round, and tracing a flow across separate requests.

for a principal

Own the policy: which tools may use MRTR at all versus taking a required argument, how many rounds are acceptable, how abandoned flows are reaped and measured, and how the amplified request and approval volume is budgeted.

## The shape of the problem In MCP revision 2026-07-28 a server that needs input mid-call returns an `InputRequiredResult` and the client comes back with a **new request**: same method and arguments, plus `inputResponses` and the echoed `requestState`. From the server's point of view a single conceptual operation has become N separate requests, each self-contained, each independently authorized, each possibly landing on a different process. Everything difficult about the pattern follows from that. ## 1. Side-effect placement The first design rule is to decide, per tool, where the point of no return sits. If a tool validates, mutates, *then* asks the user to confirm, the mutation has already happened and the confirmation is theatre — and if the user abandons the flow, you are left with a half-applied change nobody acknowledged. Structure the handler as: gather everything you need (possibly over several rounds) → confirm → commit once, in the final round. Where pre-input work must happen, make it idempotent or reversible: writes keyed by a deterministic identifier, staged into a scratch area, or wrapped so a repeat is a no-op. The same reasoning applies across rounds. Round two must tolerate being executed twice, because a client whose connection broke may re-issue it with the same `requestState`. ## 2. Nothing may be held open Between rounds there is no connection to hold, no session to hang state on, and no notification if the client walks away — declining is expressed as *not retrying*, and it is indistinguishable from a slow user, a crashed client, or a network failure. So: no database transaction, no advisory lock, no reserved seat in a rate-limit bucket, no temporary file that only the retry can clean up. If the flow genuinely needs reserved capacity, reserve it with a TTL and a reaper, not with a held resource. ## 3. Continuation design `requestState` is opaque to the client, which leaves the server a real choice: - **Self-contained blob** — encode the continuation, authenticate it (MAC or encryption), stamp it with an expiry, and keep it small, since it travels on every subsequent round. Any node can resume; nothing shared is required. - **Handle into a store** — put only a key in the blob and keep the state in a shared cache with a TTL. Smaller on the wire, but reintroduces infrastructure that every replica must reach. Either way, validate before trusting: a tampered, expired or unknown value should be rejected with an invalid-params error rather than repaired or treated as a fresh call. And never treat possession of the handle as authentication — 2026-07-28 replaced its session-hijacking guidance with **State Handle Hijacking** for exactly this. Check the retry's own credentials, and check they belong to the principal the continuation was minted for. ## 4. Bound the loop Nothing in the protocol caps the number of rounds. Each one costs a full request, a potential user approval, and, if the round asks for a completion, a model call. A server that ratchets through many rounds is both a poor experience and an amplification vector. Decide a ceiling per tool, ask for everything you can in one map — `inputRequests` holds several entries at once — and fail with a clear error rather than looping indefinitely. ## 5. Authorization can change between rounds Each round carries its own token and its own `_meta`. A token may expire, be downgraded, or belong to a different user if the client is careless. The server must re-check scope on the round that finally commits, not merely on the round that started the flow. Equally, because the tool set MAY vary by the authorization presented, a tool that existed in round one might not be permitted in round three — handle that as a clean failure. ## 6. Observability Since rounds are separate requests, ordinary per-request logs will not show them as one operation. Correlate deliberately — a flow identifier inside the continuation, surfaced in logs, plus W3C Trace Context (`traceparent`, `tracestate`, `baggage` are the reserved `_meta` exceptions) so a trace spans the rounds. Without that, abandoned flows are invisible and you cannot measure how often users decline. ## 7. When not to use it at all If the missing information is something the caller could have supplied up front, put it in the tool's `inputSchema` and let the model fill it: a required argument costs zero extra round trips. Reserve MRTR for input only the user or the client can produce at that moment — a confirmation, a credential the model must not see, a choice among options discovered mid-execution. And if the problem is *duration* rather than *missing input*, the `io.modelcontextprotocol/tasks` extension is the right tool, not a retry loop.

  • How does the server learn that the user refused to provide the input?
    It does not. Declining is expressed by the client simply not retrying, and there is no error or notification defined for it. That is indistinguishable from a crash, a network failure, or a slow user, so the server must expire the continuation on a timer and must never leave resources reserved pending an answer that may never arrive.
  • When is MRTR the wrong tool for a missing value?
    When the model could have supplied it. Anything expressible as a tool argument belongs in the tool's inputSchema — that costs no extra round trip and no user approval. Reserve MRTR for input only the user or client can produce mid-execution: confirmations, secrets the model should not see, or a choice among options discovered during the call.
  • How do you trace one logical operation across several MRTR rounds?
    Correlate explicitly. Carry a flow identifier inside the continuation and log it every round, and propagate W3C Trace Context — traceparent, tracestate and baggage are permitted _meta keys — so one trace spans the rounds. Without that, each round looks like an unrelated request and abandoned flows never appear in your metrics.

saying these in an interview costs you the question

  • Holds a database transaction open between rounds
  • Commits the change before asking the user to confirm
  • Expects a decline message when the user refuses
  • Authorizes only the first round of the flow
  • Loops through many rounds instead of asking once

context