@@ -733,9 +733,9 @@ def handle_request(request, method, session: nil, related_request_id: nil, auth_
733733
734734 handler = @handlers [ method ]
735735 unless handler
736- instrument_call ( "unsupported_method" , server_context : { request : request } ) do
736+ instrument_call ( "unsupported_method" , server_context : { request : request } ) do | instrumentation_data |
737737 client = session &.client || @client
738- add_instrumentation_data ( client : client ) if client
738+ instrumentation_data . merge! ( client : client ) if client
739739 end
740740 return
741741 end
@@ -798,7 +798,7 @@ def handle_request(request, method, session: nil, related_request_id: nil, auth_
798798 method ,
799799 server_context : { request : request } ,
800800 exception_already_reported : -> ( e ) { reported_exception . equal? ( e ) } ,
801- ) do
801+ ) do | instrumentation_data |
802802 refuse_non_object_meta! ( params )
803803 envelope = lift_request_envelope ( params , method : method , session : session )
804804
@@ -817,7 +817,7 @@ def handle_request(request, method, session: nil, related_request_id: nil, auth_
817817 when Methods ::INITIALIZE
818818 init ( params , session : session )
819819 when Methods ::RESOURCES_READ
820- contents = read_resource_contents ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
820+ contents = read_resource_contents ( params , instrumentation_data : instrumentation_data , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
821821
822822 # An SEP-2322 `input_required` result must not be wrapped as `contents` or stamped with SEP-2549 cache hints.
823823 contents . is_a? ( InputRequiredResult ) ? contents : build_read_resource_result ( contents )
@@ -827,9 +827,9 @@ def handle_request(request, method, session: nil, related_request_id: nil, auth_
827827
828828 subscription_result ( handler_result )
829829 when Methods ::TOOLS_CALL
830- call_tool ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
830+ call_tool ( params , instrumentation_data : instrumentation_data , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
831831 when Methods ::PROMPTS_GET
832- get_prompt ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
832+ get_prompt ( params , instrumentation_data : instrumentation_data , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
833833 when Methods ::COMPLETION_COMPLETE
834834 complete ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
835835 when Methods ::LOGGING_SET_LEVEL
@@ -838,10 +838,10 @@ def handle_request(request, method, session: nil, related_request_id: nil, auth_
838838 dispatch_optional_context_handler ( @handlers [ method ] , params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope , auth_info : auth_info )
839839 end
840840 client = session &.client || @client
841- add_instrumentation_data ( client : client ) if client
841+ instrumentation_data . merge! ( client : client ) if client
842842
843843 if cancellation &.cancelled?
844- add_instrumentation_data ( cancelled : true , cancellation_reason : cancellation . reason )
844+ instrumentation_data . merge! ( cancelled : true , cancellation_reason : cancellation . reason )
845845 next JsonRpcHandler ::NO_RESPONSE
846846 end
847847
@@ -851,14 +851,15 @@ def handle_request(request, method, session: nil, related_request_id: nil, auth_
851851 result = if envelope . nil? && @input_required_legacy_shim && session
852852 run_legacy_input_required_shim (
853853 result ,
854+ instrumentation_data : instrumentation_data ,
854855 method : method ,
855856 params : params ,
856857 session : session ,
857858 related_request_id : related_request_id ,
858859 cancellation : cancellation ,
859860 )
860861 else
861- serialize_input_required_result ( result , envelope : envelope , request : params , method : method )
862+ serialize_input_required_result ( result , instrumentation_data : instrumentation_data , envelope : envelope , request : params , method : method )
862863 end
863864 end
864865
@@ -884,25 +885,25 @@ def handle_request(request, method, session: nil, related_request_id: nil, auth_
884885
885886 result
886887 rescue CancelledError => e
887- add_instrumentation_data ( cancelled : true , cancellation_reason : e . reason )
888+ instrumentation_data . merge! ( cancelled : true , cancellation_reason : e . reason )
888889 next JsonRpcHandler ::NO_RESPONSE
889890 rescue OAuth ::InsufficientScopeError => e
890891 # A handler's `require_scopes!` failure is a client authorization error, not a server fault:
891892 # surface it as an invalid-request JSON-RPC error whose message names the missing scopes.
892893 # HTTP-level 403 challenges remain the transport `required_scopes` gate's job.
893894 report_exception ( e , { request : request } )
894- add_instrumentation_data ( error : :insufficient_scope )
895+ instrumentation_data . merge! ( error : :insufficient_scope )
895896 converted = RequestHandlerError . new ( e . message , request , error_type : :invalid_request , original_error : e )
896897 reported_exception = converted
897898 raise converted
898899 rescue RequestHandlerError => e
899900 report_exception ( e . original_error || e , { request : request } )
900- add_instrumentation_data ( error : e . error_type )
901+ instrumentation_data . merge! ( error : e . error_type )
901902 reported_exception = e
902903 raise e
903904 rescue => e
904905 report_exception ( e , { request : request } )
905- add_instrumentation_data ( error : :internal_error )
906+ instrumentation_data . merge! ( error : :internal_error )
906907 wrapped = RequestHandlerError . new ( "Internal error handling #{ method } request" , request , original_error : e )
907908 reported_exception = wrapped
908909 raise wrapped
@@ -998,7 +999,7 @@ def lift_request_envelope(params, method:, session:)
998999 # a final result. The capability gate enforces the SEP-2575 rule that servers MUST NOT rely on
9991000 # (or embed requests for) capabilities the client did not declare, and reports every missing capability at
10001001 # once so the client sees the full set.
1001- def serialize_input_required_result ( result , envelope :, request :, method :)
1002+ def serialize_input_required_result ( result , instrumentation_data : , envelope :, request :, method :)
10021003 if envelope . nil?
10031004 raise RequestHandlerError . new (
10041005 "input_required results require the 2026-07-28 stateless lifecycle (SEP-2322)" ,
@@ -1010,7 +1011,7 @@ def serialize_input_required_result(result, envelope:, request:, method:)
10101011 missing = result . missing_client_capabilities ( envelope . client_capabilities )
10111012 raise MissingRequiredClientCapabilityError . new ( missing , request ) unless missing . empty?
10121013
1013- add_instrumentation_data ( input_required : true )
1014+ instrumentation_data . merge! ( input_required : true )
10141015 serialized = result . to_h
10151016
10161017 if @request_state_security && serialized [ :requestState ]
@@ -1039,7 +1040,7 @@ def serialize_input_required_result(result, envelope:, request:, method:)
10391040 # re-runs with `inputResponses`/`requestState` merged into the original params - the same deterministic replay
10401041 # contract the modern client driver follows. The `requestState` round-trips in-process as the raw value
10411042 # the handler wrote; `RequestStateSecurity` sealing is wire hardening and does not apply.
1042- def run_legacy_input_required_shim ( result , method :, params :, session :, related_request_id :, cancellation :)
1043+ def run_legacy_input_required_shim ( result , instrumentation_data : , method :, params :, session :, related_request_id :, cancellation :)
10431044 rounds = 0
10441045
10451046 loop do
@@ -1071,6 +1072,7 @@ def run_legacy_input_required_shim(result, method:, params:, session:, related_r
10711072 result = redispatch_mrtr_method (
10721073 method ,
10731074 retry_params ,
1075+ instrumentation_data : instrumentation_data ,
10741076 session : session ,
10751077 related_request_id : related_request_id ,
10761078 cancellation : cancellation ,
@@ -1104,14 +1106,14 @@ def legacy_leg_params(entry)
11041106
11051107 # Re-runs the handler of one of the three MRTR-capable methods for
11061108 # the legacy shim. Legacy wire, so no envelope is threaded.
1107- def redispatch_mrtr_method ( method , params , session :, related_request_id :, cancellation :)
1109+ def redispatch_mrtr_method ( method , params , instrumentation_data : , session :, related_request_id :, cancellation :)
11081110 case method
11091111 when Methods ::TOOLS_CALL
1110- call_tool ( params , session : session , related_request_id : related_request_id , cancellation : cancellation )
1112+ call_tool ( params , instrumentation_data : instrumentation_data , session : session , related_request_id : related_request_id , cancellation : cancellation )
11111113 when Methods ::PROMPTS_GET
1112- get_prompt ( params , session : session , related_request_id : related_request_id , cancellation : cancellation )
1114+ get_prompt ( params , instrumentation_data : instrumentation_data , session : session , related_request_id : related_request_id , cancellation : cancellation )
11131115 when Methods ::RESOURCES_READ
1114- contents = read_resource_contents ( params , session : session , related_request_id : related_request_id , cancellation : cancellation )
1116+ contents = read_resource_contents ( params , instrumentation_data : instrumentation_data , session : session , related_request_id : related_request_id , cancellation : cancellation )
11151117 contents . is_a? ( InputRequiredResult ) ? contents : build_read_resource_result ( contents )
11161118 else
11171119 raise RequestHandlerError . new (
@@ -1394,21 +1396,21 @@ def list_tools(request)
13941396 apply_cache_metadata ( { tools : page [ :items ] , nextCursor : page [ :next_cursor ] } . compact )
13951397 end
13961398
1397- def call_tool ( request , session : nil , related_request_id : nil , cancellation : nil , envelope : nil , auth_info : nil )
1399+ def call_tool ( request , instrumentation_data : , session : nil , related_request_id : nil , cancellation : nil , envelope : nil , auth_info : nil )
13981400 tool_name = request [ :name ]
13991401
14001402 tool = tools [ tool_name ]
14011403 unless tool
1402- add_instrumentation_data ( tool_name : tool_name , error : :tool_not_found )
1404+ instrumentation_data . merge! ( tool_name : tool_name , error : :tool_not_found )
14031405
14041406 raise RequestHandlerError . new ( "Tool not found: #{ tool_name } " , request , error_type : :invalid_params )
14051407 end
14061408
14071409 arguments = request [ :arguments ] || { }
1408- add_instrumentation_data ( tool_name : tool_name , tool_arguments : arguments )
1410+ instrumentation_data . merge! ( tool_name : tool_name , tool_arguments : arguments )
14091411
14101412 if tool . input_schema &.missing_required_arguments? ( arguments )
1411- add_instrumentation_data ( error : :missing_required_arguments )
1413+ instrumentation_data . merge! ( error : :missing_required_arguments )
14121414
14131415 missing = tool . input_schema . missing_required_arguments ( arguments ) . join ( ", " )
14141416 return error_tool_response ( "Missing required arguments: #{ missing } " )
@@ -1418,7 +1420,7 @@ def call_tool(request, session: nil, related_request_id: nil, cancellation: nil,
14181420 begin
14191421 tool . input_schema . validate_arguments ( arguments )
14201422 rescue Tool ::InputSchema ::ValidationError => e
1421- add_instrumentation_data ( error : :invalid_schema )
1423+ instrumentation_data . merge! ( error : :invalid_schema )
14221424
14231425 return error_tool_response ( e . message )
14241426 end
@@ -1473,11 +1475,11 @@ def list_prompts(request)
14731475 apply_cache_metadata ( { prompts : page [ :items ] , nextCursor : page [ :next_cursor ] } . compact )
14741476 end
14751477
1476- def get_prompt ( request , session : nil , related_request_id : nil , cancellation : nil , envelope : nil , auth_info : nil )
1478+ def get_prompt ( request , instrumentation_data : , session : nil , related_request_id : nil , cancellation : nil , envelope : nil , auth_info : nil )
14771479 prompt_name = request [ :name ]
14781480 prompt = @prompts [ prompt_name ]
14791481 unless prompt
1480- add_instrumentation_data ( error : :prompt_not_found )
1482+ instrumentation_data . merge! ( error : :prompt_not_found )
14811483 # The explicit `error_code` maps an unknown prompt to Invalid Params (-32602) rather than
14821484 # the default Internal Error (-32603), matching the `tools/call`, `resources/read`, and
14831485 # `completion/complete` siblings for the same not-found condition, while `error_type:
@@ -1490,7 +1492,7 @@ def get_prompt(request, session: nil, related_request_id: nil, cancellation: nil
14901492 )
14911493 end
14921494
1493- add_instrumentation_data ( prompt_name : prompt_name )
1495+ instrumentation_data . merge! ( prompt_name : prompt_name )
14941496
14951497 prompt_args = request [ :arguments ]
14961498 prompt . validate_arguments! ( prompt_args )
@@ -1766,9 +1768,9 @@ def skills_extension_keys(extensions)
17661768 # Default `resources/read` handler: routes to class-based resources and resource templates.
17671769 # Fully replaced when `resources_read_handler` is set. When no class-based resource or template is registered,
17681770 # unknown URIs keep the historical no-op `[]` response instead of raising.
1769- def read_resource ( request , server_context : nil )
1771+ def read_resource ( request , instrumentation_data : , server_context : nil )
17701772 uri = request [ :uri ]
1771- add_instrumentation_data ( resource_uri : uri )
1773+ instrumentation_data . merge! ( resource_uri : uri )
17721774
17731775 # Files of directory-backed skills are looked up by exact string equality in a map built at construction,
17741776 # so request data never reaches the filesystem; a miss falls through to the routing below.
@@ -1798,7 +1800,7 @@ def read_resource(request, server_context: nil)
17981800 params = template . match_uri ( uri )
17991801 next unless params
18001802
1801- add_instrumentation_data ( resource_template : template . uri_template )
1803+ instrumentation_data . merge! ( resource_template : template . uri_template )
18021804 return normalize_resource_contents ( call_template_contents ( template , params , server_context ) )
18031805 end
18041806
@@ -1884,9 +1886,22 @@ def complete(params, session: nil, related_request_id: nil, cancellation: nil, e
18841886 # Invokes `resources/read` via the registered handler. If the handler block opts in to `server_context:`,
18851887 # pass an `MCP::ServerContext` so the handler can observe cancellation via `server_context.cancelled?` or
18861888 # `server_context.raise_if_cancelled!`.
1887- def read_resource_contents ( request , session : nil , related_request_id : nil , cancellation : nil , envelope : nil , auth_info : nil )
1889+ def read_resource_contents ( request , instrumentation_data :, session : nil , related_request_id : nil , cancellation : nil , envelope : nil , auth_info : nil )
1890+ handler = @handlers [ Methods ::RESOURCES_READ ]
1891+ if handler == method ( :read_resource )
1892+ server_context = build_server_context (
1893+ request : request ,
1894+ session : session ,
1895+ related_request_id : related_request_id ,
1896+ cancellation : cancellation ,
1897+ envelope : envelope ,
1898+ auth_info : auth_info ,
1899+ )
1900+ return read_resource ( request , instrumentation_data : instrumentation_data , server_context : server_context )
1901+ end
1902+
18881903 dispatch_optional_context_handler (
1889- @handlers [ Methods :: RESOURCES_READ ] ,
1904+ handler ,
18901905 request ,
18911906 session : session ,
18921907 related_request_id : related_request_id ,
0 commit comments