🪢 fix: Paginate MCP tools/list to Load All Tools#13840
Merged
danny-avila merged 3 commits intoJun 20, 2026
Merged
Conversation
This was referenced Jun 19, 2026
TomasPalsson
force-pushed
the
fix/mcp-tools-list-pagination
branch
from
June 19, 2026 01:01
fdb4a6e to
7cbc204
Compare
MCP `tools/list` is cursor-paginated, but LibreChat only ever read the first page. `MCPConnection.fetchTools()` called `client.listTools()` once and discarded `nextCursor`, and `MCPServerInspector` — which builds the agent-facing tool registry at startup and per request — called the raw `client.listTools()` directly. Servers that paginate (e.g. an aggregating gateway exposing hundreds of tools) only ever exposed page one; tools on later pages were never registered, and invoking one returned "This tool's MCP server is temporarily unavailable." - `MCPConnection.fetchTools()` now follows `nextCursor` across pages and concatenates every page's tools, bounded by a configurable page cap (`MCP_TOOLS_LIST_MAX_PAGES`, default 50) and a repeated-cursor guard so a misbehaving server cannot loop forever. Tools already fetched are returned if a later page fails, and the no-throw error contract is unchanged. - `MCPServerInspector.getToolFunctions()` and `fetchServerCapabilities()` now route through `fetchTools()`, so the canonical startup and per-request tool registry is fully paginated too.
TomasPalsson
force-pushed
the
fix/mcp-tools-list-pagination
branch
from
June 19, 2026 10:31
7cbc204 to
1075d23
Compare
Owner
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
fuuuzzy
pushed a commit
to fuuuzzy/LibreChat
that referenced
this pull request
Jul 7, 2026
* 🪢 fix: Paginate MCP tools/list to load all tools MCP `tools/list` is cursor-paginated, but LibreChat only ever read the first page. `MCPConnection.fetchTools()` called `client.listTools()` once and discarded `nextCursor`, and `MCPServerInspector` — which builds the agent-facing tool registry at startup and per request — called the raw `client.listTools()` directly. Servers that paginate (e.g. an aggregating gateway exposing hundreds of tools) only ever exposed page one; tools on later pages were never registered, and invoking one returned "This tool's MCP server is temporarily unavailable." - `MCPConnection.fetchTools()` now follows `nextCursor` across pages and concatenates every page's tools, bounded by a configurable page cap (`MCP_TOOLS_LIST_MAX_PAGES`, default 50) and a repeated-cursor guard so a misbehaving server cannot loop forever. Tools already fetched are returned if a later page fails, and the no-throw error contract is unchanged. - `MCPServerInspector.getToolFunctions()` and `fetchServerCapabilities()` now route through `fetchTools()`, so the canonical startup and per-request tool registry is fully paginated too. * style: Sort MCP test imports * style: Sort mutation type imports --------- Co-authored-by: Danny Avila <danny@librechat.ai>
ThomasVuNguyen
pushed a commit
to ThomasVuNguyen/LibreChat
that referenced
this pull request
Jul 15, 2026
* 🪢 fix: Paginate MCP tools/list to load all tools MCP `tools/list` is cursor-paginated, but LibreChat only ever read the first page. `MCPConnection.fetchTools()` called `client.listTools()` once and discarded `nextCursor`, and `MCPServerInspector` — which builds the agent-facing tool registry at startup and per request — called the raw `client.listTools()` directly. Servers that paginate (e.g. an aggregating gateway exposing hundreds of tools) only ever exposed page one; tools on later pages were never registered, and invoking one returned "This tool's MCP server is temporarily unavailable." - `MCPConnection.fetchTools()` now follows `nextCursor` across pages and concatenates every page's tools, bounded by a configurable page cap (`MCP_TOOLS_LIST_MAX_PAGES`, default 50) and a repeated-cursor guard so a misbehaving server cannot loop forever. Tools already fetched are returned if a later page fails, and the no-throw error contract is unchanged. - `MCPServerInspector.getToolFunctions()` and `fetchServerCapabilities()` now route through `fetchTools()`, so the canonical startup and per-request tool registry is fully paginated too. * style: Sort MCP test imports * style: Sort mutation type imports --------- Co-authored-by: Danny Avila <danny@librechat.ai>
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.
Summary
I fixed MCP tool discovery silently truncating to the first page when a server paginates
tools/list.Root cause
tools/listis a cursor-paginated method in the MCP spec, but LibreChat only ever read the first page in two places:MCPConnection.fetchTools(packages/api/src/mcp/connection.ts) calledthis.client.listTools()once and returnedresult.tools, discardingnextCursor.MCPServerInspector(packages/api/src/mcp/registry/MCPServerInspector.ts) — which builds the agent-facing tool registry (config.toolFunctions, consumed byinitializeMCPs→MCPManager.getAppToolFunctions) at startup and per request — called the rawconnection.client.listTools()directly in bothgetToolFunctionsandfetchServerCapabilities.So any server returning more tools than fit in one page only ever exposed page 1. With an aggregating gateway (observed: AWS Bedrock AgentCore Gateway returning 256 tools across 6 pages) only the first ~41 tools were registered; the rest were invisible to the agent, and invoking one failed with
"This tool's MCP server is temporarily unavailable"because it was never in the registry.Approach
fetchToolsnow followsnextCursor, passing{ cursor }tolistToolsfor each page and concatenatingtoolsin order until the server stops returning a cursor. Two defensive guards bound the loop:mcpConfig.TOOLS_LIST_MAX_PAGES(envMCP_TOOLS_LIST_MAX_PAGES, default50, clamped to>= 1). On hitting the cap the loop stops and warns.seenCursorsset — if a server repeats a cursor it already returned (an infinite-loop signature), the loop stops early and warns.MCPServerInspector.getToolFunctionsandfetchServerCapabilitiesnow route throughconnection.fetchTools(), so the canonical startup and per-request tool registry is fully paginated.What is deliberately not changed / out of scope
[].MCPServerInspectorreads the tool list twice at startup (capability summary + tool registry); this double read pre-existed the PR, so collapsing it into one fetch is left as a follow-up.fetchResourcesandfetchPromptsshare the same single-page limitation; left out to keep this PR focused.Changes
packages/api/src/mcp/connection.ts—fetchToolsfollowsnextCursorwith page-cap and repeated-cursor guards, returning already-fetched pages on error.packages/api/src/mcp/mcpConfig.ts— adds typedTOOLS_LIST_MAX_PAGES(envMCP_TOOLS_LIST_MAX_PAGES, default50).packages/api/src/mcp/registry/MCPServerInspector.ts—getToolFunctionsandfetchServerCapabilitiesuse the paginatedfetchTools().fetchTools(including fivemcpConfigmocks that previously omitted the cap).Change Type
Testing
Run from
packages/apion Nodev24.16.0:npx jest src/mcp/__tests__/MCPConnectionFetchTools.test.ts— 8/8 passing. Covers single page, multi-page concat with cursor passthrough, page-cap warn, repeated-cursor warn, empty intermediate page, empty-string cursor, mid-pagination failure (already-fetched pages returned), and first-page failure ([]). Builds a realMCPConnectionand injects a stubclient.listTools, exercising the actual loop.npx jest src/mcp/registry/__tests__/MCPServerInspector.test.ts— passing; inspector now drives the paginated path.npm run test:ci— full@librechat/apisuite green, no regressions.Test Configuration
v24.16.0, Workspace:packages/api