Skip to content

[Spec & Ecosystem] Lack of enforced Server-level routing descriptions breaks LLM tool selection under Lazy/Deferred Loading (especially when paired with Agent Skills) #3430

Open

Description

@gavincrossv1

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:

  1. In the Lifecycle / Initialization & Tools Spec:
    Clarify that while individual Tool.description fields describe atomic operations, InitializeResult.serverInfo.description (or a concise routing summary in InitializeResult.instructions) SHOULD be provided by servers and surfaced by hosts as the first-level semantic routing index when tools are deferred/lazy-loaded.
  2. Host Implementation Guidance:
    When a host implements lazy tool loading (e.g., listing available MCP servers in the system prompt or inside a ToolSearch meta-tool and deferring <toolName>.json schemas 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.)
  3. SDK Alignment:
    Official SDKs (such as Python FastMCP and the TypeScript SDK) should encourage providing a concise server description upon initialization (or fall back to extracting the module docstring) so that servers in the ecosystem actually populate serverInfo.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.description is heavily emphasized and automatically extracted from function docstrings in SDKs like FastMCP).
  • At the server level (InitializeResult.serverInfo / Implementation), only name and version are required:
    export interface Implementation extends BaseMetadata, Icons {
      version: string;
      description?: string; // Optional human-readable description, rarely populated in practice
    }
  • Because serverInfo.description is optional and framed as human/UI metadata rather than an LLM routing index, almost all MCP servers in the wild (including @playwright/mcp) only return name and version:
    # 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):
    Because playwright MCP has no description in the first-level index while agent-browser has a keyword-rich description paired with a MUST rule, the LLM immediately chooses the agent-browser Skill on turn 1. Once the multi-hundred-line SKILL.md is 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.
Image
  • Case 2: Explicitly instructing the agent to use playwright-mcp:
    When forced to use playwright-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).
Image

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:

  1. Explicitly naming the MCP server in every user prompt (e.g., "Use playwright-mcp to...").
  2. 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."
  3. Editing third-party SKILL.md frontmatter descriptions locally to remove aggressive prompt-SEO phrases (like Prefer 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

  1. AbarnaaSree commented on Oct 18, 2026

    @AbarnaaSree

    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.

  2. alexar76 commented on Oct 21, 2026

    @alexar76

    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 / instructions become 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.

  3. tanevanwifferen commented on Oct 21, 2026

    @tanevanwifferen

    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 return

    {
        subCategories: [todoItems, calendar, user] 
    }
    

    getTools('user') could return

    {
        actions: [login, logout, me]
    }
    

    getTools('user.login') could return

    {
        props: ['username', 'password']
    }
    

    Something 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

  4. Paraphern commented on Oct 22, 2026

    @Paraphern

    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.

  5. alexar76 commented on Oct 23, 2026

    @alexar76

    @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:

    • 9 changed nothing in the tool except the description. A pin over the input schema alone misses them.
    • 10 were on remote servers, which have no package version. The digest of what the client saw is the only version it has.
    • They are rare next to ordinary prose edits: 12,422 of the 23,821 changes touched a tool or parameter description. A host that flags every description edit shows a routing change about once per thousand flags.

    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.23512760

    AI 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.

  6. peach420fuzz commented on Oct 26, 2026

    @peach420fuzz

    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/list from tools/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:

    1. Candidate-server recall: with deferred tools, does the bounded server description retrieve the relevant MCP server?
    2. Tool-level precision: after loading the actual tools/list, does a read-only intent select a diagnosis tool rather than a submission/upload tool?
    3. Negative routing / capability boundary: if the user asks to render finished video, does the host avoid inferring that a preflight-only server can generate media just because its summary mentions “video production”?
    4. Authorization: even when the right server and tool are found, is the side-effecting call still blocked pending the applicable approval? Routing metadata should never be treated as that approval.

    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.

  7. alexar76 commented on Oct 27, 2026

    @alexar76

    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 tool description, or a schema path ending in description.

    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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions