Tool Description: Weak vs. Strong
Test: does the description give the model a trigger condition (when to call) and a boundary (when not to), or just a summary of what the tool does?
| Weak Pattern | Strong Pattern | Why It Matters |
|---|---|---|
| "Handles order-related queries" (on two different tools) | "Looks up order status by ID. Call when the user asks about an order they already placed. Do not use to create new orders." | Identical/overlapping descriptions give the model no way to discriminate |
| "Get current weather for a location" | "Call this when the user asks about current conditions or the forecast for a specific place" | Trigger conditions matter more on newer, more tool-conservative models |
| Positive description only | Positive description + explicit negative case ("do not use for X") | Naming what a tool is *not* for often prevents miscalls better than more positive detail |
| One do-everything tool with a mode/action enum | Several tightly-scoped tools (search_orders, create_order, cancel_order) | Less inference work for the model = fewer selection errors |
- Fix for wrong-tool selection: tighten descriptions to be specific and mutually exclusive - not a different model, lower temperature, a fixed seed, or a few-shot example bolted onto the same ambiguous description.
- Verify a fix across ~5 repeated calls, not one - tool selection has sampling variance, so a single success/failure doesn't prove a schema is fixed or broken.
- Large tool library (dozens–hundreds)? Use the tool search tool for on-demand discovery instead of loading every schema into every request.
input_schema and strict Cheat Sheet
| Field / Setting | Where It Lives | Effect |
|---|---|---|
type: "object", properties, required | Inside input_schema | Standard JSON Schema shape every tool's input follows |
Format guidance in a field's own description | Inside properties.<field> | Removes ambiguity (e.g. "ISO 8601, e.g. 2026-08-28") vs. a bare string type |
enum | Inside properties.<field> | Narrows what the model can *generate*, not just documents a constraint - use whenever values are a fixed small set |
strict: true | Top-level, sibling of name/description/input_schema (NOT a tool_choice setting) | Guarantees tool_use.input validates exactly - requires additionalProperties: false + accurate required |
- Prefer a flatter schema with clearly named fields over one that mirrors a deeply nested internal data model - every extra nesting level is another place generation can go wrong.
- Without
strict, the schema is guidance the model *usually* follows; with it, malformed/missing-field calls become structurally impossible.
MCP Primitives at a Glance
| Primitive | Shape | How It's Retrieved | Required? |
|---|---|---|---|
| Tools | Name + description + JSON Schema input (same shape as a regular tool) | Invoked with arguments; returns a tool_use/tool_result round trip | The one most exam scenarios focus on |
| Resources | Readable data (file, DB record, document) identified by a URI | Read directly into context via a conversion helper - no tool call | Optional |
| Prompts | Reusable, parameterized prompt templates | Fetched and filled in by the client | Optional |
A server is not required to expose all three - a minimal server may offer tools only. Watch for: expecting a resource read to appear as a tool_use block; it doesn't.
Local MCP Server vs. Remote MCP Connector
| Local MCP Server | Remote MCP Connector | |
|---|---|---|
| Transport | stdio subprocess, same machine | HTTP, server-side (Anthropic infra makes the connection) |
| Typical client | Claude Code / Claude Desktop | Messages API |
| Wiring | SDK conversion helpers (anthropic.lib.tools.mcp) into the Tool Runner | mcp_servers param + a tools entry of type mcp_toolset referencing it by name |
| Beta header | None needed | mcp-client-2025-11-20 |
| Common validation error | - | Passing mcp_servers without a matching mcp_toolset entry (or naming a server it doesn't reference) |
- Scoping: configure at global / per-project / per-session level; scope sensitive-system servers to exactly the project that needs them (least privilege).
- Tool-level allowlisting: set
default_config: {"enabled": false}on the toolset plus aconfigsmap enabling only the specific tools you want, rather than exposing everything a remote server offers. - Security: MCP tool/resource results are untrusted external content - treat them like a fetched web page, regardless of MCP's official-protocol status. A compromised server can embed prompt-injection instructions in a result.
tool_choice Modes
| Mode | Effect | Typical Use |
|---|---|---|
{"type": "auto"} | Claude decides whether and which tool to call (default when omitted) | Ordinary agent turns - the correct default |
{"type": "any"} | Some tool call is required; model picks which | A routing step that must hand off to exactly one specialist tool |
{"type": "tool", "name": "..."} | Forces one specific named tool | e.g. force ask_clarifying_question after an ambiguous turn |
{"type": "none"} | Suppresses tool calls entirely for that turn | Final summarization turn - clean text answer, no risk of another call |
Modifier on any mode: disable_parallel_tool_use: true caps the response to at most one tool call. Trap: defaulting to any "just to be safe" instead of auto - it forces unnecessary or premature tool calls; reserve any/forced-tool for genuinely mandatory situations.
Error Category → Model Recovery Action
| Error Category | Meaning | Correct Model Follow-Up | Auto-Retry? |
|---|---|---|---|
invalid_input | Malformed order ID, date outside valid range | Ask the user for corrected input | No - same input, same result |
not_found | Record legitimately doesn't exist | Tell the user it doesn't exist; don't imply a system fault | No |
permission_denied | Caller lacks access | Explain the limitation | No - retrying with different params won't help |
transient_failure | Timeout, rate limit, temporary outage | Brief retry with explanation is fine | Yes - the one category where retry can succeed |
- Set
is_error: trueon thetool_resultcontent block so the model knows it's a failure, not an odd success. - Never return a raw exception/stack trace - unactionable, and can leak internal file paths, schema, or library versions.
- Parallel batch with partial failure: report each
tool_resultindependently (success andis_error: truefailures alike) but return them all together in oneusermessage - never drop the failed one or split across messages.