Client query flow with an LLM: seven steps
The standard MCP client query flow is: 1) the client lists available tools from the server; 2) the user query is sent to the model along with the tool descriptions; 3) the model decides which tools, if any, to use; 4) the client executes requested tool calls through the server; 5) results are sent back to the model; 6) the model produces a natural language response; 7) the response is displayed.
Progressive tool discovery: defer injecting tools/list results
Hosts connected to many MCP servers should fetch tool definitions via `tools/list` as normal but defer injecting them into the model's context. Instead the host exposes a lightweight `search_tools` meta-tool and loads full definitions into context only when needed. Loading every definition upfront can consume ~150,000 tokens versus ~2,000 tokens with progressive discovery.
Threshold for switching to progressive discovery (1%-5% of context)
Loading all tools is fine when definitions take a small part of the context window. Clients should implement a threshold expressed as a percentage of the context window, for example 1%-5%; once tool definitions exceed that threshold, switch to progressive discovery.
Four tool-search strategies: keyword, embedding, subagent, hybrid
When the model invokes `search_tools`, the host can retrieve with: keyword-based matching (BM25, regex), which is simple and effective for descriptive names/descriptions; embedding-based vector-similarity over tool descriptions, which handles synonyms; subagent-based, where a small fast model (e.g. Claude Haiku or Gemini Flash) selects tools — usually very effective but more costly; or hybrid, combining scores or picking strategies per query.
Provider built-in tool search (OpenAI, Anthropic)
Some model providers offer built-in tool search natively — OpenAI and Anthropic both support it. Prefer the platform's tool search when available; build your own only when the provider lacks one or you need specialized retrieval such as domain-specific ranking or access-control filtering.
Three-layer discovery pattern: catalog, inspect, execute
A common progressive-discovery implementation has three layers. Layer 1 Catalog: a `search_tools` meta-tool takes a natural-language query and returns matching tool names with one-line descriptions only, e.g. search_tools({ query: "update salesforce record" }) → [{name: "salesforce_updateRecord", description: "Update fields on a Salesforce object"}, {name: "salesforce_upsertRecord", ...}]. Layer 2 Inspect: get_tool_details({ name: "salesforce_updateRecord" }) returns the full definition (inputSchema, outputSchema, docs) for that one tool. Layer 3 Execute: the model calls the tool with full knowledge of its interface. The layered principle applies regardless of the retrieval mechanism used in the catalog layer.
Dynamic server management: connect and disconnect servers on demand
Progressive discovery extends to whole servers: maintain a registry of available servers with high-level descriptions, connect to a server only when the model determines it needs that server's capabilities, and disconnect servers no longer relevant to free context. A typical flow is search_available_servers("CRM") → enable_server("salesforce") → host sends `server/discover` (returns supported versions + capabilities) then `tools/list` → later disable_server("salesforce"). Agent skill files can declare which MCP servers they need so the host connects them only when the skill is invoked.
Progressive discovery implementation guidelines table
Four guidelines for implementing progressive discovery: (1) Offer multiple detail levels — let the model choose name-only, name-and-description, or full-schema responses. (2) Cache tool definitions — memoize host-side after fetching so re-injecting later avoids another `tools/list` round trip; this cache is separate from what is in the model's context. (3) Refresh on list_changed — re-index the search catalog when a server sends `notifications/tools/list_changed`. (4) Group tools by server so the model can reason about related capabilities.
Pitfall: mutating the tools array invalidates provider prompt caching
Most model providers cache the prompt prefix including the `tools` array, so adding or removing tool definitions mid-conversation invalidates that cache and the resulting miss can cost more tokens than the definitions removed. Mitigations: append newly discovered definitions after the cache breakpoint instead of re-sorting the `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool so the array never changes; and treat server disconnection as a conversation-boundary operation rather than a per-turn one.
Programmatic tool calling (code mode): model writes code that calls tools
In programmatic tool calling, instead of each tool invocation being a round trip through the model's context, the model writes code that calls tools; the code runs in a sandbox and only the final result (typically console.log output) returns to the model. Illustrative token comparison: direct calling passes every intermediate result through the model (~100K+ tokens), while programmatic calling sends a ~200-token script to a sandbox that returns a ~15-token summary. It requires the client to implement a sandbox environment.
Generating typed sandbox stubs from MCP tool schemas
The host reads each server's tool definitions and generates typed functions from each tool's arguments and `outputSchema`. Example: function logging_getLogs(input: { level: "error"|"warn"|"info"; since: number }): Promise<{ entries: LogEntry[] }> { return mcp.callTool<{entries: LogEntry[]}>("logging_getLogs", input); }. Function names are typically namespaced by server, e.g. `logging_getLogs`, `ticketing_createIssue`.
Sandbox runtime options for code mode
Example sandbox runtimes by language: JavaScript — Deno or `isolated-vm`, hosted from Rust/Node/CLI, V8-based with fine-grained permissions that can be fully disabled; Python — Monty (experimental, pydantic), hosted from Rust, a minimal Python interpreter with no I/O by default; TypeScript — pctx (early-stage), hosted from Python/Rust, code-mode concepts as a library with low-level Rust support; Any language via Wasm — Wasmtime, hosted from Rust/C/Go, capability-based security. Regardless of sandbox the integration pattern is identical: the host injects function stubs, intercepts calls over an in-process or stdio channel so network permissions stay fully denied, and dispatches them as `tools/call` requests.
Code-mode execution architecture: sandbox, host broker, model
Programmatic tool calling has three components. The sandbox runs model-generated code with no direct network access; its only outside interface is the generated function stubs that route calls back to the host. The host acts as a broker: it receives function calls from the sandbox, maps them to the correct MCP server, executes the `tools/call`, and returns the result; authorization tokens and credentials stay with the host and are never exposed to generated code. The model sees only what the sandbox returns, typically console.log output or a final return value.
Combining progressive discovery with programmatic tool calling
The two patterns compose: the model uses discovery tools to identify which tools it needs, loads only those schemas, then writes a single script that calls multiple tools in one execution pass. This minimizes both the token cost of tool definitions and the token cost of tool results.
Host vs client vs server roles in MCP
MCP clients are instantiated by host applications (e.g. Claude.ai or an IDE) to communicate with particular MCP servers. The host is the application users interact with and manages the overall UX, coordinating multiple clients; each client handles exactly one direct communication with one server.
MCP Apps message flow between app, host and server
Typical MCP Apps sequence: the agent calls `tools/call` on the server, the server returns the tool input/result, the agent pushes the tool result to the app iframe; when the user interacts, the app sends a `tools/call` request to the agent, which forwards it to the server, and fresh data flows back to the app; the app can also send a context update to the agent. The app can request tool calls, send messages, update the model's context, and receive data from the host.
Who consumes the MCP Registry: aggregators, not host apps
The MCP Registry is intended to be consumed primarily by downstream aggregators such as MCP server marketplaces, which are expected to pull new metadata on a regular but infrequent basis (for example once per hour). Host applications should not consume the official registry directly; instead they should consume downstream registries/marketplaces via a REST API conforming to the official MCP Registry OpenAPI spec (docs/reference/api/openapi.yaml in the registry repo).
One MCP client object per server connection
MCP uses a client-server architecture in which an MCP host (the AI application, e.g. Claude Code, Claude Desktop, VS Code) creates one MCP client for each MCP server it connects to, and each client maintains a dedicated connection. Connecting to a second server instantiates an additional client object. Two clients in the same host may each hold a separate dedicated connection to the same remote server.
Host-side pattern: registry of tools across all connected servers
An AI application fetches tools from all connected MCP servers and merges them into a unified tool registry exposed to the language model; when the model emits a tool call the application finds the client owning that tool, calls `call_tool(tool_name, arguments)`, and feeds `result.content` back into the conversation. Clients that federate many servers can use progressive tool discovery instead of loading every tool upfront.
Request flow when a host calls an MCP tool
When a user asks a question in an MCP host: (1) the client sends the question to the model, (2) the model analyses the available tools and decides which to use, (3) the client executes the chosen tool(s) through the MCP server, (4) results are sent back to the model, (5) the model formulates a natural-language response, (6) the response is displayed to the user.