@@ -65,13 +65,15 @@ def initialize(elicitations)
6565 #
6666 # https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
6767 class UnsupportedProtocolVersionError < RequestHandlerError
68- def initialize ( requested , request = nil , supported : Configuration ::SUPPORTED_MODERN_PROTOCOL_VERSIONS )
68+ # No keyword parameters here: with one present, Ruby 2.7 would split a trailing symbol-keyed `request` Hash
69+ # into keywords and fail with "unknown keywords".
70+ def initialize ( requested , request = nil )
6971 super (
7072 "Unsupported protocol version" ,
7173 request ,
7274 error_type : :unsupported_protocol_version ,
7375 error_code : ErrorCodes ::UNSUPPORTED_PROTOCOL_VERSION ,
74- error_data : { supported : supported , requested : requested || "unknown" } ,
76+ error_data : { supported : Configuration :: SUPPORTED_MODERN_PROTOCOL_VERSIONS , requested : requested || "unknown" } ,
7577 )
7678 end
7779 end
@@ -560,25 +562,26 @@ def handle_request(request, method, session: nil, related_request_id: nil)
560562 server_context : { request : request } ,
561563 exception_already_reported : -> ( e ) { reported_exception . equal? ( e ) } ,
562564 ) do
565+ envelope = lift_request_envelope ( params , method : method , session : session )
563566 result = case method
564567 when Methods ::INITIALIZE
565568 init ( params , session : session )
566569 when Methods ::RESOURCES_READ
567- build_read_resource_result ( read_resource_contents ( params , session : session , related_request_id : related_request_id , cancellation : cancellation ) )
570+ build_read_resource_result ( read_resource_contents ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope ) )
568571 when Methods ::RESOURCES_SUBSCRIBE , Methods ::RESOURCES_UNSUBSCRIBE
569572 validate_resource_subscription_params! ( params )
570- dispatch_optional_context_handler ( @handlers [ method ] , params , session : session , related_request_id : related_request_id , cancellation : cancellation )
573+ dispatch_optional_context_handler ( @handlers [ method ] , params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
571574 { }
572575 when Methods ::TOOLS_CALL
573- call_tool ( params , session : session , related_request_id : related_request_id , cancellation : cancellation )
576+ call_tool ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
574577 when Methods ::PROMPTS_GET
575- get_prompt ( params , session : session , related_request_id : related_request_id , cancellation : cancellation )
578+ get_prompt ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
576579 when Methods ::COMPLETION_COMPLETE
577- complete ( params , session : session , related_request_id : related_request_id , cancellation : cancellation )
580+ complete ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
578581 when Methods ::LOGGING_SET_LEVEL
579582 configure_logging_level ( params , session : session )
580583 else
581- dispatch_optional_context_handler ( @handlers [ method ] , params , session : session , related_request_id : related_request_id , cancellation : cancellation )
584+ dispatch_optional_context_handler ( @handlers [ method ] , params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
582585 end
583586 client = session &.client || @client
584587 add_instrumentation_data ( client : client ) if client
@@ -609,6 +612,34 @@ def handle_request(request, method, session: nil, related_request_id: nil)
609612 }
610613 end
611614
615+ # Lifts the SEP-2575 per-request `_meta` envelope for modern requests. Only a request whose `_meta` carries
616+ # the full required triple is classified as modern; a partial triple keeps flowing through the legacy path untouched.
617+ # Notifications carry no envelope (their `_meta` is a `NotificationMetaObject`), and `server/discover` is
618+ # pre-version discovery, so both are exempt. On a session already era-locked to modern, `initialize` is
619+ # rejected with `-32022` (the modern lifecycle has no handshake) and the triple becomes required for
620+ # every other request.
621+ def lift_request_envelope ( params , method :, session :)
622+ return if Methods . notification? ( method )
623+ return if method == Methods ::SERVER_DISCOVER
624+
625+ modern_session = session . respond_to? ( :era ) && session . era == :modern
626+
627+ if modern_session && method == Methods ::INITIALIZE
628+ requested = params . is_a? ( Hash ) ? params [ :protocolVersion ] || params [ "protocolVersion" ] : nil
629+ raise UnsupportedProtocolVersionError . new ( requested , params )
630+ end
631+
632+ if RequestEnvelope . modern? ( params )
633+ RequestEnvelope . parse! ( params , request : params )
634+ elsif modern_session
635+ raise RequestHandlerError . new (
636+ "Invalid Request: modern sessions require the SEP-2575 `_meta` envelope" ,
637+ params ,
638+ error_type : :invalid_request ,
639+ )
640+ end
641+ end
642+
612643 def handle_cancelled_notification ( params , session : nil )
613644 return unless session
614645 return unless params . is_a? ( Hash )
@@ -748,7 +779,7 @@ def list_tools(request)
748779 apply_cache_metadata ( { tools : page [ :items ] , nextCursor : page [ :next_cursor ] } . compact )
749780 end
750781
751- def call_tool ( request , session : nil , related_request_id : nil , cancellation : nil )
782+ def call_tool ( request , session : nil , related_request_id : nil , cancellation : nil , envelope : nil )
752783 tool_name = request [ :name ]
753784
754785 tool = tools [ tool_name ]
@@ -781,7 +812,7 @@ def call_tool(request, session: nil, related_request_id: nil, cancellation: nil)
781812 progress_token = request . dig ( :_meta , :progressToken )
782813
783814 response = call_tool_with_args (
784- tool , arguments , server_context_with_meta ( request ) , progress_token : progress_token , session : session , related_request_id : related_request_id , cancellation : cancellation
815+ tool , arguments , server_context_with_meta ( request ) , progress_token : progress_token , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope
785816 )
786817 result = response . to_h
787818 validate_tool_call_result! ( tool , result )
@@ -808,7 +839,7 @@ def list_prompts(request)
808839 apply_cache_metadata ( { prompts : page [ :items ] , nextCursor : page [ :next_cursor ] } . compact )
809840 end
810841
811- def get_prompt ( request , session : nil , related_request_id : nil , cancellation : nil )
842+ def get_prompt ( request , session : nil , related_request_id : nil , cancellation : nil , envelope : nil )
812843 prompt_name = request [ :name ]
813844 prompt = @prompts [ prompt_name ]
814845 unless prompt
@@ -826,6 +857,7 @@ def get_prompt(request, session: nil, related_request_id: nil, cancellation: nil
826857 session : session ,
827858 related_request_id : related_request_id ,
828859 cancellation : cancellation ,
860+ envelope : envelope ,
829861 )
830862
831863 call_prompt_template_with_args ( prompt , prompt_args , server_context )
@@ -921,7 +953,7 @@ def apply_cache_metadata(result)
921953 { ttlMs : @ttl_ms || 0 , cacheScope : @cache_scope || "public" } . merge ( result )
922954 end
923955
924- def complete ( params , session : nil , related_request_id : nil , cancellation : nil )
956+ def complete ( params , session : nil , related_request_id : nil , cancellation : nil , envelope : nil )
925957 validate_completion_params! ( params )
926958
927959 result = dispatch_optional_context_handler (
@@ -930,6 +962,7 @@ def complete(params, session: nil, related_request_id: nil, cancellation: nil)
930962 session : session ,
931963 related_request_id : related_request_id ,
932964 cancellation : cancellation ,
965+ envelope : envelope ,
933966 )
934967
935968 normalize_completion_result ( result )
@@ -938,28 +971,30 @@ def complete(params, session: nil, related_request_id: nil, cancellation: nil)
938971 # Invokes `resources/read` via the registered handler. If the handler block opts in to `server_context:`,
939972 # pass an `MCP::ServerContext` so the handler can observe cancellation via `server_context.cancelled?` or
940973 # `server_context.raise_if_cancelled!`.
941- def read_resource_contents ( request , session : nil , related_request_id : nil , cancellation : nil )
974+ def read_resource_contents ( request , session : nil , related_request_id : nil , cancellation : nil , envelope : nil )
942975 dispatch_optional_context_handler (
943976 @handlers [ Methods ::RESOURCES_READ ] ,
944977 request ,
945978 session : session ,
946979 related_request_id : related_request_id ,
947980 cancellation : cancellation ,
981+ envelope : envelope ,
948982 )
949983 end
950984
951985 # Opt-in `server_context:` dispatch for block-based handlers registered via `resources_read_handler`,
952986 # `completion_handler`, `resources_subscribe_handler`, `resources_unsubscribe_handler`, or `define_custom_method`.
953987 # Existing handlers that only accept `params` are called unchanged; handlers that declare a `server_context:`
954988 # keyword receive an `MCP::ServerContext` wrapping the raw server context with cancellation plumbing.
955- def dispatch_optional_context_handler ( handler , params , session : nil , related_request_id : nil , cancellation : nil )
989+ def dispatch_optional_context_handler ( handler , params , session : nil , related_request_id : nil , cancellation : nil , envelope : nil )
956990 return handler . call ( params ) unless handler_declares_server_context? ( handler )
957991
958992 server_context = build_server_context (
959993 request : params ,
960994 session : session ,
961995 related_request_id : related_request_id ,
962996 cancellation : cancellation ,
997+ envelope : envelope ,
963998 )
964999 handler . call ( params , server_context : server_context )
9651000 end
@@ -984,7 +1019,7 @@ def handler_declares_server_context?(handler)
9841019
9851020 # Builds an `MCP::ServerContext` used to give a handler access to session-scoped helpers
9861021 # (progress, cancellation, nested server-to-client requests).
987- def build_server_context ( request :, session :, related_request_id :, cancellation :)
1022+ def build_server_context ( request :, session :, related_request_id :, cancellation :, envelope : nil )
9881023 meta_source = request . is_a? ( Hash ) ? request : { }
9891024 progress_token = meta_source . dig ( :_meta , :progressToken )
9901025 progress = Progress . new ( notification_target : session , progress_token : progress_token , related_request_id : related_request_id )
@@ -994,6 +1029,7 @@ def build_server_context(request:, session:, related_request_id:, cancellation:)
9941029 notification_target : session ,
9951030 related_request_id : related_request_id ,
9961031 cancellation : cancellation ,
1032+ envelope : envelope ,
9971033 )
9981034 end
9991035
@@ -1053,7 +1089,7 @@ def accepts_server_context?(method_object)
10531089 end
10541090 end
10551091
1056- def call_tool_with_args ( tool , arguments , context , progress_token : nil , session : nil , related_request_id : nil , cancellation : nil )
1092+ def call_tool_with_args ( tool , arguments , context , progress_token : nil , session : nil , related_request_id : nil , cancellation : nil , envelope : nil )
10571093 # Transports parse incoming JSON with `symbolize_names: true`, so `arguments` already arrives symbolized
10581094 # at every nesting level. This top-level transform only guards callers that hand in string-keyed top-level arguments;
10591095 # it does not recurse, and nested object keys remain symbols. Tools therefore receive symbol keys all the way down.
@@ -1068,6 +1104,7 @@ def call_tool_with_args(tool, arguments, context, progress_token: nil, session:
10681104 notification_target : session ,
10691105 related_request_id : related_request_id ,
10701106 cancellation : cancellation ,
1107+ envelope : envelope ,
10711108 )
10721109 tool . call ( **args , server_context : server_context )
10731110 else
0 commit comments