From 07a0dba34dca200d89bc54008bf69fc358e0c543 Mon Sep 17 00:00:00 2001 From: Vishal Habib Date: Sat, 29 Aug 2026 09:25:41 -0400 Subject: [PATCH] everything: remove disconnected sessions from resource subscriptions subscriptions (Map>) only ever had entries removed via an explicit resources/unsubscribe call. A session that disconnects without unsubscribing stayed in every Set it had joined for the life of the process, since cleanup(sessionId) stopped logging/update intervals and task-store timers but never touched subscriptions. Add removeSubscriber(sessionId), which drops the session from every URI's subscriber set and removes any URI entry left with no subscribers, and call it from cleanup(). Fixes #4710 --- src/everything/resources/subscriptions.ts | 19 +++++++++++++++++++ src/everything/server/index.ts | 3 +++ 2 files changed, 22 insertions(+) diff --git a/src/everything/resources/subscriptions.ts b/src/everything/resources/subscriptions.ts index 854a8633a2..9d2044147c 100644 --- a/src/everything/resources/subscriptions.ts +++ b/src/everything/resources/subscriptions.ts @@ -166,3 +166,22 @@ export const stopSimulatedResourceUpdates = (sessionId?: string) => { subsUpdateIntervals.delete(sessionId); } }; + +/** + * Removes a session from every URI's subscriber set, dropping any URI entry + * that ends up with no remaining subscribers. + * + * A session that disconnects without explicitly unsubscribing otherwise stays + * in `subscriptions` for the life of the process. Call this from the + * transport's `cleanup(sessionId)` when a session ends. + * + * @param {string} [sessionId] + */ +export const removeSubscriber = (sessionId?: string) => { + for (const [uri, subscribers] of subscriptions) { + subscribers.delete(sessionId); + if (subscribers.size === 0) { + subscriptions.delete(uri); + } + } +}; diff --git a/src/everything/server/index.ts b/src/everything/server/index.ts index f1459cc812..182a5a084a 100644 --- a/src/everything/server/index.ts +++ b/src/everything/server/index.ts @@ -6,6 +6,7 @@ import { import { setSubscriptionHandlers, stopSimulatedResourceUpdates, + removeSubscriber, } from "../resources/subscriptions.js"; import { registerConditionalTools, registerTools } from "../tools/index.js"; import { registerResources, readInstructions } from "../resources/index.js"; @@ -110,6 +111,8 @@ export const createServer: () => ServerFactoryResponse = () => { // Stop any simulated logging or resource updates that may have been initiated. stopSimulatedLogging(sessionId); stopSimulatedResourceUpdates(sessionId); + // Drop this session from any resource subscriptions it left open. + removeSubscriber(sessionId); // Clean up task store timers taskStore.cleanup(); if (initializeTimeout) clearTimeout(initializeTimeout);