Skip to content

feat(docs): some MCP client best practices#2582

Open
alexhancock wants to merge 1 commit intomodelcontextprotocol:mainfrom
alexhancock:alexhancock/client-best-practices-docs
Open

feat(docs): some MCP client best practices#2582
alexhancock wants to merge 1 commit intomodelcontextprotocol:mainfrom
alexhancock:alexhancock/client-best-practices-docs

Conversation

@alexhancock
Copy link
Copy Markdown
Contributor

Motivation and Context

Start a Client Best Practices section to provide info to client implementors on patterns that lead to higher quality clients

How Has This Been Tested?

Local render of the docs

Breaking Changes

N/A

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

N/A

@alexhancock alexhancock requested review from a team as code owners April 15, 2026 21:32
@github-actions github-actions bot added the documentation Improvements or additions to documentation label Apr 15, 2026

This section covers two complementary patterns that address these scaling challenges: **progressive discovery**, which controls _when_ tool definitions enter context, and **programmatic tool calling**, which controls _how_ tools are invoked.

### Progressive Discovery of Servers and Tools
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Progressive Discovery of Servers and Tools
### Avoiding context bloat using progressive discovery of Servers and Tools

Comment on lines +258 to +269
**Layer 1 — Catalog.** The client exposes a small number of meta-tools that let the model search and browse available capabilities. A `search_tools` tool accepts a natural-language query and returns a list of matching tool names with brief descriptions. This is analogous to browsing an API reference rather than reading every page.

```typescript
// The model calls a lightweight search tool
search_tools({ query: "update salesforce record" })

// Returns concise matches — names and one-line descriptions only
→ [
{ name: "salesforce.updateRecord", description: "Update fields on a Salesforce object" },
{ name: "salesforce.upsertRecord", description: "Insert or update based on external ID" }
]
```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We likely wnat to tell people that models often have been trained on this and some platforms have support for this: https://developers.openai.com/api/docs/guides/tools-tool-search, https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool, and you can either rely on the platform or use a custom implementation, but should prefer a tool search tool when available.

Comment on lines +271 to +292
**Layer 2 — Inspect.** Once the model identifies a relevant tool, it can request the full definition — input schema, output schema, and detailed documentation — for just that tool. This keeps the context focused.

```typescript
// The model inspects only the tool it needs
get_tool_details({ name: "salesforce.updateRecord" })

// Returns the complete schema for this single tool
→ {
name: "salesforce.updateRecord",
description: "Updates a record in Salesforce",
inputSchema: {
type: "object",
properties: {
objectType: { type: "string", description: "Salesforce object type" },
recordId: { type: "string", description: "Record ID to update" },
data: { type: "object", description: "Fields to update" }
},
required: ["objectType", "recordId", "data"]
}
}
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you add tools you need to be careful of invalidating prompt caching when inserting tool definitions.


A well-structured progressive discovery system operates in three layers:

**Layer 1 — Catalog.** The client exposes a small number of meta-tools that let the model search and browse available capabilities. A `search_tools` tool accepts a natural-language query and returns a list of matching tool names with brief descriptions. This is analogous to browsing an API reference rather than reading every page.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to highlight that this is one strategy and leans heavily on the learnings from progressive discovery in skills. Harnesses should use the principle, but are free to implement somewhat custom versions, e.g. using subagents to select tools instead of BM25 or regex search, or use vector embeddings, or add a short description to the system prompt initially.

They should alos probably define a threshold after which you start progressive discovery while below threshold you want the full tool description to be added to the system prompt.

Client-->>Model: Server disconnected, context freed
```

This pattern is especially valuable for general-purpose agents where the user's intent isn't known in advance. The agent can start with a minimal set of always-on servers and expand its capabilities as the conversation evolves.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its particularly valuable when combinig it with skills, only adding tools and servers in specific skills that are fully loaded by the model

| **Cache tool definitions** | Once a tool definition is loaded, keep it available for the duration of the session. |
| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |

### Programmatic Tool Calling
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Programmatic Tool Calling
### Programmatic Tool Calling / Code Mode

Comment on lines +381 to +388
<Tip>
Servers that define an
[`outputSchema`](/specification/draft/server/tools#output-schema) on their
tools dramatically improve the quality of generated APIs. When an output
schema is present, the client can produce precise return types (like
`LogEntry` above) instead of generic `any` types — giving the model type
information that leads to more correct code with fewer errors.
</Tip>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the outputSchema is missing, there are two techniques:

  • Use a generic any or string type and deal with it.
  • Pass the result of a call to a fast model like Haiku or Gemini Flash and instruct it to return a specific type, e.g. extract(mcpTool('generic_tool', params), Model.AnthropicHaiku, ExpectedType)) where ExpectedType is a definition of a type the model can generate in advance. If the conversion fails, the model can handle that as well and fallback to generic string or fail

Comment on lines +426 to +434
LLMs have been trained on vast amounts of real-world code. They are significantly more capable at writing function calls in a popular programming language than at producing the synthetic JSON format used by tool-calling protocols. This has several practical consequences:

| Benefit | Explanation |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Better tool selection** | Models handle larger tool catalogs more accurately when tools are presented as typed functions with doc comments than as tool-call JSON schemas. |
| **Data stays out of context** | Intermediate results flow between tools inside the sandbox. The model only sees what the code explicitly logs or returns, dramatically reducing token consumption. |
| **Batched execution** | Multiple tool calls execute in a single round trip. A script that reads five files and writes a summary makes one trip to the model instead of eleven. |
| **Native control flow** | Loops, conditionals, error handling, and retries are expressed in code rather than requiring multiple model turns to orchestrate. |
| **Composability** | The model can define helper functions, reuse variables, and build up complex operations — things that are awkward or impossible with sequential tool calls. |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is true. The real value is that you are getting composition without having to go through inference. LlMs have also been trained on the specific tool calling protocol quite extensively.

);
```

**Step 3 — The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the client. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output — a single summary line — returns to the model.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to dig into this more. This is actually a big hurdle for harness implementors. Sandbox selection is hard and knowing which one to pick. Should they pick Deno/V8 Isolates, Monty (Pydantic's Python Sandbox), mlua? We should at least list some options here.

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

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants