3939# Shared SSE parse helper
4040# ---------------------------------------------------------------------------
4141module SSETestHelpers
42- # Parse raw SSE text (may span many chunks) into [{event:, data:}] hashes.
42+ # Parse raw SSE text (may span many chunks) into [{event:, data:, kind:}].
43+ #
44+ # `event:` is always the wire-mandated "message"; `kind:` is the
45+ # payload-derived classification (see #sse_kind) that tests assert on,
46+ # mirroring how a real MCP client discriminates frames.
4347 def parse_sse ( raw )
4448 events = [ ]
4549 raw . scan ( /event:\s *(\S +)\r ?\n data:\s *(.+?)(?=\r ?\n \r ?\n |\z )/m ) do |event , data |
46- events << { event : event . strip , data : data . strip }
50+ clean = data . strip
51+ events << { event : event . strip , data : clean , kind : sse_kind ( clean ) }
4752 end
4853 events
4954 end
5055
56+ # Classify an SSE frame the way an MCP client does — from the envelope.
57+ def sse_kind ( data )
58+ payload = begin
59+ JSON . parse ( data )
60+ rescue StandardError
61+ nil
62+ end
63+ return :unknown unless payload . is_a? ( Hash )
64+ return :progress if payload [ "method" ] == "notifications/progress"
65+ return :notification if payload . key? ( "method" )
66+ return :response if payload . key? ( "id" ) && ( payload . key? ( "result" ) || payload . key? ( "error" ) )
67+ :unknown
68+ end
69+
5170 # Collect all streamed chunks from a Net::HTTP response into a single String.
5271 def collect_sse_body ( response )
5372 buf = +""
@@ -261,7 +280,7 @@ def test_at_least_two_progress_events_arrive_for_slow_tool
261280 @@dispatch_delay = 0.6
262281
263282 events , _headers , _raw = sse_post ( tools_call_body )
264- progress_events = events . select { |e | e [ :event ] == " progress" }
283+ progress_events = events . select { |e | e [ :kind ] == : progress }
265284
266285 assert progress_events . size >= 2 ,
267286 "Expected >=2 progress events for 0.6s tool; got #{ progress_events . size } "
@@ -273,7 +292,7 @@ def test_progress_events_have_jsonrpc_notification_shape
273292 @@dispatch_delay = 0.6
274293
275294 events , _headers , _raw = sse_post ( tools_call_body )
276- progress_events = events . select { |e | e [ :event ] == " progress" }
295+ progress_events = events . select { |e | e [ :kind ] == : progress }
277296
278297 assert progress_events . size >= 1 , "Need at least one progress event to check shape"
279298
@@ -300,7 +319,7 @@ def test_progress_token_from_client_params_echoed_in_events
300319 @@dispatch_delay = 0.6
301320
302321 events , _headers , _raw = sse_post ( tools_call_body ( progress_token : token ) )
303- progress_events = events . select { |e | e [ :event ] == " progress" }
322+ progress_events = events . select { |e | e [ :kind ] == : progress }
304323
305324 assert progress_events . size >= 1 , "Need progress events to verify token"
306325
@@ -322,7 +341,7 @@ def test_exactly_one_response_event_in_stream
322341 @@dispatch_delay = 0.3
323342
324343 events , _headers , _raw = sse_post ( tools_call_body ( id : 42 ) )
325- response_events = events . select { |e | e [ :event ] == " response" }
344+ response_events = events . select { |e | e [ :kind ] == : response }
326345
327346 assert_equal 1 , response_events . size ,
328347 "Expected exactly one response event, got #{ response_events . size } "
@@ -332,7 +351,7 @@ def test_response_event_contains_valid_jsonrpc_envelope
332351 @@dispatch_delay = 0.15
333352
334353 events , _headers , _raw = sse_post ( tools_call_body ( id : 99 ) )
335- response_event = events . find { |e | e [ :event ] == " response" }
354+ response_event = events . find { |e | e [ :kind ] == : response }
336355 refute_nil response_event , "No response event found in SSE stream"
337356
338357 data = JSON . parse ( response_event [ :data ] )
@@ -353,7 +372,7 @@ def test_response_event_matches_plain_json_result
353372 @@dispatch_delay = 0
354373
355374 events , _headers , _raw = sse_post ( tools_call_body ( id : 7 ) )
356- response_event = events . find { |e | e [ :event ] == " response" }
375+ response_event = events . find { |e | e [ :kind ] == : response }
357376 refute_nil response_event
358377
359378 data = JSON . parse ( response_event [ :data ] )
@@ -520,8 +539,8 @@ def test_multiple_concurrent_sse_connections
520539 Thread . new do
521540 events , _h , _r = sse_post ( tools_call_body ( id : 100 + i ) )
522541 results [ i ] = {
523- progress : events . count { |e | e [ :event ] == " progress" } ,
524- response : events . count { |e | e [ :event ] == " response" } ,
542+ progress : events . count { |e | e [ :kind ] == : progress } ,
543+ response : events . count { |e | e [ :kind ] == : response } ,
525544 }
526545 end
527546 end
@@ -545,8 +564,10 @@ def test_response_event_is_last_in_stream
545564
546565 refute events . empty? , "Should have received at least one event"
547566 last_event = events . last
548- assert_equal "response" , last_event [ :event ] ,
549- "Final event must be 'response', got '#{ last_event [ :event ] } '"
567+ assert_equal :response , last_event [ :kind ] ,
568+ "Final frame must carry the JSON-RPC response envelope, got #{ last_event [ :kind ] . inspect } "
569+ assert_equal "message" , last_event [ :event ] ,
570+ "Final frame must use the wire event name 'message', got '#{ last_event [ :event ] } '"
550571 end
551572
552573 # ---------------------------------------------------------------------------
@@ -558,8 +579,8 @@ def test_progress_events_precede_response_event
558579
559580 events , _headers , _raw = sse_post ( tools_call_body )
560581
561- progress_indices = events . each_index . select { |i | events [ i ] [ :event ] == " progress" }
562- response_index = events . each_index . find { |i | events [ i ] [ :event ] == " response" }
582+ progress_indices = events . each_index . select { |i | events [ i ] [ :kind ] == : progress }
583+ response_index = events . each_index . find { |i | events [ i ] [ :kind ] == : response }
563584
564585 refute_nil response_index , "No response event found"
565586 assert progress_indices . size >= 1 , "Expected at least one progress event before response"
@@ -580,7 +601,7 @@ def test_auto_generated_progress_token_is_non_empty_string
580601 # Do NOT supply a progressToken in the request
581602 events , _headers , _raw = sse_post ( tools_call_body )
582603
583- progress_events = events . select { |e | e [ :event ] == " progress" }
604+ progress_events = events . select { |e | e [ :kind ] == : progress }
584605 assert progress_events . size >= 1 , "Need at least one progress event"
585606
586607 token = JSON . parse ( progress_events . first [ :data ] ) . dig ( "params" , "progressToken" )
0 commit comments