Let Handlers Return Multi Round-Trip input_required Results per SEP-2322 - #481
Open
koic wants to merge 1 commit into
Open
Let Handlers Return Multi Round-Trip input_required Results per SEP-2322#481koic wants to merge 1 commit into
input_required Results per SEP-2322#481koic wants to merge 1 commit into
Conversation
…-2322 ## Motivation and Context SEP-2322 (modelcontextprotocol/modelcontextprotocol#2322, merged for the 2026-07-28 MCP spec release) replaces server-initiated JSON-RPC requests, which the stateless modern lifecycle (SEP-2575) forbids, with multi round-trip results: a `tools/call`, `prompts/get`, or `resources/read` handler returns `resultType: "input_required"` with `inputRequests` (server-assigned keys mapping to `elicitation/create`, `sampling/createMessage`, or `roots/list` request shapes) and/or an opaque `requestState`, and the client retries the ORIGINAL request with `inputResponses` under the same keys plus the echoed state. The client-side recognition landed earlier (`MCP::ResultType`, `Client::InputRequiredError`); this adds the server side, mirroring the Python SDK's low-level Server (python-sdk#2967/#2986) and the TypeScript SDK's umbrella 2026-07-28 work. - New `MCP::Server::InputRequiredResult` value object: construction validation (at least one of the two fields, embedded methods restricted to the three shapes), `to_h` wire serialization, and the capability mapping shared with the TypeScript SDK's `requiredClientCapabilitiesForInputRequest` (url-mode elicitation requires `elicitation.url`, form otherwise; sampling with `tools`/`toolChoice` requires `sampling.tools`; `roots/list` requires `roots`), including the 2025 back-compat rule that a bare `elicitation: {}` declaration implies form support. - Handlers return the object through the existing paths: both branches of `call_tool_with_args` and `call_prompt_template_with_args` pass it through instead of calling `.to_h`, `call_tool` skips output schema validation and the structured-content fallback for it (output schema validation would otherwise run against a `nil` `structuredContent`), and the `resources/read` dispatch skips the `contents` wrapping and SEP-2549 cache-hint stamping. - One central hook in the dispatch lambda gates and serializes the result, after the cancellation check so cancelled requests stay suppressed: a legacy request (no SEP-2575 envelope) gets an internal error, because pre-2026 clients treat an unknown `resultType` as a final result; embedded requests exceeding the request's declared client capabilities get `-32021` with the full merged `requiredCapabilities` set. - `MCP::ServerContext` gains `input_responses`, `request_state`, and the key-tolerant `input_response(key)` reader. The retry fields are params-top-level siblings of `name`/`arguments`/`uri` (not `_meta`), so only handlers that opt in to `server_context:` can participate, matching the envelope readers' access model. The server holds no memory between rounds: handlers re-run from the start on every retry (deterministic replay, as in the Python SDK). Sealing of the echoed `requestState` (it arrives as client-controlled input) and the client-side auto-fulfillment loop follow in the next changes. Refs modelcontextprotocol#382. ## How Has This Been Tested? New `test/mcp/server/input_required_result_test.rb` covers construction validation, key normalization and freezing, the wire shape of `to_h`, the full capability mapping, missing-capability subtraction with symbol/string declarations, and the implied-form back-compat rule. New tests in `test/mcp/server_test.rb` drive `Server#handle` with the modern envelope: issuance wire shape for all three methods, the retry leg exposing `input_responses`/`request_state`/ `input_response(key)` to the handler, the legacy-request internal error, `-32021` with the merged `requiredCapabilities` data, output-schema validation bypass under `validate_tool_call_results: true`, and `resources/read` results staying unwrapped without `ttlMs`/`cacheScope` stamping even when the server configures cache hints. ## Breaking Changes None. The serialization seams only branch on a return type that previously could not occur, all new keyword arguments default to `nil`, and requests that carry no `inputResponses`/`requestState` behave exactly as before.
koic
force-pushed
the
input_required_results
branch
from
August 4, 2026 14:19
778acd2 to
d2e568b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
SEP-2322 (modelcontextprotocol/modelcontextprotocol#2322, merged for the 2026-07-28 MCP spec release) replaces server-initiated JSON-RPC requests, which the stateless modern lifecycle (SEP-2575) forbids, with multi round-trip results: a
tools/call,prompts/get, orresources/readhandler returnsresultType: "input_required"withinputRequests(server-assigned keys mapping toelicitation/create,sampling/createMessage, orroots/listrequest shapes) and/or an opaquerequestState, and the client retries the ORIGINAL request withinputResponsesunder the same keys plus the echoed state. The client-side recognition landed earlier (MCP::ResultType,Client::InputRequiredError); this adds the server side, mirroring the Python SDK's low-level Server (python-sdk#2967/#2986) and the TypeScript SDK's umbrella 2026-07-28 work.MCP::Server::InputRequiredResultvalue object: construction validation (at least one of the two fields, embedded methods restricted to the three shapes),to_hwire serialization, and the capability mapping shared with the TypeScript SDK'srequiredClientCapabilitiesForInputRequest(url-mode elicitation requireselicitation.url, form otherwise; sampling withtools/toolChoicerequiressampling.tools;roots/listrequiresroots), including the 2025 back-compat rule that a bareelicitation: {}declaration implies form support.call_tool_with_argsandcall_prompt_template_with_argspass it through instead of calling.to_h,call_toolskips output schema validation and the structured-content fallback for it (output schema validation would otherwise run against anilstructuredContent), and theresources/readdispatch skips thecontentswrapping and SEP-2549 cache-hint stamping.resultTypeas a final result; embedded requests exceeding the request's declared client capabilities get-32021with the full mergedrequiredCapabilitiesset.MCP::ServerContextgainsinput_responses,request_state, and the key-tolerantinput_response(key)reader. The retry fields are params-top-level siblings ofname/arguments/uri(not_meta), so only handlers that opt in toserver_context:can participate, matching the envelope readers' access model. The server holds no memory between rounds: handlers re-run from the start on every retry (deterministic replay, as in the Python SDK).Sealing of the echoed
requestState(it arrives as client-controlled input) and the client-side auto-fulfillment loop follow in the next changes.Refs #382.
How Has This Been Tested?
New
test/mcp/server/input_required_result_test.rbcovers construction validation, key normalization and freezing, the wire shape ofto_h, the full capability mapping, missing-capability subtraction with symbol/string declarations, and the implied-form back-compat rule.New tests in
test/mcp/server_test.rbdriveServer#handlewith the modern envelope: issuance wire shape for all three methods, the retry leg exposinginput_responses/request_state/input_response(key)to the handler, the legacy-request internal error,-32021with the mergedrequiredCapabilitiesdata, output-schema validation bypass undervalidate_tool_call_results: true, andresources/readresults staying unwrapped withoutttlMs/cacheScopestamping even when the server configures cache hints.Breaking Changes
None. The serialization seams only branch on a return type that previously could not occur, all new keyword arguments default to
nil, and requests that carry noinputResponses/requestStatebehave exactly as before.Types of changes
Checklist