📡 fix: Handle Pre-Session 406 for Optional SSE MCP Stream (#13202)

This commit is contained in:
Peter Nancarrow 2026-05-19 19:59:45 -05:00 committed by GitHub
parent 0b5530be41
commit 2418f854df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 32 additions and 6 deletions

View file

@ -538,11 +538,15 @@ describe('MCPConnection SSE 404 handling session-aware', () => {
});
}
function fire404(conn: MCPConnection, transport: ReturnType<typeof makeTransportStub>) {
function fireSSEError(
conn: MCPConnection,
transport: ReturnType<typeof makeTransportStub>,
code = 404,
) {
(
conn as unknown as { setupTransportErrorHandlers: (t: unknown) => void }
).setupTransportErrorHandlers(transport);
const sseError = Object.assign(new Error('Failed to open SSE stream'), { code: 404 });
const sseError = Object.assign(new Error('Failed to open SSE stream'), { code });
transport.onerror?.(sseError);
}
@ -556,7 +560,7 @@ describe('MCPConnection SSE 404 handling session-aware', () => {
const transport = makeTransportStub();
const emitSpy = jest.spyOn(conn, 'emit');
fire404(conn, transport);
fireSSEError(conn, transport);
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('no session'));
expect(emitSpy).not.toHaveBeenCalledWith('connectionChange', 'error');
@ -567,7 +571,7 @@ describe('MCPConnection SSE 404 handling session-aware', () => {
const transport = makeTransportStub('existing-session-id');
const emitSpy = jest.spyOn(conn, 'emit');
fire404(conn, transport);
fireSSEError(conn, transport);
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('session lost'));
expect(emitSpy).toHaveBeenCalledWith('connectionChange', 'error');
@ -578,10 +582,32 @@ describe('MCPConnection SSE 404 handling session-aware', () => {
const transport = makeTransportStub('');
const emitSpy = jest.spyOn(conn, 'emit');
fire404(conn, transport);
fireSSEError(conn, transport);
expect(emitSpy).not.toHaveBeenCalledWith('connectionChange', 'error');
});
it('treats a 406 before session establishment as an unsupported optional SSE stream', () => {
const conn = makeConn();
const transport = makeTransportStub();
const emitSpy = jest.spyOn(conn, 'emit');
fireSSEError(conn, transport, 406);
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('no session'));
expect(emitSpy).not.toHaveBeenCalledWith('connectionChange', 'error');
});
it('falls through on a 406 when a session already exists', () => {
const conn = makeConn();
const transport = makeTransportStub('existing-session-id');
const emitSpy = jest.spyOn(conn, 'emit');
fireSSEError(conn, transport, 406);
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('session lost'));
expect(emitSpy).toHaveBeenCalledWith('connectionChange', 'error');
});
});
describe('MCPConnection SSE stream disconnect handling', () => {

View file

@ -1283,7 +1283,7 @@ export class MCPConnection extends EventEmitter {
isTransient,
} = extractSSEErrorMessage(error);
if (errorCode === 400 || errorCode === 404 || errorCode === 405) {
if (errorCode === 400 || errorCode === 404 || errorCode === 405 || errorCode === 406) {
const hasSession =
'sessionId' in transport &&
(transport as { sessionId?: string }).sessionId != null &&