From ed783097e734feffef58fee41e9a17ef121a9c57 Mon Sep 17 00:00:00 2001 From: Vishal Habib Date: Fri, 28 Aug 2026 21:45:38 -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);