Fix MCP server hanging on all but the most-recently-connected session (#267)
* Give each MCP session its own McpServer to fix response misrouting * Deregister tools from live MCP sessions on cleanup The remove() closure returned by private tool() previously only spliced the spec from toolSpecs[]. When a plugin unloaded and called its cleanup function, every already-connected MCP session retained the stale tool and continued routing calls to the dead handler. Fix by storing the RegisteredTool handle returned by server.tool() for each live session in a per-session toolHandles map. When remove() is called, iterate this.sessions and call the SDK-level .remove() on the matching handle in each session, then evict it from the map. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Register tools on already-connected sessions when added late buildServer() is a one-shot snapshot taken at session-init time. A plugin that calls registerTool() after a client has already connected would have its tool in toolSpecs[] for future sessions, but the already-connected session's McpServer would never see it — resulting in method-not-found errors with no useful message. Fix by iterating this.sessions after pushing the new spec and calling server.tool() on each live session's McpServer directly, storing the returned handle in session.toolHandles so it can be cleaned up later. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Pass DEFAULT_SETTINGS to McpHandler in handleRequest tests Three test cases in the handleRequest describe block constructed McpHandler with only one argument, leaving this.settings undefined. The tests passed only because none invoked a tool callback, but any future test that does would throw TypeError on this.settings.enableVerboseLogging. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add integration test for multi-session MCP routing Adds a regression test that connects two clients to the same server and verifies the first client can still complete a tool call after the second connects. This is the exact failure mode from the shared-McpServer bug: the second connect() call would overwrite the server's internal _transport, causing the first session's responses to be routed to the wrong client and hang indefinitely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Rename private resource() to addResourceSpec() to avoid confusion The method name shadowed MinimalMcpServer.resource(), making it easy to misread registerResources() and assume resources were being registered immediately rather than deferred to session build time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Replace toolSpecs array + registeredToolNames set with a single Map toolSpecs: ToolSpec[] and registeredToolNames: Set<string> were redundant — the Set existed solely to guard against duplicate names, but toolSpecs already held all the names. Replacing both with a Map<string, ToolSpec> keyed by name eliminates the redundancy and makes the duplicate guard, insertion, and removal all O(1). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Adam Coddington <me@adamcoddington.net> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
P
Piotr committed
1408e8a38d3b43d2bf34c343cda27d9ad7b88738
Parent: 7f556e0
Committed by GitHub <noreply@github.com>
on 5/29/2026, 2:33:41 AM