From fad343797719f96c33417ef32b54fe4107d51581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romuald=20Cz=C5=82onkowski?= <56956555+czlonkowski@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:56:16 +0100 Subject: [PATCH] fix: memory leak in SSE session reset (#542) (#544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When SSE sessions are recreated every 5 minutes, the old session's MCP server was not being closed, causing: - SimpleCache cleanup timer continuing to run indefinitely - Database connections remaining open - Cached data (~50-100MB per session) persisting in memory Added server.close() call before transport.close() in resetSessionSSE(), mirroring the existing cleanup pattern in removeSession(). Fixes #542 Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-authored-by: Claude Opus 4.5 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- src/http-server-single-session.ts | 20 +++++++++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2eb15b..f2a1b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.33.4] - 2026-01-21 + +### Fixed + +- **Memory leak in SSE session reset** (Issue #542): Fixed memory leak when SSE sessions are recreated every 5 minutes + - Root cause: `resetSessionSSE()` only closed the transport but not the MCP server + - This left the SimpleCache cleanup timer (60-second interval) running indefinitely + - Database connections and cached data (~50-100MB per session) persisted in memory + - Fix: Added `server.close()` call before `transport.close()`, mirroring the existing cleanup pattern in `removeSession()` + - Impact: Prevents ~288 leaked server instances per day in long-running HTTP deployments + ## [2.33.3] - 2026-01-21 ### Changed diff --git a/package.json b/package.json index 3ffd0a5..a6301da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp", - "version": "2.33.3", + "version": "2.33.4", "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/http-server-single-session.ts b/src/http-server-single-session.ts index ef9e547..14a7df2 100644 --- a/src/http-server-single-session.ts +++ b/src/http-server-single-session.ts @@ -677,11 +677,25 @@ export class SingleSessionHTTPServer { private async resetSessionSSE(res: express.Response): Promise { // Clean up old session if exists if (this.session) { + const sessionId = this.session.sessionId; + logger.info('Closing previous session for SSE', { sessionId }); + + // Close server first to free resources (database, cache timer, etc.) + // This mirrors the cleanup pattern in removeSession() (issue #542) + // Handle server close errors separately so transport close still runs + if (this.session.server && typeof this.session.server.close === 'function') { + try { + await this.session.server.close(); + } catch (serverError) { + logger.warn('Error closing server for SSE session', { sessionId, error: serverError }); + } + } + + // Close transport last - always attempt even if server.close() failed try { - logger.info('Closing previous session for SSE', { sessionId: this.session.sessionId }); await this.session.transport.close(); - } catch (error) { - logger.warn('Error closing previous session:', error); + } catch (transportError) { + logger.warn('Error closing transport for SSE session', { sessionId, error: transportError }); } }