@@ -3156,6 +3156,16 @@ def call(numbers:, strings:, objects:, server_context: nil)
31563156 end
31573157 end
31583158
3159+ class JsonObjectValue
3160+ def initialize ( value )
3161+ @value = value
3162+ end
3163+
3164+ def to_json ( *args )
3165+ @value . to_json ( *args )
3166+ end
3167+ end
3168+
31593169 test "server_context_with_meta uses accessor method, not ivar directly" do
31603170 subclass = Class . new ( Server ) do
31613171 def server_context
@@ -3247,23 +3257,28 @@ def server_context
32473257 end
32483258 end
32493259
3250- test "#handle tools/call mirrors non-object structuredContent into serialized text content " do
3260+ test "#handle tools/call mirrors non-object structuredContent when content is omitted or nil " do
32513261 # Per SEP-2106, `structuredContent` may be any JSON value. Older clients may only read `content`,
32523262 # so the server adds a serialized fallback when the tool provided no content blocks.
32533263 server = Server . new ( name : "structured_test" , tools : [ ] )
3254- server . define_tool ( name : "array_tool " ) do
3264+ server . define_tool ( name : "nil_content_tool " ) do
32553265 Tool ::Response . new ( nil , structured_content : [ 1 , 2 ] )
32563266 end
3267+ server . define_tool ( name : "omitted_content_tool" ) do
3268+ Tool ::Response . new ( structured_content : [ 1 , 2 ] )
3269+ end
32573270
3258- response = server . handle ( {
3259- jsonrpc : "2.0" ,
3260- method : "tools/call" ,
3261- id : 1 ,
3262- params : { name : "array_tool" , arguments : { } } ,
3263- } )
3271+ [ "nil_content_tool" , "omitted_content_tool" ] . each_with_index do |tool_name , index |
3272+ response = server . handle ( {
3273+ jsonrpc : "2.0" ,
3274+ method : "tools/call" ,
3275+ id : index + 1 ,
3276+ params : { name : tool_name , arguments : { } } ,
3277+ } )
32643278
3265- assert_equal [ 1 , 2 ] , response . dig ( :result , :structuredContent )
3266- assert_equal [ { type : "text" , text : "[1,2]" } ] , response . dig ( :result , :content )
3279+ assert_equal [ 1 , 2 ] , response . dig ( :result , :structuredContent )
3280+ assert_equal [ { type : "text" , text : "[1,2]" } ] , response . dig ( :result , :content )
3281+ end
32673282 end
32683283
32693284 test "#handle tools/call does not overwrite explicit content when structuredContent is non-object" do
@@ -3299,6 +3314,60 @@ def server_context
32993314 assert_empty response . dig ( :result , :content )
33003315 end
33013316
3317+ test "#handle tools/call recognizes object-like structuredContent by its JSON shape" do
3318+ structured_content = JsonObjectValue . new ( answer : 42 )
3319+ server = Server . new ( name : "structured_test" , tools : [ ] )
3320+ server . define_tool ( name : "object_tool" ) do
3321+ Tool ::Response . new ( nil , structured_content : structured_content )
3322+ end
3323+
3324+ response = server . handle ( {
3325+ jsonrpc : "2.0" ,
3326+ method : "tools/call" ,
3327+ id : 1 ,
3328+ params : { name : "object_tool" , arguments : { } } ,
3329+ } )
3330+
3331+ assert_equal structured_content , response . dig ( :result , :structuredContent )
3332+ assert_empty response . dig ( :result , :content )
3333+ end
3334+
3335+ test "#handle tools/call preserves explicit empty content for non-object structuredContent" do
3336+ server = Server . new ( name : "structured_test" , tools : [ ] )
3337+ server . define_tool ( name : "array_tool" ) do
3338+ Tool ::Response . new ( [ ] , structured_content : [ 1 , 2 ] )
3339+ end
3340+
3341+ response = server . handle ( {
3342+ jsonrpc : "2.0" ,
3343+ method : "tools/call" ,
3344+ id : 1 ,
3345+ params : { name : "array_tool" , arguments : { } } ,
3346+ } )
3347+
3348+ assert_equal [ 1 , 2 ] , response . dig ( :result , :structuredContent )
3349+ assert_empty response . dig ( :result , :content )
3350+ end
3351+
3352+ test "#handle tools/call preserves explicit empty content for object-like structuredContent" do
3353+ structured_content = JsonObjectValue . new ( answer : 42 )
3354+
3355+ server = Server . new ( name : "structured_test" , tools : [ ] )
3356+ server . define_tool ( name : "object_tool" ) do
3357+ Tool ::Response . new ( [ ] , structured_content : structured_content )
3358+ end
3359+
3360+ response = server . handle ( {
3361+ jsonrpc : "2.0" ,
3362+ method : "tools/call" ,
3363+ id : 1 ,
3364+ params : { name : "object_tool" , arguments : { } } ,
3365+ } )
3366+
3367+ assert_equal structured_content , response . dig ( :result , :structuredContent )
3368+ assert_empty response . dig ( :result , :content )
3369+ end
3370+
33023371 test "list results carry ttlMs and cacheScope when ttl_ms is configured" do
33033372 # SEP-2549 cache hints. The cacheScope default is "public", matching
33043373 # the spec default and the Python SDK.
0 commit comments