Skip to content

Commit 12d7d5b

Browse files
czlonkowskiclaude
andauthored
fix: resolve SSE reconnection loop with separate /sse + /messages endpoints (v2.46.1) (czlonkowski#699)
Fix SSE clients entering rapid reconnection loops because POST /mcp never routed messages to SSEServerTransport.handlePostMessage() (czlonkowski#617). Root cause: SSE sessions were stored in a separate `this.session` property invisible to the StreamableHTTP POST handler. The POST handler only checked `this.transports` (StreamableHTTP map), so SSE messages were never delivered, causing immediate reconnection and rate limiter exhaustion. Changes: - Add GET /sse + POST /messages endpoints following the official MCP SDK backward-compatible server pattern (separate endpoints per transport) - Store SSE transports in the shared this.transports map with instanceof guards for type discrimination - Remove legacy this.session singleton, resetSessionSSE(), and isExpired() - Extract duplicated auth logic into authenticateRequest() method - Add Bearer token auth and rate limiting to SSE endpoints - Add skipSuccessfulRequests to authLimiter to prevent 429 storms - Mark SSE transport as deprecated (removed in MCP SDK v2.x) The handleRequest() codepath used by the downstream SaaS backend (N8NMCPEngine.processRequest()) is unchanged. Session persistence (exportSessionState/restoreSessionState) is unchanged. Closes czlonkowski#617 Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2d41155 commit 12d7d5b

10 files changed

Lines changed: 493 additions & 437 deletions

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [2.46.1] - 2026-04-03
11+
12+
### Fixed
13+
14+
- **Fix SSE reconnection loop** — SSE clients entering rapid reconnection loops because `POST /mcp` never routed messages to `SSEServerTransport.handlePostMessage()` (Fixes #617). Root cause: SSE sessions were stored in a separate `this.session` property invisible to the StreamableHTTP POST handler
15+
- **Add authentication to SSE endpoints**`GET /sse` and `POST /messages` now require Bearer token authentication, closing an auth gap where SSE connections were unauthenticated
16+
- **Fix rate limiter exhaustion during reconnection** — added `skipSuccessfulRequests: true` to `authLimiter` so legitimate requests don't count toward the rate limit, preventing 429 storms during SSE reconnection loops
17+
18+
### Changed
19+
20+
- **Separate SSE endpoints (SDK pattern)** — SSE transport now uses dedicated `GET /sse` + `POST /messages` endpoints instead of sharing `/mcp` with StreamableHTTP, following the official MCP SDK backward-compatible server pattern
21+
- **Unified auth into `authenticateRequest()` method** — consolidated duplicated Bearer token validation logic from three endpoints into a single method with consistent JSON-RPC error responses
22+
- **SSE sessions use shared transports map** — removed the legacy `this.session` singleton; SSE sessions are now stored in the same `this.transports` map as StreamableHTTP sessions with `instanceof` guards for type discrimination
23+
24+
### Deprecated
25+
26+
- **SSE transport (`GET /sse`, `POST /messages`)** — SSE is deprecated in MCP SDK v1.x and removed in v2.x. Clients should migrate to StreamableHTTP (`POST /mcp`). These endpoints will be removed in a future major release
27+
28+
### Security
29+
30+
- **Rate limiting on all authenticated endpoints**`authLimiter` now applied to `GET /sse` and `POST /messages` in addition to `POST /mcp`
31+
- **Transport type guards**`instanceof` checks prevent cross-protocol access (SSE session IDs rejected on StreamableHTTP endpoint and vice versa)
32+
33+
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
34+
1035
## [2.46.0] - 2026-04-03
1136

1237
### Added

dist/http-server-single-session.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export declare class SingleSessionHTTPServer {
1212
private sessionMetadata;
1313
private sessionContexts;
1414
private contextSwitchLocks;
15-
private session;
1615
private consoleManager;
1716
private expressServer;
1817
private sessionTimeout;
@@ -29,14 +28,14 @@ export declare class SingleSessionHTTPServer {
2928
private isJsonRpcNotification;
3029
private sanitizeErrorForClient;
3130
private updateSessionAccess;
31+
private authenticateRequest;
3232
private switchSessionContext;
3333
private performContextSwitch;
3434
private getSessionMetrics;
3535
private loadAuthToken;
3636
private validateEnvironment;
3737
handleRequest(req: express.Request, res: express.Response, instanceContext?: InstanceContext): Promise<void>;
38-
private resetSessionSSE;
39-
private isExpired;
38+
private createSSESession;
4039
private isSessionExpired;
4140
start(): Promise<void>;
4241
shutdown(): Promise<void>;

dist/http-server-single-session.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)