ruby-sdk/lib/mcp
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Koichi ITO 5b4729294c Handle the SEP-2575 Modern Request Envelope in the Server Core
## Motivation and Context

Second step of the stateless lifecycle (SEP-2575, modelcontextprotocol/modelcontextprotocol#2575)
for the 2026-07-28 MCP spec release, building on the `MCP::RequestEnvelope` foundations.

`Server#handle_request` now lifts the per-request `_meta` envelope before dispatch: a request
whose `_meta` carries the full required triple is validated (raising `-32022` with
`data: { supported:, requested: }` for unsupported versions) and its envelope is threaded into
`tools/call`, `prompts/get`, `completion/complete`, `resources/read`, subscribe/unsubscribe,
and custom method handlers via `MCP::ServerContext`. A partial triple keeps flowing through
the legacy path untouched, so existing `_meta` usage (`progressToken`, trace context) is unaffected.

`MCP::ServerContext` gains per-request readers and a capability guard:

- `modern?`, `protocol_version`, `client_info`, and `client_capabilities` read from the envelope first
  and fall back to session state stored by `initialize` on legacy requests. The envelope always wins
  because servers MUST NOT infer client state from prior requests; nothing is written to the session
  on the modern path.
- `require_client_capability!(*path)` raises `Server::MissingRequiredClientCapabilityError`
  (`-32021` with `data: { requiredCapabilities: }`) when the request did not declare the capability.
- `notify_log_message` honors the per-request `io.modelcontextprotocol/logLevel`: on modern requests without it,
  the server MUST NOT send `notifications/message`, and an insufficient level drops the message the same way.

`MCP::ServerSession` gains the connection-era lock of the dual-era serving model: `era` is `nil`
until the first era-distinctive message succeeds, a successful `initialize` locks `:legacy` as
a side effect of `mark_initialized!`, `lock_era!` refuses to flip an established era, and transports can construct
per-request sessions with `era: :modern`. On a modern-locked session, `initialize` is rejected with `-32022`
(the modern lifecycle has no handshake) and the envelope triple becomes required for every other request
except `server/discover`.

The existing plain `RuntimeError` raises in `ServerSession#list_roots` and friends are intentionally unchanged:
converting them to `RequestHandlerError` subclasses would break callers rescuing `RuntimeError`.
Typed `-32021` errors are scoped to the new envelope-based guard.

Refs #389.

## How Has This Been Tested?

New tests in `test/mcp/server_test.rb` cover the wire behavior through `Server#handle`: envelope data exposure
without session mutation, `-32022` for unsupported envelope versions, partial triples staying legacy,
the envelope requirement and `initialize` rejection on modern-locked sessions, the `server/discover` exemption,
`require_client_capability!` returning `-32021` with the `requiredCapabilities` data shape,
and the `ServerSession` era-lock transitions (including preset `era: :modern` and invalid values).

New tests in `test/mcp/server_context_test.rb` cover the per-request logLevel gate (absent, insufficient,
and sufficient levels), the envelope-first/session-fallback readers, and string-keyed capability matching
in `require_client_capability!`.

## Breaking Changes

None. All new keyword arguments default to `nil`, legacy requests take exactly the same code path as before,
and the era lock only constrains sequences that were previously impossible (modern-era traffic).

Fix a Ruby 2.7 ArgumentError When Raising UnsupportedProtocolVersionError

## Motivation and Context

On Ruby 2.7, a method that declares a keyword parameter splits a trailing
symbol-keyed Hash positional argument into keywords. `UnsupportedProtocolVersionError#initialize`
declared `supported:`, so passing the request Hash as the second positional argument raised
`ArgumentError: unknown keywords: :name, :arguments, :_meta` inside the error constructor,
and the intended `-32022` response surfaced as a `-32603` internal error on the Ruby 2.7 CI job.

No caller overrides `supported:`; drop the keyword parameter and read
`Configuration::SUPPORTED_MODERN_PROTOCOL_VERSIONS` directly, matching the keyword-free
shape of `ResourceNotFoundError` and `MissingRequiredClientCapabilityError`.

## How Has This Been Tested?

- Reproduced the `-32603` responses on Ruby 2.7.8 and confirmed both now return `-32022`
- Added a regression test constructing the error with a symbol-keyed request Hash
- `bundle exec rake test` on Ruby 2.7.8: 1467 runs, 0 failures

## Breaking Changes

None. The `supported:` keyword argument was never passed by any caller.
2026-07-31 10:16:41 +09:00
..
client Merge pull request #445 from rickreyhsig-wealthbox/client-tool-annotations 2026-07-28 01:21:53 +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 Lowercase response header names in StreamableHTTPTransport 2026-07-16 11:45:48 +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 Merge pull request #445 from rickreyhsig-wealthbox/client-tool-annotations 2026-07-28 01:21:53 +09:00
configuration.rb Add Stateless Lifecycle Foundations per SEP-2575 2026-07-26 20:03:57 +09:00
content.rb Add _meta field to resource, content, and result classes 2026-04-11 15:08:55 +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 Fix RuboCop offenses detected with rubocop-shopify 3.0.0 2026-07-15 01:46:51 +09:00
logging_message_notification.rb logging_message_notification: Remove unused require 2026-02-05 16:05:08 -05:00
methods.rb Add server/discover and stateless lifecycle error codes per SEP-2575 2026-07-04 20:24:37 +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
request_envelope.rb Add Stateless Lifecycle Foundations per SEP-2575 2026-07-26 20:03:57 +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 Handle the SEP-2575 Modern Request Envelope in the Server Core 2026-07-31 10:16:41 +09:00
server_context.rb Handle the SEP-2575 Modern Request Envelope in the Server Core 2026-07-31 10:16:41 +09:00
server_session.rb Handle the SEP-2575 Modern Request Envelope in the Server Core 2026-07-31 10:16:41 +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 Auto-set server.transport in Transport#initialize 2026-04-09 12:08:55 +09:00
version.rb Release 1.0.0 2026-07-23 13:10:51 +09:00