ruby-sdk/lib/mcp
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Koichi ITO b159314baa Fulfill input_required results on the legacy wire per SEP-2322
## Motivation and Context

SEP-2322 handlers return `InputRequiredResult` instead of issuing in-flight server-to-client requests,
but that result type exists only on the 2026-07-28 wire: pre-2026 clients treat an unknown `resultType`
as a final result, so the server rejected `input_required` on legacy requests with an internal error.
That forced authors to keep two handler styles, one per era.

This adds the dual-era authoring shim both reference implementations converged on:
the TypeScript SDK's default-on legacy fulfilment shim (typescript-sdk#2381)
and the Python SDK's version-negotiating resolver (python-sdk#2986).
When a `tools/call`, `prompts/get`, or `resources/read` handler returns an `InputRequiredResult` on
the legacy wire, the server fulfills it in place of the client's driver:

- Every `inputRequests` entry is sent verbatim as the equivalent real server-to-client request through
  a new `ServerSession#fulfill_input_request` (per the spec, clients treat each embedded entry exactly like
  the standalone request), stamped with the originating request id so the association requirement of
  SEP-2260 holds and Streamable HTTP delivery rides the originating POST stream. One exception to
  the verbatim forwarding: a URL-mode elicitation leg gains a synthesized `elicitationId` when
  the handler supplied none, since the 2025-11-25 wire requires the field and the 2026-07-28 in-band
  shape dropped it (correlation rides `requestState` there) - matching the TypeScript shim.
- The answers are collected under the same keys and the handler re-runs with `inputResponses`
  and the raw `requestState` merged into the original params - the same deterministic replay contract
  the modern client driver follows, read through the existing `server_context.input_responses`/`request_state` accessors.
  Only the current round's responses are sent, matching the client driver; handlers carry earlier answers through `requestState`.
- `requestState` round-trips in-process as the value the handler wrote. `RequestStateSecurity` sealing is
  wire hardening against a tampering client and does not apply to the in-process replay.
- The embedded-capability gate still runs each round against the session's declared client capabilities;
  a violation fails before any client contact. The `-32021` code is 2026-only, so the legacy wire gets
  an internal error naming the missing capabilities.
- Rounds are capped at 8 (`LEGACY_INPUT_REQUIRED_MAX_ROUNDS`), matching the TypeScript shim's `maxRounds` default;
  exhaustion fails with an explanatory internal error, which also bounds the `requestState`-only load-shedding form
  that cannot make progress in-process.

The shim is on by default, matching the TypeScript SDK; `Server.new(input_required_legacy_shim: false)` restores
the strict rejection. Requests without a session (no notification path back to a client) and all modern requests
are untouched: the modern wire keeps returning the `input_required` result for the client driver.

Resolves #382.

## How Has This Been Tested?

`bundle exec rake` passes: 1691 test runs / 4381 assertions with zero failures and RuboCop clean.
The conformance baseline check passes on both legs (the frozen 2025-11-25 wire is where the shim lives),
and the modern `input-required-result-*` scenarios stay green, confirming the modern path is untouched.

New `test/mcp/server_input_required_legacy_shim_test.rb` covering: completion through
a real elicitation with the SEP-2260 association asserted on the transport, replay exposing
the collected responses and the raw `requestState`, sealing bypass under `RequestStateSecurity`,
`prompts/get` and `resources/read` shimming, multi-round collection, the round cap, the capability gate failing
before client contact, the URL-mode leg gaining a synthesized `elicitationId` (and a handler-supplied
one riding unchanged), a client answering a fulfilment leg with an error failing the original request,
the opt-out, session-less rejection, and modern requests staying untouched.

The pre-existing legacy rejection test (session-less) and all modern MRTR serialization tests pass unchanged.

## Breaking Changes

Only for handlers that returned `InputRequiredResult` to legacy clients THROUGH A SESSION and relied
on the internal-error rejection: those now drive a real fulfilment round trip by default.
No such handler could have worked end to end before (the request always failed), so this is the intended upgrade;
`input_required_legacy_shim: false` restores the previous behavior. Everything else is unchanged.
2026-08-14 02:46:38 +09:00
..
client Mirror x-mcp-header tool parameters into Mcp-Param-* headers per SEP-2243 2026-08-08 20:58:35 +09:00
elicitation Add MCP::Elicitation::EnumSchema builders for SEP-1330 2026-08-05 12:26:13 +09:00
prompt Add _meta field to resource, content, and result classes 2026-04-11 15:08:55 +09:00
resource Add _meta field to resource, content, and result classes 2026-04-11 15:08:55 +09:00
server Refuse server-to-client requests in the modern lifecycle per SEP-2575 2026-08-13 23:25:04 +09:00
tool fix: preserve explicit tool response content 2026-07-23 03:11:48 +08:00
annotations.rb Add audience role validation for MCP::Annotations per MCP specification 2026-06-22 01:13:17 +09:00
apps.rb Add Server-Side MCP Apps Helpers per SEP-1865 2026-07-07 01:16:02 +09:00
cancellation.rb Support notifications/cancelled per MCP specification 2026-05-03 04:02:22 +09:00
cancelled_error.rb Support notifications/cancelled per MCP specification 2026-05-03 04:02:22 +09:00
client.rb Drive multi round-trip input_required results on the client per SEP-2322 2026-08-14 02:04:21 +09:00
configuration.rb Expose the user-defined server_context in instrumentation data 2026-08-08 08:33:07 +09:00
content.rb Add _meta field to resource, content, and result classes 2026-04-11 15:08:55 +09:00
elicitation.rb Add MCP::Elicitation::EnumSchema builders for SEP-1330 2026-08-05 12:26:13 +09:00
error_codes.rb Add Stateless Lifecycle Foundations per SEP-2575 2026-07-26 20:03:57 +09:00
icon.rb Fix RuboCop offenses detected with rubocop-shopify 3.0.0 2026-07-15 01:46:51 +09:00
instrumentation.rb Expose the user-defined server_context in instrumentation data 2026-08-08 08:33:07 +09:00
logging_message_notification.rb logging_message_notification: Remove unused require 2026-02-05 16:05:08 -05:00
methods.rb Serve the subscriptions/listen notification stream per SEP-2575 2026-08-11 00:05:39 +09:00
progress.rb Support POST response SSE streams for server-to-client messages 2026-04-04 19:20:31 +09:00
prompt.rb Use autoload to defer loading of unused subsystems 2026-03-12 14:08:27 +09:00
protocol_deprecations.rb feat: deprecate roots sampling and logging 2026-08-02 13:13:10 -04:00
request_envelope.rb Serve the subscriptions/listen notification stream per SEP-2575 2026-08-11 00:05:39 +09:00
resource.rb Support class-based Resource and ResourceTemplate definitions 2026-07-10 22:39:42 +09:00
resource_template.rb Fix RuboCop offenses detected with rubocop-shopify 3.0.0 2026-07-15 01:46:51 +09:00
result_type.rb Recognize multi round-trip input_required results per SEP-2322 2026-06-30 11:34:17 +09:00
server.rb Fulfill input_required results on the legacy wire per SEP-2322 2026-08-14 02:46:38 +09:00
server_context.rb Bound server-to-client requests with a timeout 2026-08-13 23:16:59 +09:00
server_session.rb Fulfill input_required results on the legacy wire per SEP-2322 2026-08-14 02:46:38 +09:00
string_utils.rb Fix ReDoS vulnerability in StringUtils#underscore 2026-01-09 01:14:52 +09:00
tool.rb Use autoload to defer loading of unused subsystems 2026-03-12 14:08:27 +09:00
trace_context.rb Support W3C Trace Context Propagation via _meta per SEP-414 2026-06-12 14:36:24 +09:00
transport.rb Serve the subscriptions/listen notification stream per SEP-2575 2026-08-11 00:05:39 +09:00
version.rb Release 1.1.0 2026-08-01 11:29:49 +09:00