@@ -29,7 +29,14 @@ export class JsonHTTPServerTransport implements Transport {
2929 public onerror ?: ( error : Error ) => void ;
3030 public onmessage ?: (
3131 message : JSONRPCMessage ,
32- extra ?: { requestInfo ?: { headers : IncomingMessage [ 'headers' ] } ; authInfo ?: unknown } ,
32+ extra ?: {
33+ requestInfo ?: {
34+ headers : IncomingMessage [ 'headers' ] ;
35+ method ?: string ;
36+ transport ?: 'http' | 'sse' ;
37+ } ;
38+ authInfo ?: unknown ;
39+ } ,
3340 ) => void ;
3441
3542 private started = false ;
@@ -70,43 +77,45 @@ export class JsonHTTPServerTransport implements Transport {
7077 }
7178
7279 async send ( message : JSONRPCMessage , options ?: TransportSendOptions ) : Promise < void > {
73- // If SSE is connected, stream all server -> client messages via SSE
74- if ( this . sse && ! this . sse . res . writableEnded ) {
75- const line = `data: ${ JSON . stringify ( message ) } \n\n` ;
76- this . sse . res . write ( line ) ;
77- return ;
78- }
79-
8080 let relatedId = options ?. relatedRequestId ;
8181 if ( isJSONRPCResponse ( message ) || isJSONRPCError ( message ) ) {
8282 relatedId = message . id ;
8383 }
84- if ( relatedId === undefined ) {
85- // No place to send notifications/responses without a related request in JSON-only mode
86- return ;
87- }
88- const connId = this . requestIdToConn . get ( relatedId ) ;
89- if ( ! connId ) throw new Error ( `No HTTP connection for request ${ String ( relatedId ) } ` ) ;
90- const ctx = this . connections . get ( connId ) ;
91- if ( ! ctx ) throw new Error ( `HTTP connection closed for request ${ String ( relatedId ) } ` ) ;
84+ // If a related HTTP connection is pending, complete it with JSON
85+ if ( relatedId !== undefined ) {
86+ const connId = this . requestIdToConn . get ( relatedId ) ;
87+ if ( connId ) {
88+ const ctx = this . connections . get ( connId ) ;
89+ if ( ! ctx ) throw new Error ( `HTTP connection closed for request ${ String ( relatedId ) } ` ) ;
90+
91+ ctx . responses . set ( relatedId , message ) ;
92+ const allReady = ctx . orderedIds . every ( ( id ) => ctx . responses . has ( id ) ) ;
93+ if ( ! allReady ) return ;
94+
95+ const body =
96+ ctx . orderedIds . length === 1
97+ ? ctx . responses . get ( ctx . orderedIds [ 0 ] ! )
98+ : ctx . orderedIds . map ( ( id ) => ctx . responses . get ( id ) ) ;
9299
93- ctx . responses . set ( relatedId , message ) ;
94- // When all responses for this HTTP request are ready, flush JSON and end
95- const allReady = ctx . orderedIds . every ( ( id ) => ctx . responses . has ( id ) ) ;
96- if ( ! allReady ) return ;
100+ const headers : Record < string , string > = { 'Content-Type' : 'application/json' } ;
101+ ctx . res . writeHead ( 200 , headers ) ;
102+ ctx . res . end ( JSON . stringify ( body ) ) ;
97103
98- const body =
99- ctx . orderedIds . length === 1
100- ? ctx . responses . get ( ctx . orderedIds [ 0 ] ! )
101- : ctx . orderedIds . map ( ( id ) => ctx . responses . get ( id ) ) ;
104+ this . connections . delete ( connId ) ;
105+ ctx . orderedIds . forEach ( ( id ) => this . requestIdToConn . delete ( id ) ) ;
106+ return ;
107+ }
108+ }
102109
103- const headers : Record < string , string > = { 'Content-Type' : 'application/json' } ;
104- ctx . res . writeHead ( 200 , headers ) ;
105- ctx . res . end ( JSON . stringify ( body ) ) ;
110+ // Otherwise, if SSE is connected, stream server -> client messages via SSE
111+ if ( this . sse && ! this . sse . res . writableEnded ) {
112+ const line = `data: ${ JSON . stringify ( message ) } \n\n` ;
113+ this . sse . res . write ( line ) ;
114+ return ;
115+ }
106116
107- // Cleanup
108- this . connections . delete ( connId ) ;
109- ctx . orderedIds . forEach ( ( id ) => this . requestIdToConn . delete ( id ) ) ;
117+ // No pending HTTP response and no SSE: drop notifications without a target
118+ return ;
110119 }
111120
112121 // Handle HTTP requests: supports POST for JSON and GET for SSE
@@ -240,6 +249,8 @@ export class JsonHTTPServerTransport implements Transport {
240249 this . onmessage ?.( msg , {
241250 requestInfo : {
242251 headers : req . headers ,
252+ method : req . method ,
253+ transport : 'http' ,
243254 } ,
244255 authInfo : req . auth ,
245256 } ) ;
@@ -250,13 +261,17 @@ export class JsonHTTPServerTransport implements Transport {
250261
251262 const orderedIds : RequestId [ ] = messages . filter ( isJSONRPCRequest ) . map ( ( m ) => m . id ) ;
252263 const sseConnected = ! ! this . sse && ! this . sse . res . writableEnded ;
253- if ( sseConnected ) {
264+ // Prefer JSON response when client explicitly accepts JSON; use SSE only when
265+ // Accept doesn't include JSON and an SSE stream is connected
266+ if ( sseConnected && ! acceptsJson ) {
254267 // With SSE, we emit responses on the SSE stream; reply 202 to POST immediately
255268 res . writeHead ( 202 ) . end ( ) ;
256269 for ( const msg of messages ) {
257270 this . onmessage ?.( msg , {
258271 requestInfo : {
259272 headers : req . headers ,
273+ method : req . method ,
274+ transport : 'sse' ,
260275 } ,
261276 authInfo : req . auth ,
262277 } ) ;
@@ -283,6 +298,8 @@ export class JsonHTTPServerTransport implements Transport {
283298 this . onmessage ?.( msg , {
284299 requestInfo : {
285300 headers : req . headers ,
301+ method : req . method ,
302+ transport : 'http' ,
286303 } ,
287304 authInfo : req . auth ,
288305 } ) ;
0 commit comments