Paginate MCP tools/list and prompts/list - #1308
Open
DivyaMaddipudi wants to merge 2 commits into
Open
Conversation
McpServerProxy dropped the MCP nextCursor, truncating multi-page servers to their first ~30 tools. Page through it with termination guards (blank cursor ends, repeated/cycling cursor aborts, hard page cap), return an immutable list, and log items/pages fetched. Also harden the surrounding paths: isolate each proxy's listing in McpService so one failure can't abort discovery for the rest; run tools/list_changed refreshes off the transport reader thread (a synchronous refresh deadlocks the stdio reader) with an add-then-prune swap; and give StdioProxy a per-request timeout (default 5m, builder-configurable) so a silent server can't block the caller forever.
franco2002lu
approved these changes
Aug 5, 2026
franco2002lu
enabled auto-merge (squash)
August 5, 2026 21:28
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Paginate MCP tools/list and prompts/list
McpServerProxydropped the MCPnextCursor, so a server that returned more thanone page (servers commonly page at ~30 items) was silently truncated to its first
page. This change pages through
nextCursorwith three termination guards — a blankor absent cursor ends pagination, a repeated or cycling cursor aborts, and a hard
page cap backstops a runaway server — returns an immutable list, and logs the number
of items/pages fetched.
It also hardens the surrounding paths:
McpServiceis wrapped so one proxy'sfailure can't abort tool/prompt discovery for the rest.
tools/list_changedrefreshes now run off thetransport's reader thread (a synchronous refresh calls
listTools()whose responseis read by that same thread, deadlocking it), using an add-then-prune swap so a
concurrent
tools/listnever sees a gap.StdioProxygets a per-request timeout (default 5 min,builder-configurable, symmetric with
HttpMcpProxy) so a server that stays alivebut never responds can't block the caller forever.
What behavior changes?
Before:
listTools()/listPrompts()issued a single JSON-RPC call and ignorednextCursor, returning only the first page (~30 tools) for any multi-page server.After: they return the union of all pages. Single-page servers are unchanged — still
exactly one round-trip. Secondary observable differences: a failing/misbehaving proxy
no longer wipes out discovery for other proxies; a
tools/list_changedrefresh nolonger risks freezing a stdio connection; and a silent stdio server now fails after
the timeout instead of hanging indefinitely.
Why is this change needed?
Bug. MCP
tools/listandprompts/listare cursor-paginated, but the client onlyever read the first page, so any server/gateway federating more than ~30 tools had
its tool set truncated everywhere
McpServerProxyis used (offline enumeration andthe runtime proxy federation in
McpService) — with no error and no log, making ithard to notice.
How was this validated?
./gradlew :mcp:mcp-server:test(all green) plus Spotless. Tests added:McpServerProxyTest(12): multi-page union with exactnextCursor->cursorthreading, single-page regression, blank-cursor termination, repeated-cursor abort,
cycling (
A -> B -> A) abort, page-cap boundary, error on the first page, error on alater page, null-
resulthandling,listPromptsguard path, and immutable return.HttpMcpProxyTest(+1): real wire round-trip — a localhostHttpServerpagestools/listwith anextCursorand assertslistTools()follows it, reading thecursor off the actually-deserialized response.
McpServiceTest(2): atools/list_changedrefresh runs off the notifying thread(deadlock regression), and add-then-prune correctly adds new tools while pruning
stale ones.
StdioProxyTest(1): a silent server times out instead of hanging.What should reviewers focus on?
McpServerProxy#listPaginated(...)— the pagination loop and its three terminationguards, the immutable return, and the null-
resultguard.mcp/mcp-schemas/model/main.smithy— the additive optionalnextCursoronListToolsResult/ListPromptsResult.McpService— the twolistTools()call sites now isolated per-proxy, and thetools/list_changedhandler: async dispatch off the reader thread +refreshProxyToolsadd-then-prune swap.
StdioProxy#rpc— the per-request timeout and pending-request cleanup on completion.Additional Links
nextCursor/cursor): https://modelcontextprotocol.io/specificationBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.