Merge pull request #489 from koic/enforce_modern_request_admission
Enforce the modern lifecycle admission rules per SEP-2575
This commit is contained in:
commit
b98b783a5f
3 changed files with 144 additions and 39 deletions
|
|
@ -21,6 +21,20 @@ module MCP
|
|||
TOOLS_CALL = "tools/call"
|
||||
TOOLS_LIST = "tools/list"
|
||||
|
||||
# RPC methods the stateless modern lifecycle removes (MCP 2026-07-28, SEP-2575):
|
||||
# `initialize` is replaced by the per-request `_meta` envelope plus `server/discover`,
|
||||
# `logging/setLevel` by the envelope's `logLevel` member, and `ping` and the resource
|
||||
# subscription pair by the connectionless model, which leaves nothing to keep alive
|
||||
# or subscribe on. A modern-era request naming one of these answers with `-32601`
|
||||
# Method not found (HTTP 404 on Streamable HTTP).
|
||||
MODERN_REMOVED_METHODS = [
|
||||
INITIALIZE,
|
||||
PING,
|
||||
LOGGING_SET_LEVEL,
|
||||
RESOURCES_SUBSCRIBE,
|
||||
RESOURCES_UNSUBSCRIBE,
|
||||
].freeze
|
||||
|
||||
ROOTS_LIST = "roots/list"
|
||||
SAMPLING_CREATE_MESSAGE = "sampling/createMessage"
|
||||
ELICITATION_CREATE = "elicitation/create"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1041,10 +1041,16 @@ module MCP
|
|||
!MCP::Configuration.modern_protocol_version?(version)
|
||||
end
|
||||
|
||||
# Era sniff for a sessionless POST under a dual-era header version: only an `initialize` body is legacy-distinctive.
|
||||
# Unparsable or non-object bodies go to the modern path, whose error responses cover them.
|
||||
# Era sniff for a sessionless POST under a dual-era header version: only an `initialize` body
|
||||
# WITHOUT the modern `_meta` envelope is legacy-distinctive. An `initialize` carrying
|
||||
# the envelope comes from a modern client naming a method its lifecycle removed, so it stays
|
||||
# on the modern path and answers with -32601/404 (SEP-2575).
|
||||
# Unparsable or non-object bodies go to the modern path, whose error responses cover them.
|
||||
def legacy_handshake_body?(body_string)
|
||||
initialize_request?(parse_request_body(body_string))
|
||||
body = parse_request_body(body_string)
|
||||
return false unless initialize_request?(body)
|
||||
|
||||
!RequestEnvelope.modern?(body[:params])
|
||||
rescue InvalidJsonError
|
||||
false
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue