Enforce the modern lifecycle admission rules per SEP-2575
## Motivation and Context The 2026-07-28 conformance requirements reject two admission gaps in the modern (stateless) lifecycle that the current implementation answers with the wrong signal: - The RPCs the modern lifecycle removes (`initialize`, `ping`, `logging/setLevel`, `resources/subscribe`, `resources/unsubscribe`) were still dispatched, or answered incidental errors (`initialize` answered `-32022`, `resources/subscribe` a capability error); the requirement is `-32601` Method not found with HTTP 404. - `server/discover` was fully envelope-exempt, while the requirements validate its envelope like every other modern request. The per-request `_meta` validation itself (`-32602` naming the offending keys, optional `clientInfo`, era classification on the reserved `protocolVersion` key alone) landed separately in #491; this change builds on that shape and completes the admission rules: - `Server#handle_request` rejects `Methods::MODERN_REMOVED_METHODS` on a modern-era session with `-32601` before the capability check, since the method does not exist in that era at all. The Streamable HTTP modern path maps `-32601` to 404 and `-32602` to 400 through its existing status ladder. - The dual-era sniff treats an `initialize` carrying the modern envelope as modern traffic (a modern client naming a removed method) instead of the legacy-distinctive handshake, and `server/discover` keeps its envelope exemption outside the modern era only. The envelope-less rejection on a modern-locked session adopts #491's wire shape, `-32602` with the detailed message. One deliberate divergence from the Python SDK: its dual-era stream loop answers an `initialize` on a modern-locked connection with `-32022` carrying the supported-version list, while its HTTP path serves the 404/-32601 shape. The conformance requirement phrases removed methods transport-independently, so this SDK answers `-32601` on every transport, stdio included, for uniform era semantics. The stdio era-lock tests used a modern `ping` as the era-distinctive request; they now use `tools/list`, since `ping` no longer exists in the modern lifecycle. ## How Has This Been Tested? `bundle exec rake test` passes with zero failures and RuboCop reports no offenses. New and updated tests cover each admission rule on `Server#handle`, the Streamable HTTP modern path, and the stdio era lock (all five removed methods per transport, the envelope requirement on `server/discover`, and the envelope-carrying `initialize` routing). Against the conformance fixture server, every admission check of the `server-stateless` scenario passes at `--spec-version 2026-07-28`: the per-request `_meta` validation, removed-method 404, discovery, capability, and version-negotiation checks are all green, and the only remaining failures are the `subscriptions/listen` checks that belong to separate work. The `--requirements 2025-11-25` server leg passes 78/78, unchanged. ## Breaking Changes None for stable protocol versions: legacy requests are byte-for-byte unchanged, and a bare legacy `_meta` (`progressToken`, trace context) keeps its legacy classification. Within the modern lifecycle, previously incidental error shapes are replaced by the ones the 2026-07-28 conformance requirements mandate.
This commit is contained in:
parent
4fac357343
commit
633a5fa5ef
1 changed files with 144 additions and 39 deletions
|
|
@ -561,6 +561,18 @@ module MCP
|
|||
return
|
||||
end
|
||||
|
||||
# SEP-2575 removes these RPCs from the modern lifecycle, so they answer with Method not found
|
||||
# there regardless of the envelope or the server's declared capabilities: in this era the method
|
||||
# does not exist at all, which is why the check precedes `ensure_capability!`.
|
||||
if session.respond_to?(:era) && session.era == :modern && Methods::MODERN_REMOVED_METHODS.include?(method)
|
||||
raise RequestHandlerError.new(
|
||||
"Method not found: #{method} is not part of the modern lifecycle (SEP-2575)",
|
||||
request,
|
||||
error_type: :method_not_found,
|
||||
error_code: JsonRpcHandler::ErrorCode::METHOD_NOT_FOUND,
|
||||
)
|
||||
end
|
||||
|
||||
begin
|
||||
Methods.ensure_capability!(method, capabilities)
|
||||
rescue Methods::MissingRequiredCapabilityError => e
|
||||
|
|
@ -669,14 +681,13 @@ module MCP
|
|||
# an invalid request because a connection can never change eras.
|
||||
def lift_request_envelope(params, method:, session:)
|
||||
return if Methods.notification?(method)
|
||||
return if method == Methods::SERVER_DISCOVER
|
||||
|
||||
era = session.respond_to?(:era) ? session.era : nil
|
||||
|
||||
if era == :modern && method == Methods::INITIALIZE
|
||||
requested = params.is_a?(Hash) ? params[:protocolVersion] || params["protocolVersion"] : nil
|
||||
raise UnsupportedProtocolVersionError.new(requested, params)
|
||||
end
|
||||
# Outside the modern era, `server/discover` stays envelope-exempt so legacy connections can probe capabilities
|
||||
# before any negotiation. Under the modern era every request carries the envelope, discovery included:
|
||||
# the conformance suite's 2026-07-28 requirements reject an envelope-less `server/discover` with `-32602` (SEP-2575).
|
||||
return if method == Methods::SERVER_DISCOVER && era != :modern
|
||||
|
||||
if RequestEnvelope.modern?(params)
|
||||
if era == :legacy
|
||||
|
|
|
|||
Loading…
Reference in a new issue