From 4729c877083edf7bc6b73ab32961dc5bbdecc6d5 Mon Sep 17 00:00:00 2001 From: Adam Boudjemaa Date: Fri, 6 Mar 2026 03:56:43 +0100 Subject: [PATCH 1/2] fix(transport): handle 404 and 406 for GET SSE streams When a server returns 404 or 406 for SSE GET requests, the client should fall back gracefully instead of only handling 405. Fixes #1635 --- packages/client/src/client/streamableHttp.ts | 5 +- .../client/test/client/streamableHttp.test.ts | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/packages/client/src/client/streamableHttp.ts b/packages/client/src/client/streamableHttp.ts index 79a20adfc..b4fbf6162 100644 --- a/packages/client/src/client/streamableHttp.ts +++ b/packages/client/src/client/streamableHttp.ts @@ -236,8 +236,9 @@ export class StreamableHTTPClientTransport implements Transport { } // 405 indicates that the server does not offer an SSE stream at GET endpoint - // This is an expected case that should not trigger an error - if (response.status === 405) { + // 404 indicates the endpoint does not exist, 406 indicates the content type is not accepted + // All three are expected cases that should not trigger an error + if (response.status === 405 || response.status === 404 || response.status === 406) { return; } diff --git a/packages/client/test/client/streamableHttp.test.ts b/packages/client/test/client/streamableHttp.test.ts index 8a550feae..1dd123691 100644 --- a/packages/client/test/client/streamableHttp.test.ts +++ b/packages/client/test/client/streamableHttp.test.ts @@ -282,6 +282,70 @@ describe('StreamableHTTPClientTransport', () => { expect(globalThis.fetch).toHaveBeenCalledTimes(2); }); + it('should attempt initial GET connection and handle 404 gracefully', async () => { + // Mock the server returning 404 for the SSE GET endpoint (endpoint does not exist) + (globalThis.fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 404, + statusText: 'Not Found' + }); + + // We expect the 404 error to be caught and handled gracefully + // This should not throw an error that breaks the transport + await transport.start(); + await expect(transport['_startOrAuthSse']({})).resolves.not.toThrow('Failed to open SSE stream: Not Found'); + // Check that GET was attempted + expect(globalThis.fetch).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + method: 'GET', + headers: expect.any(Headers) + }) + ); + + // Verify transport still works after 404 + (globalThis.fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 202, + headers: new Headers() + }); + + await transport.send({ jsonrpc: '2.0', method: 'test', params: {} } as JSONRPCMessage); + expect(globalThis.fetch).toHaveBeenCalledTimes(2); + }); + + it('should attempt initial GET connection and handle 406 gracefully', async () => { + // Mock the server returning 406 for the SSE GET endpoint (content type not acceptable) + (globalThis.fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 406, + statusText: 'Not Acceptable' + }); + + // We expect the 406 error to be caught and handled gracefully + // This should not throw an error that breaks the transport + await transport.start(); + await expect(transport['_startOrAuthSse']({})).resolves.not.toThrow('Failed to open SSE stream: Not Acceptable'); + // Check that GET was attempted + expect(globalThis.fetch).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + method: 'GET', + headers: expect.any(Headers) + }) + ); + + // Verify transport still works after 406 + (globalThis.fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 202, + headers: new Headers() + }); + + await transport.send({ jsonrpc: '2.0', method: 'test', params: {} } as JSONRPCMessage); + expect(globalThis.fetch).toHaveBeenCalledTimes(2); + }); + it('should handle successful initial GET connection for SSE', async () => { // Set up readable stream for SSE events const encoder = new TextEncoder(); From c8e861a650d3f1b933636a8d7cb2917e1537e29b Mon Sep 17 00:00:00 2001 From: Adam Boudjemaa Date: Fri, 6 Mar 2026 04:35:08 +0100 Subject: [PATCH 2/2] chore: add changeset for 404/406 SSE handling Co-Authored-By: Claude Opus 4.6 --- .changeset/stale-mayflies-beam.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/stale-mayflies-beam.md diff --git a/.changeset/stale-mayflies-beam.md b/.changeset/stale-mayflies-beam.md new file mode 100644 index 000000000..7b2234ba9 --- /dev/null +++ b/.changeset/stale-mayflies-beam.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/client': patch +--- + +Handle 404 and 406 status codes gracefully for GET SSE streams, matching existing 405 behavior