diff --git a/.changeset/web-standard-connection-policy.md b/.changeset/web-standard-connection-policy.md new file mode 100644 index 0000000000..0df5da2ac8 --- /dev/null +++ b/.changeset/web-standard-connection-policy.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/server': patch +--- + +Leave hop-by-hop connection policy for Streamable HTTP SSE responses to the HTTP adapter. diff --git a/packages/server/src/server/streamableHttp.ts b/packages/server/src/server/streamableHttp.ts index 7da5fb853c..c63b5537fe 100644 --- a/packages/server/src/server/streamableHttp.ts +++ b/packages/server/src/server/streamableHttp.ts @@ -480,8 +480,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { const headers: Record = { 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache, no-transform', - Connection: 'keep-alive' + 'Cache-Control': 'no-cache, no-transform' }; // After initialization, always include the session ID if we have one @@ -536,8 +535,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { const headers: Record = { 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache, no-transform', - Connection: 'keep-alive' + 'Cache-Control': 'no-cache, no-transform' }; if (this.sessionId !== undefined) { @@ -837,8 +835,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { const headers: Record = { 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - Connection: 'keep-alive' + 'Cache-Control': 'no-cache' }; // After initialization, always include the session ID if we have one diff --git a/packages/server/test/server/streamableHttp.test.ts b/packages/server/test/server/streamableHttp.test.ts index beca451113..27ed23e812 100644 --- a/packages/server/test/server/streamableHttp.test.ts +++ b/packages/server/test/server/streamableHttp.test.ts @@ -166,6 +166,7 @@ describe('Zod v4', () => { expect(response.status).toBe(200); expect(response.headers.get('content-type')).toBe('text/event-stream'); expect(response.headers.get('mcp-session-id')).toBeDefined(); + expect(response.headers.get('connection'), 'connection policy belongs to the HTTP adapter').toBeNull(); }); it('should reject second initialization request', async () => { @@ -358,6 +359,7 @@ describe('Zod v4', () => { expect(response.status).toBe(200); expect(response.headers.get('content-type')).toBe('text/event-stream'); expect(response.headers.get('mcp-session-id')).toBe(sessionId); + expect(response.headers.get('connection'), 'connection policy belongs to the HTTP adapter').toBeNull(); }); it('should reject GET without Accept: text/event-stream', async () => { @@ -857,6 +859,7 @@ describe('Zod v4', () => { createRequest('GET', undefined, { sessionId, extraHeaders: { 'Last-Event-ID': primingId! } }) ); expect(reconnect.status).toBe(200); + expect(reconnect.headers.get('connection'), 'connection policy belongs to the HTTP adapter').toBeNull(); release(); const replayed = await readSSEEvent(reconnect); expect(replayed).toContain('notifications/progress');