@@ -944,6 +944,78 @@ describe('StreamableHTTPClientTransport', () => {
944944 expect ( fetchMock . mock . calls [ 0 ] ! [ 1 ] ?. method ) . toBe ( 'POST' ) ;
945945 } ) ;
946946
947+ it ( 'should NOT reconnect a POST stream when error response was received' , async ( ) => {
948+ // ARRANGE
949+ transport = new StreamableHTTPClientTransport ( new URL ( 'http://localhost:1234/mcp' ) , {
950+ reconnectionOptions : {
951+ initialReconnectionDelay : 10 ,
952+ maxRetries : 1 ,
953+ maxReconnectionDelay : 1000 ,
954+ reconnectionDelayGrowFactor : 1
955+ }
956+ } ) ;
957+
958+ const messageSpy = vi . fn ( ) ;
959+ transport . onmessage = messageSpy ;
960+
961+ // Create a stream that sends:
962+ // 1. Priming event with ID (enables potential reconnection)
963+ // 2. An error response (should also prevent reconnection, just like success)
964+ // 3. Then closes
965+ const streamWithErrorResponse = new ReadableStream ( {
966+ start ( controller ) {
967+ // Priming event with ID
968+ controller . enqueue ( new TextEncoder ( ) . encode ( 'id: priming-123\ndata: \n\n' ) ) ;
969+ // An error response to the request (tool not found, for example)
970+ controller . enqueue (
971+ new TextEncoder ( ) . encode (
972+ 'id: error-456\ndata: {"jsonrpc":"2.0","error":{"code":-32602,"message":"Tool not found"},"id":"request-1"}\n\n'
973+ )
974+ ) ;
975+ // Stream closes normally
976+ controller . close ( ) ;
977+ }
978+ } ) ;
979+
980+ const fetchMock = global . fetch as Mock ;
981+ fetchMock . mockResolvedValueOnce ( {
982+ ok : true ,
983+ status : 200 ,
984+ headers : new Headers ( { 'content-type' : 'text/event-stream' } ) ,
985+ body : streamWithErrorResponse
986+ } ) ;
987+
988+ const requestMessage : JSONRPCRequest = {
989+ jsonrpc : '2.0' ,
990+ method : 'tools/call' ,
991+ id : 'request-1' ,
992+ params : { name : 'nonexistent-tool' }
993+ } ;
994+
995+ // ACT
996+ await transport . start ( ) ;
997+ await transport . send ( requestMessage ) ;
998+ await vi . advanceTimersByTimeAsync ( 50 ) ;
999+
1000+ // ASSERT
1001+ // THE KEY ASSERTION: Fetch was called ONCE only - no reconnection!
1002+ // The error response was received, so no need to reconnect.
1003+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
1004+ expect ( fetchMock . mock . calls [ 0 ] ! [ 1 ] ?. method ) . toBe ( 'POST' ) ;
1005+
1006+ // Verify the error response was delivered to the message handler
1007+ expect ( messageSpy ) . toHaveBeenCalledWith (
1008+ expect . objectContaining ( {
1009+ jsonrpc : '2.0' ,
1010+ error : expect . objectContaining ( {
1011+ code : - 32602 ,
1012+ message : 'Tool not found'
1013+ } ) ,
1014+ id : 'request-1'
1015+ } )
1016+ ) ;
1017+ } ) ;
1018+
9471019 it ( 'should not attempt reconnection after close() is called' , async ( ) => {
9481020 // ARRANGE
9491021 transport = new StreamableHTTPClientTransport ( new URL ( 'http://localhost:1234/mcp' ) , {
0 commit comments