Ask the user mid-call: multi round-trip requests
Let a server request input or a model completion during a call, without keeping server-side state.
- Explain how a server asks for more input with
input_required - Build the retry request with
inputResponsesandrequestState - Protect
requestStateagainst tampering
Sometimes a tool can’t finish without help: it needs the user’s confirmation, a missing detail, or a quick model completion. In the stateless 2026-07-28 revision this uses multi round-trip requests (MRTR):
- The client calls the tool as usual.
- The server replies with
resultType: "input_required", listing what it needs ininputRequests(each keyed by a name it picks), plus an opaquerequestState. - The client gathers the answers - by asking the user, say - and retries the original request (with a new id), adding
inputResponsesunder the same keys and echoingrequestStateexactly. - The server completes the call.
The request kinds a server can ask for are elicitation/create (ask the user, via a form), sampling/createMessage and roots/list - and only ones the client declared support for in its capabilities.
Try it
Booking that needs a confirmation
Follow a booking tool that asks the user to confirm the seat before buying. Predict each step.
Never trust what comes back
Because requestState travels through the client, a malicious client could edit it - say, change the price. The spec says servers must treat it as attacker-controlled: if it affects authorization or business logic, protect it with an HMAC or authenticated encryption and reject anything that fails verification. Servers should also bind it to the user, give it a short expiry, and tie it to the original request.
1import hashlib, hmac
2secret = b"server-only-secret"
3state = '{"flight": "TP123", "price": 120}'
4signature = hmac.new(secret, state.encode(), hashlib.sha256).hexdigest()[:16]
5tampered = '{"flight": "TP123", "price": 1}'
6print(hmac.compare_digest(signature, hmac.new(secret, tampered.encode(), hashlib.sha256).hexdigest()[:16]))False
Key takeaways
Servers ask for input with
resultType: "input_required"andinputRequests; clients retry withinputResponses.The retry uses a new id, the same keys, and the exact same
requestState.Servers must integrity-protect
requestStateand only request what the client supports.
Lesson quiz
6 questions · pass with 5 correct · up to 50 XP
Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.
Practice: write Python
Write Python in the editor and run it against sample inputs. Python runs locally in your browser using a WebAssembly runtime.
Build the retry request
The first line is the original tools/call request (JSON). The second is the server’s input_required result (just the result object). The third is a JSON object of the user’s answers, keyed like inputRequests, each already shaped like {"action": "accept", "content": {...}}.
Print the retry request as compact JSON (json.dumps): a copy of the original with id set to the original id + 1, params.inputResponses set to the answers, and params.requestState set to the result’s requestState if it has one. If any input request has no answer, print missing KEY for the first such key (in inputRequests order) instead.
- One answer with state
- An unanswered request
Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.
Verify request state
The first line is the server’s secret. Each following line is a requestState of the form PAYLOAD.SIGNATURE, where SIGNATURE should be the first 16 hex characters of HMAC-SHA256(secret, PAYLOAD). Print valid PAYLOAD or rejected for each line, comparing with hmac.compare_digest.
- Genuine and tampered
- Unsigned
Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.
Questions about this lesson
Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.
Loading posts…