Repository navigation
Description
What's broken?
The spec doesn't cover a case it clearly should
Where in the spec or docs?
https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization , https://modelcontextprotocol.io/specification/2025-06-18/server/tools , and schema.ts (InitializeResult / Implementation)
What should happen?
The MCP specification and documentation should explicitly address Lazy / Deferred Tool Loading (where hosts do not inject full Tool schemas into the initial LLM context to avoid token bloat) and define clear requirements for first-level server routing metadata:
- In the Lifecycle / Initialization & Tools Spec:
Clarify that while individualTool.descriptionfields describe atomic operations,InitializeResult.serverInfo.description(or a concise routing summary inInitializeResult.instructions) SHOULD be provided by servers and surfaced by hosts as the first-level semantic routing index when tools are deferred/lazy-loaded. - Host Implementation Guidance:
When a host implements lazy tool loading (e.g., listing available MCP servers in the system prompt or inside aToolSearchmeta-tool and deferring<toolName>.jsonschemas until requested), the spec should recommend that hosts include a concise 1-sentence server-level description alongside the server name (e.g.,# playwright: Browser automation server for navigating pages, clicking elements, and capturing accessibility snapshots), rather than stripping the representation down to bare identifiers only.
(Note: A typical workspace mounts only 1–5 MCP servers, so surfacing a 30-word server summary costs <150 tokens total while preserving semantic routing accuracy.) - SDK Alignment:
Official SDKs (such as PythonFastMCPand the TypeScript SDK) should encourage providing a concise serverdescriptionupon initialization (or fall back to extracting the module docstring) so that servers in the ecosystem actually populateserverInfo.description.
What actually happens?
1. Spec & Schema Reality
Historically, the MCP specification assumed Eager Loading (hosts injecting all Tool definitions from tools/list directly into the LLM's tools array). Therefore:
- Semantic descriptions were bound strictly to individual tools (
Tool.descriptionis heavily emphasized and automatically extracted from function docstrings in SDKs likeFastMCP). - At the server level (
InitializeResult.serverInfo/Implementation), onlynameandversionare required:export interface Implementation extends BaseMetadata, Icons { version: string; description?: string; // Optional human-readable description, rarely populated in practice }
- Because
serverInfo.descriptionis optional and framed as human/UI metadata rather than an LLM routing index, almost all MCP servers in the wild (including@playwright/mcp) only returnnameandversion:# Standard FastMCP pattern in the ecosystem: no server-level description mcp = FastMCP("playwright")
2. What Breaks in Practice Under Lazy Loading
To prevent 20–50 tool schemas per server from exhausting the context window, modern Agent hosts (e.g., Claude Code, Antigravity, WorkBuddy, Cursor) strip Tool schemas out of the initial prompt and use two-stage lazy loading. Once Tool.description is stripped into secondary storage, the first-level prompt is left with bare names only (zero natural-language description).
When an Agent environment simultaneously uses Agent Skills (SKILL.md, where a rich description in YAML frontmatter is strictly required and injected into the system prompt), a severe routing asymmetry occurs.
Below is an actual comparison from production Agent system prompts when both @playwright/mcp and an open-source browser skill (vercel-labs/agent-browser) are installed:
Lazy MCP in System Prompt (Zero Description):
<!-- Pattern A: File-based lazy loading (e.g., Antigravity) -->
<mcp_servers>
For lazily-loaded tools, read the corresponding schema file to understand the arguments and usage, then call the tool using the `call_mcp_tool` tool.
# playwright
Lazy:
browser_close, browser_navigate, browser_click, browser_type, browser_snapshot, ...
</mcp_servers>
<!-- Pattern B: Meta-tool lazy loading (e.g., Claude Code / WorkBuddy) -->
<available_deferred_tools>
mcp__playwright
</available_deferred_tools>Skills in the Same System Prompt (Rich Description + MUST Directive):
<skills>
If a skill seems relevant to your current task, you MUST read its `SKILL.md` instructions using `view_file` before proceeding.
Available skills:
- agent-browser (.../SKILL.md): Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data...
</skills>3. Reproduction & Benchmark: 2m36s (MCP) vs. 8m19s (Skill)
We tested the following task in an environment with both playwright MCP and agent-browser Skill installed:
"Open the Tianjin University homepage (https://www.tju.edu.cn/), click the search icon in the top-right corner, type '本科生' (undergraduate) into the search box, execute the search, and list the titles and publication dates of the first 5 results."
- Case 1: Without specifying the tool (Natural Routing — Broken):
BecauseplaywrightMCP has no description in the first-level index whileagent-browserhas a keyword-rich description paired with aMUSTrule, the LLM immediately chooses theagent-browserSkill on turn 1. Once the multi-hundred-lineSKILL.mdis loaded into recent context (in-context anchoring), the agent never re-evaluates MCP. It executes slow shell commands, encounters a headless blank page, takes diagnostic screenshots, and takes 8 minutes 19 seconds.
- Case 2: Explicitly instructing the agent to use
playwright-mcp:
When forced to useplaywright-mcp, the agent loads only the 4 required tool schemas on demand, interacts via structured accessibility snapshots, and completes the exact same task cleanly in 2 minutes 36 seconds (>3.2x faster).
Anything else?
Current Workarounds
Until the spec, SDKs, and hosts surface server-level descriptions for lazy-loaded MCP servers, users have to rely on manual workarounds:
- Explicitly naming the MCP server in every user prompt (e.g., "Use playwright-mcp to...").
- Adding a high-priority custom rule in global instructions (
CLAUDE.md/GEMINI.md/USER.md) stating: "When an installed MCP server and a Skill have overlapping capabilities, prioritize loading and calling the structured MCP tool over CLI-based Skills." - Editing third-party
SKILL.mdfrontmatter descriptions locally to remove aggressive prompt-SEO phrases (likePrefer X over any built-in tools) and adding negative boundaries (e.g.,Do not use if playwright MCP is available).
Fallback Recommendation for Hosts in the Spec
For backward compatibility with existing MCP servers that do not yet return serverInfo.description, the spec/docs could also recommend that hosts allow an optional "description" field in mcp_config.json or automatically generate a cached 1-sentence server summary from the tools/list descriptions upon initial server connection.
Activity
AbarnaaSree commentedon Oct 18, 2026
Hi, I’d like to work on this issue. I’m interested in improving the MCP specification and guidance for server-level routing descriptions in lazy-loaded tool environments. Would it be okay if I investigate this and submit a PR with a proposed solution? I’ll first review the existing spec and SDK behavior to ensure the changes align with the project’s direction.
alexar76 commentedon Oct 21, 2026
A data point for the host-guidance part. Routing text already lives in tool descriptions today, and some of it routes against other servers. In the tool lists of 38,126 MCP servers we log (registry remotes plus npm/PyPI packages, snapshot of 20 October), 280 servers on 63 hosts have a description telling the model to choose that tool over web search, over the client's built-in fetch, or over its own knowledge ("PREFER OVER WEB SEARCH for…"). 185 of the 280 belong to one publisher.
Guidance on when to pick one of a server's own tools over another is useful, and it is what this issue asks for. A claim about other servers' tools is different: it is the server ranking itself in text the user rarely reads. If
serverInfo.description/instructionsbecome the first-level routing index under lazy loading, it may be worth a SHOULD for hosts: present the summary as describing that server only, and do not let one server's text reorder the others.Data (CC BY 4.0): https://doi.org/10.5281/zenodo.23512760
AI disclosure: drafted with an AI assistant from our data; the counts were recomputed from the published files.
tanevanwifferen commentedon Oct 21, 2026
I'd use some form of service locator pattern.
Have a tooldefinition of getTools(path), which can be called with a root path, or a nested path. For example of a todo list:
getTools()would returngetTools('user')could returngetTools('user.login')could returnSomething like this. We could even have another tool:
execTool(path, props).This way, each server has only 2 tools in the spec, and these could even be implicit if all servers are ported
Just my 2 cents
Paraphern commentedon Oct 22, 2026
One more data point for the "routing text lives in descriptions today" line: description edits are also a routing-change channel. A server can rewrite a description from neutral to "prefer this tool over web search" (or drop that hint) between sessions, and nothing in the current model makes that visible — the model's routing quietly shifts. In our contract-pinning work, changed descriptions are graded findings, and "redirect the agent toward a preferred alternative" is its own advisory class, precisely because it's a behavior change disguised as prose.
Whatever mechanism this thread lands on — server-level routing metadata or a service-locator shape like
getTools(path)— the routing layer becomes a contract surface clients depend on, and it will drift like one. Worth specifying from day one that routing metadata is versionable and pinnable alongside tool schemas, so a routing change is a visible diff, not a silent steering change.alexar76 commentedon Oct 23, 2026
@Paraphern, we measured that channel in our record, since it decides how often a routing pin would fire.
Snapshot of 20 October: 23,821 recorded tool-list changes on 38,126 pinned servers, 24 September to 20 October. In 12 of them, a rewrite of an existing tool's description added or removed a hint that steers the model away from web search, the client's fetch or browser, or its own knowledge. 3 added one and 9 removed one, on 10 servers from 9 publishers. 4 more tools were new and already carried such a hint. A phrase filter found 28 candidates. We read each before/after pair, and 12 of the 28 were not routing against other tools (they routed between a server's own tools, or said "does not fetch").
What the 12 show:
So, adding to your point: if routing metadata becomes a field (server-level or per tool), give it its own digest, separate from the free prose. A host can then report "routing changed" on its own and ask for re-approval on that, without asking again for every typo fix. Hints left inside descriptions would still need text classification like yours.
Data (CC BY 4.0,
changes.jsonl.gz+toolsets.jsonl.gz): https://doi.org/10.5281/zenodo.23512760AI disclosure: drafted with an AI assistant from our data. The counts were recomputed from the published files, and each candidate was checked against its before and after text.
peach420fuzz commentedon Oct 26, 2026
A separate acceptance case for server-level routing summaries is mixed-purpose servers: finding the right server must not be confused with selecting an authorized operation inside it.
Our first-party Production Desk compatibility probe exposes seven tool declarations in the legacy MCP tool list, ranging from a read-only production diagnosis to creating upload sessions and submitting/updating/deleting a brief. This is an actual observed tool catalog, not a test of host routing accuracy or tool execution (read-only evidence and limits).
For a user asking “evaluate whether this video shot is feasible, without sending any files,” a one-sentence summary of “AI/VFX production planning and brief submission” might correctly retrieve the server but leave two additional questions unanswered: which tool and whether the user authorized side effects. The MCP 2025-11-25 tools spec separates
tools/listfromtools/call, recommends human oversight, and treats annotations as untrusted unless the server itself is trusted.A small host-oriented conformance/evaluation matrix could keep these stages independent:
tools/list, does a read-only intent select a diagnosis tool rather than a submission/upload tool?This is not a proposal to require a new schema field: a concise positive and negative scope in the initial server summary plus host-side testing may be enough. It would distinguish an improvement in semantic discovery from an unintended increase in unsafe calls.
Disclosure: I work on AI/VFX production methodology at objekts. No quantitative host-routing result is being claimed here.
alexar76 commentedon Oct 27, 2026
A correction to my comment above: 12,422 is the number of changes that edited description text and added or removed no tool, not the number that touched a description. In the same snapshot, 14,164 of the 23,821 changes touched a tool description, and 18,599 touched a tool description or a description inside the input or output schema. So a host that flags every description edit would see a routing change about once per 1,200 to 1,500 flags, not once per thousand. The 12 routing changes and the rest of the comment stand. Both counts come straight from
changes.jsonl.gz: a change counts when its summary lists a modified tooldescription, or a schema path ending indescription.On @peach420fuzz's point 4: agreed. Routing metadata can tell a host which server to look at, never what it may do there. A separate digest for it, as suggested above, lets a host notice when that pointer changes without touching the approval of any call.
Written with an AI assistant.