Refuse server-to-client requests in the modern lifecycle per SEP-2575

## Motivation and Context

The modern lifecycle removes server-initiated JSON-RPC requests: a stateless server has no connection to send them on,
and SEP-2322 multi round-trip `input_required` results replace what sampling and elicitation covered before.
`StdioTransport#send_request` already says so, refusing outright when its session is modern.
`StreamableHTTPTransport#send_request` says nothing, so a modern handler that reaches for elicitation
or sampling falls through to the session lookup and is told `Session not found: <uuid>` against a session id it never chose.
Failing is right, the explanation is not: `handle_modern` mints a session per request without ever registering it in `@sessions`,
so that lookup cannot succeed for a modern request no matter what the handler does.

`send_request` now refuses a modern-era session up front with the message `StdioTransport` uses, which turns
an incidental registration detail into a stated rule and leaves the rest of the method a legacy-only path by construction.
Callers see the same `RuntimeError` class as before, only with a message naming the actual constraint.

## How Has This Been Tested?

A new test in `test/mcp/server/transports/streamable_http_transport_test.rb` sends `elicitation/create` from
a session constructed with `era: :modern` and asserts the SEP-2575 message. It sits with the existing guard tests for stateless mode,
a missing `session_id`, and an unknown session, which still pass unchanged.
`bundle exec rake` is green, and both conformance legs pass their baseline check.

## Breaking Changes

None. The call already failed; only the message changes.
This commit is contained in:
Koichi ITO 2026-08-11 03:21:33 +09:00
commit bea636c007

View file

@ -479,6 +479,16 @@ module MCP
# so a client that never answers cannot park the calling thread for good. On expiry the peer is
# sent `notifications/cancelled` and `MCP::Server::RequestTimeoutError` is raised.
def send_request(method, params = nil, session_id: nil, related_request_id: nil, parent_cancellation: nil, server_session: nil, timeout: nil)
# The modern lifecycle (SEP-2575) forbids server-initiated JSON-RPC requests;
# multi round-trip `input_required` results (SEP-2322) replace them. A modern session has never reached
# the rest of this method anyway, since `handle_modern` mints its session without registering it in `@sessions`,
# but that is incidental: without the rule stated here a modern handler that reaches for elicitation
# or sampling is told "Session not found: <uuid>", which points at everything except the actual reason.
# `StdioTransport#send_request` refuses the same way.
if server_session&.era == :modern
raise "Server-initiated requests are not available in the modern lifecycle (SEP-2575)."
end
if @stateless
raise "Stateless mode does not support server-to-client requests."
end