@@ -463,19 +463,25 @@ def completion_handler(&block)
463463 end
464464
465465 # Sets a custom handler for `resources/subscribe` requests.
466- # The block receives the parsed request params. The return value is
467- # ignored; the response is always an empty result `{}` per the MCP specification.
466+ # The block receives the parsed request params. The response is an empty result, except that
467+ # a `_meta` hash the block returns is passed through - the spec defines no other member for this result,
468+ # so any other field the block returns is dropped. Nest a subscription identifier or other advisory data
469+ # under `_meta`.
468470 #
469471 # @yield [params] The request params containing `:uri`.
472+ # @yieldreturn [Hash, nil] Optionally `{ _meta: { ... } }`; any other shape yields an empty result.
470473 def resources_subscribe_handler ( &block )
471474 @handlers [ Methods ::RESOURCES_SUBSCRIBE ] = block
472475 end
473476
474477 # Sets a custom handler for `resources/unsubscribe` requests.
475- # The block receives the parsed request params. The return value is
476- # ignored; the response is always an empty result `{}` per the MCP specification.
478+ # The block receives the parsed request params. The response is an empty result, except that
479+ # a `_meta` hash the block returns is passed through - the spec defines no other member for this result,
480+ # so any other field the block returns is dropped. Nest a subscription identifier or other advisory data
481+ # under `_meta`.
477482 #
478483 # @yield [params] The request params containing `:uri`.
484+ # @yieldreturn [Hash, nil] Optionally `{ _meta: { ... } }`; any other shape yields an empty result.
479485 def resources_unsubscribe_handler ( &block )
480486 @handlers [ Methods ::RESOURCES_UNSUBSCRIBE ] = block
481487 end
@@ -686,8 +692,9 @@ def handle_request(request, method, session: nil, related_request_id: nil)
686692 contents . is_a? ( InputRequiredResult ) ? contents : build_read_resource_result ( contents )
687693 when Methods ::RESOURCES_SUBSCRIBE , Methods ::RESOURCES_UNSUBSCRIBE
688694 validate_resource_subscription_params! ( params )
689- dispatch_optional_context_handler ( @handlers [ method ] , params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
690- { }
695+ handler_result = dispatch_optional_context_handler ( @handlers [ method ] , params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
696+
697+ subscription_result ( handler_result )
691698 when Methods ::TOOLS_CALL
692699 call_tool ( params , session : session , related_request_id : related_request_id , cancellation : cancellation , envelope : envelope )
693700 when Methods ::PROMPTS_GET
@@ -1116,6 +1123,18 @@ def validate_resource_subscription_params!(params)
11161123 end
11171124 end
11181125
1126+ # The `resources/subscribe` and `resources/unsubscribe` result is an empty object except for the optional `_meta`
1127+ # every result may carry: the TypeScript SDK validates it against `EmptyResultSchema.strict()`,
1128+ # which rejects any other member, so only `_meta` is passed through from the handler. A handler that returns
1129+ # anything else keeps the empty `{}` result it had before, so returning a subscription identifier or
1130+ # other advisory data means nesting it under `_meta`.
1131+ def subscription_result ( handler_result )
1132+ return { } unless handler_result . is_a? ( Hash )
1133+
1134+ meta = handler_result [ :_meta ] || handler_result [ "_meta" ]
1135+ meta . is_a? ( Hash ) ? { _meta : meta } : { }
1136+ end
1137+
11191138 def validate_initialize_params! ( params )
11201139 unless params . is_a? ( Hash )
11211140 raise RequestHandlerError . new ( "Invalid params" , params , error_type : :invalid_params )
0 commit comments