@@ -40,6 +40,35 @@ import {
4040const CLOUDFLARE_SERVER_ID = 'cloudflare-server' ;
4141const STUB_HTTP_PORT = 3457 ;
4242
43+ /**
44+ * Parse an MCP Streamable HTTP response that may be either JSON or SSE format.
45+ *
46+ * Per the MCP spec (2025-03-26), when a POST contains JSON-RPC requests, the
47+ * server MUST respond with either `Content-Type: application/json` (single JSON
48+ * object) or `Content-Type: text/event-stream` (SSE stream). The client MUST
49+ * support both cases.
50+ *
51+ * SSE responses contain `data:` lines with JSON-RPC messages. We extract the
52+ * first JSON-RPC response message from the stream.
53+ */
54+ function parseMcpResponse < T > ( contentType : string | null , responseText : string ) : T {
55+ if ( contentType ?. includes ( 'text/event-stream' ) ) {
56+ // Parse SSE: extract JSON from `data:` lines
57+ const lines = responseText . split ( '\n' ) ;
58+ for ( const line of lines ) {
59+ if ( line . startsWith ( 'data:' ) ) {
60+ const data = line . slice ( 'data:' . length ) . trim ( ) ;
61+ if ( data ) {
62+ return JSON . parse ( data ) as T ;
63+ }
64+ }
65+ }
66+ throw new Error ( `No data events found in SSE response: ${ responseText . substring ( 0 , 500 ) } ` ) ;
67+ }
68+ // Default: parse as plain JSON
69+ return JSON . parse ( responseText ) as T ;
70+ }
71+
4372// ============================================================================
4473// Test Suite: Streamable HTTP Transport & Notifications
4574// ============================================================================
@@ -372,6 +401,7 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
372401 } ) ;
373402
374403 console . log ( '[test] Initialize response status:' , res . status , res . statusText ) ;
404+ console . log ( '[test] Initialize response content-type:' , res . headers . get ( 'content-type' ) ) ;
375405 const responseText = await res . text ( ) ;
376406 console . log ( '[test] Initialize response body:' , responseText . substring ( 0 , 1000 ) ) ;
377407
@@ -381,7 +411,8 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
381411 }
382412 expect ( res . status ) . toBeLessThan ( 400 ) ;
383413
384- const body = JSON . parse ( responseText ) as {
414+ // The server may respond with JSON or SSE (per MCP Streamable HTTP spec)
415+ const body = parseMcpResponse < {
385416 jsonrpc : string ;
386417 id : number ;
387418 result ?: {
@@ -393,7 +424,7 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
393424 } ;
394425 serverInfo : { name : string ; version : string } ;
395426 } ;
396- } ;
427+ } > ( res . headers . get ( 'content-type' ) , responseText ) ;
397428
398429 console . log ( '[test] Initialize result:' , JSON . stringify ( body ) ) ;
399430
@@ -447,13 +478,17 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
447478 } ) ;
448479
449480 console . log ( '[test] Session init status:' , initRes . status , initRes . statusText ) ;
481+ console . log ( '[test] Session init content-type:' , initRes . headers . get ( 'content-type' ) ) ;
450482 const initText = await initRes . text ( ) ;
451483 console . log ( '[test] Session init body:' , initText . substring ( 0 , 1000 ) ) ;
452484 if ( ! initRes . ok ) {
453485 console . log ( '[test] FAILURE: /mcp returned' , initRes . status , '- body:' , initText ) ;
454486 }
455487 expect ( initRes . status ) . toBeLessThan ( 400 ) ;
456488
489+ // Parse the response (may be JSON or SSE per spec)
490+ parseMcpResponse < { jsonrpc : string ; id : number } > ( initRes . headers . get ( 'content-type' ) , initText ) ;
491+
457492 // Check for Mcp-Session-Id in response headers
458493 const sessionId = initRes . headers . get ( 'mcp-session-id' ) ;
459494 console . log ( '[test] Session ID:' , sessionId ) ;
@@ -496,9 +531,12 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
496531 } ) ;
497532
498533 expect ( toolsRes . ok ) . toBe ( true ) ;
499- const toolsBody = await toolsRes . json ( ) as {
534+ console . log ( '[test] Tools response content-type:' , toolsRes . headers . get ( 'content-type' ) ) ;
535+ const toolsText = await toolsRes . text ( ) ;
536+ console . log ( '[test] Tools response body:' , toolsText . substring ( 0 , 1000 ) ) ;
537+ const toolsBody = parseMcpResponse < {
500538 result ?: { tools : Array < { name : string ; description ?: string } > } ;
501- } ;
539+ } > ( toolsRes . headers . get ( 'content-type' ) , toolsText ) ;
502540
503541 console . log ( '[test] Tools count:' , toolsBody . result ?. tools ?. length ?? 0 ) ;
504542 if ( toolsBody . result ?. tools && toolsBody . result . tools . length > 0 ) {
0 commit comments