InputRequiredResult elicitation flow
MCP 2026-07-28 (SEP-2322) replaces the experimental Tasks-shaped elicitation flow with a stateless multi-round-trip exchange. When a server cannot finish a call without more input, it returns an InputRequiredResult carrying one or more input requests and an opaque continuation token. The client re-issues the original method with the user's answers and the echoed token; any server instance can pick the retry up because the state travels in the request, not in a per-session table.
This page is the operator-level reference: the wire shapes, the recognition rule, and the v1 / v2 scope split.
Wire shapes
The server's response carries an InputRequiredResult inside its result field:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"inputRequests": [
{
"id": "city",
"kind": "string",
"description": "What city?",
"required": true
}
],
"requestState": "opaque-token-abc-123",
"description": "Need the city before I can fetch the forecast."
}
}
The client's retry is the original method call with two added fields: inputResponses (the user's answers, keyed by the matching request's id) and requestState (the echoed token):
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "weather",
"arguments": { "loc": "SFO" },
"inputResponses": [
{ "id": "city", "value": "San Francisco" }
],
"requestState": "opaque-token-abc-123"
}
}
The original args stay intact so the server treats the retry as the same logical call.
Recognition rule
A response is an InputRequiredResult when its result field deserializes as one and:
inputRequestsis a non-empty array.requestStateis a non-empty string.
Both conditions must hold; a half-shaped response (inputRequests present but empty, or requestState missing or empty) is treated as a normal result so the client does not loop on a server bug.
The library function is mcptest_core::protocol::recognize_input_required(envelope) -> Option<InputRequiredResult>.
Library
mcptest_core::protocol::elicitation exposes:
| Type / Function | Purpose |
|---|---|
InputRequest | One requested field: id, kind, description, required. |
InputResponse | One user answer: id matches a prior request. |
InputRequiredResult | The server-side result shape. |
RequestState | Opaque continuation token wrapper. |
recognize_input_required(envelope) | Detect and parse an InputRequiredResult off a response. |
build_retry_params(original, state, responses) | Construct the retry params object. |
All wire types use camelCase serde renames so they match the spec on the wire. InputRequest.required defaults to true when the server omits the field (matches the SEP-2322 default).
What's shipped
- The typed wire model and the recognizer (
mcptest_core::protocol::elicitation). build_retry_paramsfor constructing the retry envelope from a validated answer list.- Runner-side retry loop (
mcptest_core::executor::elicitation):run_elicitation_chain(initial_response, initial_params, fixture, max_rounds, next_response)walks the elicitation chain until the server returns a non-elicitation result or the cap fires.collect_responses(requests, fixture)enforces the required-vs-optional rule and skips optional requests that have no fixture entry. InputResponseFixturefor non-interactive answer sources, plusDEFAULT_MAX_ELICITATION_ROUNDS = 5so a misbehaving server cannot loop forever.- Tests pin every recognition branch, the camelCase wire keys, the
requireddefault, the params-non-object defensive path, plus the executor's pure retry loop (one-round happy path, max-rounds cap, pass-through).
Planned follow-up
- YAML surface: declare an
inputResponses:fixture on a tool test so a suite can satisfy elicitation deterministically. The library accepts the fixture today; the YAML field + loader wiring lands once the wire shape is reviewed. - Cassette extension: record the elicitation round-trip so a replay reproduces it byte-stable, even when the request state is opaque. The current cassette format captures one request/response per exchange; multi-turn capture extends the schema with an explicit elicitation-turn array.
- Interactive prompt: read answers from stdin in TTY mode, validate against the requested
kind, fall through to the fixture in non-interactive mode.
Cross-references
mcptest_core::protocol::elicitationfor the recognizer / retry builder library.mcptest_core::executor::elicitationfor the runner loop.- SEP-2322 for the spec.