@@ -146,6 +146,68 @@ def test_send_request_skips_notifications
146146 stdout_write . close
147147 end
148148
149+ def test_send_request_skips_non_object_frames
150+ stdin_read , stdin_write = IO . pipe
151+ stdout_read , stdout_write = IO . pipe
152+ stderr_read , _ = IO . pipe
153+
154+ Open3 . stubs ( :popen3 ) . returns ( [ stdin_write , stdout_read , stderr_read , mock_wait_thread ] )
155+
156+ transport = Stdio . new ( command : "ruby" , args : [ "server.rb" ] )
157+
158+ request = {
159+ jsonrpc : "2.0" ,
160+ id : "test-id" ,
161+ method : "tools/list" ,
162+ }
163+
164+ server_thread = Thread . new do
165+ init_line = stdin_read . gets
166+ init_request = JSON . parse ( init_line )
167+ stdout_write . puts ( JSON . generate ( {
168+ jsonrpc : "2.0" ,
169+ id : init_request [ "id" ] ,
170+ result : {
171+ protocolVersion : "2025-11-25" ,
172+ capabilities : { } ,
173+ serverInfo : { name : "test-server" , version : "1.0.0" } ,
174+ } ,
175+ } ) )
176+ stdout_write . flush
177+
178+ # Read initialized notification
179+ stdin_read . gets
180+
181+ # Read tools/list request
182+ stdin_read . gets
183+
184+ # Non-object frames are skipped like any other non-matching frame.
185+ stdout_write . puts ( "[[]]" )
186+ stdout_write . puts ( "null" )
187+ stdout_write . flush
188+
189+ # Then send the actual response
190+ stdout_write . puts ( JSON . generate ( {
191+ jsonrpc : "2.0" ,
192+ id : "test-id" ,
193+ result : { tools : [ ] } ,
194+ } ) )
195+ stdout_write . flush
196+ end
197+
198+ transport . connect
199+ response = transport . send_request ( request : request )
200+
201+ assert_equal ( "test-id" , response [ "id" ] )
202+ assert_empty ( response . dig ( "result" , "tools" ) )
203+ ensure
204+ server_thread . join
205+ stdin_read . close
206+ stdin_write . close
207+ stdout_read . close
208+ stdout_write . close
209+ end
210+
149211 def test_send_request_raises_error_when_process_exits
150212 stdin_read , stdin_write = IO . pipe
151213 stdout_read , stdout_write = IO . pipe
0 commit comments