Skip to content

Defer Oversized MCP Tool Schemas by Size - #16179

Open
aligulzar729 wants to merge 3 commits into
LibreChat-AI:devfrom
aligulzar729:feat/mcp-defer-schema-size
Open

aligulzar729 wants to merge 3 commits into
LibreChat-AI:devfrom
aligulzar729:feat/mcp-defer-schema-size

Conversation

@aligulzar729

@aligulzar729 aligulzar729 commented Sep 22, 2026 •

Copy link
Copy Markdown

Summary

What this changes: an MCP tool whose argument schema is larger than mcpSettings.deferSchemaChars characters of JSON starts deferred, so the model sees only its name and pulls the schema in through tool_search when it needs it. It needs the agents deferred_tools capability. Default 0, which is off. No network call, no new dependency.

I run a self-hosted instance with a large MCP catalog. A few tools dominate it: two tools from one server serialize to about 287,000 characters each against descriptions of 150 and 261 characters, roughly two thirds of the whole tool surface, paid on every turn.

Measured on my own catalog: 8 connected servers, 202 tools, 239,613 tokens of definitions, of which 92.6% is argument schemas. Everything else, which is what a turn still pays once schemas are withheld, is 17,822 tokens: the names and descriptions the model needs to decide whether to call a tool, plus the JSON scaffolding around each definition.

server tools tokens largest single schema
A 12 161,062 79,699
B 83 43,546 1,531
C 45 11,410 461
D 27 9,711 1,178
E 7 6,195 1,097
F 18 4,967 323

One server with 12 tools is 67% of the catalog, and one schema inside it is 79,699 tokens on its own. Capping by size removes that without touching the other 190 tools.

Deferred loading already solves this, but only per tool and by hand, so you have to find the expensive ones before you can hide any of them. mcpSettings.deferSchemaChars applies it by measured size instead. Default 0 disables the rule, so nothing changes for an instance that does not set it.

Related to #13951 and #15713, which default deferral by server and by agent. This defers by cost, and the precedence chain is compatible with both.

How it works

explicit defer_loading set?                -> use it
deferSchemaChars <= 0                      -> false
schemaSize(parameters) > deferSchemaChars  -> true

A per-tool toggle always wins, in either direction, so the rule is safe to set instance-wide. The size is counted in characters, which is closer to the tokens it saves than bytes would be. Applied in both registry branches, so agents with no tool_options are covered too.

Agent builder. GET /api/mcp/tools marks each tool the rule would defer with deferredBySize: true. In the tool dialog that tool's existing defer toggle shows as on, with the tooltip "Deferred automatically because this tool's schema is larger than the server's size limit. Click to keep it loaded instead." Switching it off stores defer_loading: false, which keeps the tool loaded; switching it back on clears that, so the tool follows the rule again. Tools the rule does not touch store exactly what they did before, and "Defer all tools" counts size-deferred tools as deferred. No new component: it reuses OptionToggle, its pressed style and TooltipAnchor, with one new English string.

stored defer_loading   deferredBySize   shows as   click stores
true                   any              on         (removed)
false                  true             off        (removed), back to the rule
none                   true             on         false
none                   false            off        true

Testing

How the catalog was measured: a read-only script against my own instance reading GET /api/mcp/servers and summing, per tool, name.length + description.length + JSON.stringify(parameters).length. Tokens are characters / 3.6. Server names are anonymized above; the shape is what matters.

Added 16 cases to classification.spec.ts: rule off reproducing current behavior, an explicit false pinning a large tool open, an explicit true hiding a small one, unrelated tool_options still falling under the rule, an unserializable schema counted as weightless, and buildToolClassification for an agent with no tool_options (the oversized tool defers and brings tool_search; with deferred_tools off every tool stays callable).

useMCPToolOptions.spec.ts covers the builder states in the table above, including "Defer all", and mcp.spec.js checks that GET /api/mcp/tools marks only the oversized tool.

cd packages/api && npx jest src/tools/classification src/tools/definitions passes 98, cd api && npx jest server/services/__tests__/ToolService passes 140, cd api && npx jest server/routes/__tests__/mcp.spec.js -t "GET /tools" passes 9, and cd client && npx jest src/hooks/Agents/__tests__/useMCPToolOptions.spec.ts src/components/SidePanel/Agents passes 574. Checked in the browser against a 25-tool Playwright server with deferSchemaChars: 300. npx tsc --noEmit clean for packages/api, data-provider and data-schemas, and npm run typecheck clean for client.

Risk / compatibility

No-op by default. One JSON.stringify per MCP tool at registry build.

Type of change

  • Feature

Checklist

@aligulzar729 aligulzar729 changed the title 🪶 feat: Defer Oversized MCP Tool Schemas by Size Defer Oversized MCP Tool Schemas by Size Sep 22, 2026
@aligulzar729
aligulzar729 force-pushed the feat/mcp-defer-schema-size branch from bbc923b to f7ab8dc Compare September 22, 2026 04:08
@danny-avila

Copy link
Copy Markdown
Collaborator

@aligulzar729 thanks for the PRs! Will review when I get a chance

@aligulzar729
aligulzar729 force-pushed the feat/mcp-defer-schema-size branch from 0c0593a to 6616e6d Compare September 24, 2026 03:23
@aligulzar729
aligulzar729 force-pushed the feat/mcp-defer-schema-size branch from 2c07bd3 to 9d4b1b3 Compare September 24, 2026 04:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants