Repository navigation
Answer subscriptions/listen without building the caller's tool surface - #1074
Merged
Merged
Conversation
Claude clients on MCP 2026-07-28 keep subscriptions/listen streams open for about five minutes; on 13 Oct about 200 were open at once, ~50 new ones a minute. The SDK reads only the server's capabilities and name for such a stream, yet every listen filtered the whole tool registry on the shared /mcp, and on a server's own URL built its full tool surface and kept it referenced for the stream's life. Listen is now answered from a server that only declares the same list-changed capabilities, after the same auth and membership checks. Nothing is ever published on these streams (each request has its own event bus), so clients see the same acknowledgement and an open stream as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Claude clients on MCP 2026-07-28 open
subscriptions/listenstreams and keep them for about five minutes. On Cloud on 13 Oct there were about 240 open at once, roughly 50 new ones a minute, 89% on the shared/mcpand the rest on/mcp/<serverId>.For a listen the SDK only reads the server's capabilities and name, closes it, and serves the stream itself. We still did the full work every time:
/mcp:attachVisibleToolsfiltered the whole tool registry (~50k tools) and resolved the grant and the OAuth client, for nothing;/mcp/<serverId>: the full tool surface was built before the method was known and stayed referenced by the factory closure for the stream's whole life, so about 200 surfaces sat in memory unused.This came up while looking at today's 12:16 UTC backend crash (V8 "Zone Allocation failed" with the heap at a third of its cap). It is not proven to be the cause; it removes wasted memory and CPU either way. #1072 adds a crash report for the next one.
Changes
isListenRequest(body): a single JSON-RPC message with methodsubscriptions/listen.listenOnlyServer(name, version, capabilities): anMcpServerthat only declares capabilities./mcp(fixed mode, identified caller, same condition asservesSharedToolset): a listen is answered beforeattachVisibleTools, declaringtools.listChanged, as the full shared server does./mcp/<serverId>: a listen is answered after the inactive-server and membership checks and before the connectors and tools are loaded, declaringtoolsandresourceslistChanged. The stateful mode (MCP_STATEFUL_SESSIONS) is unchanged.Nothing is ever published on these streams: each request gets its own event bus, so the declared capabilities only decide what the acknowledgement lists and whether the stream stays open. Checked against the real SDK: with these capabilities the client gets the same
notifications/subscriptions/acknowledgedand an open stream; with none the stream would close at once.Cloud and self-hosted behave the same; on self-hosted the shared
/mcppath only applies in fixed mode (MCP_SHARED_ENDPOINT_TOOLS=fixed).Type
Testing
In
packages/backend:npx jest src/mcp-server: 22 suites, 353 tests passed. New: listen on shared/mcptouches neither the registry, the grant nor the OAuth client; listen on/mcp/<id>still refuses a non-member with 403, answers a member without loading connectors, content or tools; other methods keep the full path;isListenRequestcases.npx tsc --noEmit -p tsconfig.json: clean. eslint on the changed files: clean.createMcpHandlerwith a 2026-07-28 listen: acknowledgement liststoolsListChanged(andresourcesListChangedfor the server form), stream stays open.Checklist
npm test)Related Issues
Follows #1072.