Skip to content
claude-certified-architect

Domain 2 · 18% of the exam

Tool Design & MCP Integration

Covers how to design tool interfaces that LLMs can reliably use, how to handle errors from tools, how to distribute tools across agents, and how MCP servers are configured and operate.


2.1 Tool Interface Design

Tool Descriptions Are Everything

Tool descriptions are the primary mechanism Claude uses to decide which tool to call. The description is not just documentation — it's the selection criteria.

Bad description (leads to unreliable selection):

{
  "name": "search",
  "description": "Search for things"
}

Good description:

{
  "name": "search_knowledge_base",
  "description": "Search the internal knowledge base for support articles, FAQs, and troubleshooting guides. Use this when the customer asks a question that may have an existing documented answer. Input should be a natural language query describing the customer's issue. Returns matching articles ranked by relevance with titles, snippets, and article IDs."
}

What to Include in Tool Descriptions

  1. Purpose — What the tool does and when to use it
  2. Input format — What the input should look like, with examples
  3. Output format — What the tool returns
  4. Edge cases — What happens with invalid inputs or no results
  5. Differentiation — When to use THIS tool vs similar tools

The Full Tool Definition Schema

{
  "name": "get_weather",
  "description": "Get the current weather in a given location. Returns temperature, conditions, humidity, and wind speed. Use this when the user asks about current weather conditions. For forecasts, use get_forecast instead.",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "The city and state/country, e.g. 'San Francisco, CA' or 'London, UK'"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Temperature unit. Defaults to celsius if not specified."
      }
    },
    "required": ["location"]
  }
}

Strict Mode

Strict mode guarantees Claude's tool inputs conform exactly to the schema:

{
  "name": "extract_invoice",
  "strict": true,
  "input_schema": {
    "type": "object",
    "properties": {
      "vendor_name": { "type": "string" },
      "invoice_number": { "type": "string" },
      "total_amount": { "type": "number" },
      "currency": { "type": "string", "enum": ["USD", "EUR", "GBP"] }
    },
    "required": ["vendor_name", "invoice_number", "total_amount", "currency"],
    "additionalProperties": false
  }
}

Requirements for strict mode:

  • additionalProperties: false must be set
  • All properties must be in required
  • Only a subset of JSON Schema is supported (see Domain 4)

Anti-Pattern: Ambiguous Tool Overlap

Having two tools with near-identical descriptions causes misrouting:

Tool 1: "analyze_content" — "Analyze content for insights"
Tool 2: "analyze_document" — "Analyze a document for insights"

Claude can't reliably distinguish between these. Fix: rename to clearly differentiate purpose, or merge into one tool.


2.2 Structured Error Responses

MCP Error Structure

When tools fail, the error response should be structured, not a generic string:

{
  "isError": true,
  "errorCategory": "transient",
  "isRetryable": true,
  "description": "Database connection timed out after 30 seconds. The query was for customer #12345's order history.",
  "partial_results": null,
  "attempted_action": "SELECT * FROM orders WHERE customer_id = 12345"
}

Error Categories

Category Retryable? Examples Agent Action
Transient Yes Timeouts, service unavailability, rate limits Retry with backoff
Validation No (without input changes) Invalid email format, missing required field Fix input, retry
Business No Refund exceeds policy limit, account frozen Escalate or inform user
Permission No Insufficient access, expired token Escalate

What the Agent Should Do with Errors

  1. Transient errors: Subagents should implement local recovery (retry with backoff). Only propagate if recovery fails.
  2. Validation errors: Claude should examine the error, fix the input, and retry.
  3. Business errors: Claude should explain the policy to the user or escalate.
  4. Permission errors: Claude should escalate to a human.

Anti-Patterns (Wrong Answers)

  1. Generic error messages: "Operation failed" — hides the cause and prevents intelligent recovery
  2. Silent suppression: Returning { "results": [] } when the search engine is down. This makes it look like there are no results when actually the search didn't work. The agent will confidently tell the user "I found nothing" when the truth is "I couldn't search."
  3. Generic status messages: "search unavailable" without context about what was attempted
  4. Terminating on single failure: Killing an entire multi-step workflow because one step failed, instead of using partial results or trying alternatives

Distinguishing Empty from Failed

This is a specific exam concept:

Situation What Happened Correct Response
Search returns [] with status 200 Valid query, no matches "No results found for that query"
Search returns timeout error Query didn't execute "I wasn't able to search right now, let me try again"

These must be handled differently. The first is information. The second is an error.


2.3 Tool Distribution and tool_choice

The 4-5 Tool Maximum

Each agent should have 4-5 tools maximum. Giving an agent 18 tools degrades selection reliability — Claude may pick the wrong tool, call unnecessary tools, or struggle to reason about which to use.

Solution for complex systems: Distribute tools across specialized subagents.

Instead of:
  One agent with 18 tools

Do:
  Coordinator with: Agent, AskUser
  Search subagent with: search_web, search_docs, search_db
  Analysis subagent with: analyze_text, extract_entities, summarize
  Action subagent with: send_email, create_ticket, update_record

This is the exam's answer, and the underlying principle (one agent, one focused job) is still correct. The current numbers from Anthropic's docs are worth knowing alongside it.

Anthropic's current published guidance is that tool-selection accuracy degrades once an agent exceeds roughly 30–50 available tools, and that a typical multi-server MCP setup (GitHub, Slack, Sentry, Grafana, Splunk) burns ~55k tokens of context in tool definitions before any work happens. The fix is no longer "split into subagents" alone — it's the tool search tool, which is GA on the Claude API.

"tools": [
  { "type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex" },
  { "name": "get_weather", "description": "...", "input_schema": { ... },
    "defer_loading": true }
]

How it works:

  1. You still send every tool definition on every request. defer_loading controls what enters the context window, not what you transmit.
  2. Non-deferred tools load into context immediately; deferred ones don't.
  3. When Claude needs something else, it calls the search tool. The API returns tool_reference blocks and expands them into full definitions inline — the system-prompt prefix is untouched, so prompt caching survives.
  4. Two variants: tool_search_tool_regex_20251119 (Claude writes Python re.search() patterns, max 200 chars) and tool_search_tool_bm25_20251119 (natural-language queries, max 500 chars). Both search names, descriptions, argument names, and argument descriptions.
Rule Detail
Never defer everything At least one tool must have defer_loading: false, normally the search tool itself. Otherwise: 400, "All tools cannot be deferred"
Keep a hot set Leave your 3–5 most-used tools non-deferred so Claude can call them without a search round-trip
Limits Up to 10,000 deferred tools per request; searches return 5 matches by default (Claude may set limit, 1–10,000)
Cache A tool with defer_loading: true can't also carry cache_control — 400. Put the breakpoint on a non-deferred tool
Composability Works with strict: true; the grammar builds from the full toolset
MCP Don't set defer_loading per tool — set it on the mcp_toolset entry's default_config
Models Both variants on every current model (Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5) and back to Opus 4.5 / Sonnet 4.5 / Haiku 4.5; not on Opus 4.1 or earlier

When to use it: 10+ tools, tool definitions over 10k tokens, accuracy dropping as the toolset grows, or aggregating multiple MCP servers. When not to: fewer than 10 tools, every tool used every request, or definitions totalling under ~100 tokens.

Reconciling the two: the exam's "4-5 tools" is about how many tools are in front of the model at decision time. Tool search doesn't raise that number — it keeps it at 3–5 while letting the catalog behind it grow to thousands.

tool_choice Parameter

Controls how Claude interacts with tools:

Value Behavior Use Case
"auto" (default) Claude may call a tool OR respond with text General conversation with optional tool use
"any" Claude MUST call a tool, but can choose which Guarantee structured output
{"type": "tool", "name": "specific_tool"} Claude MUST call this exact tool Force a specific operation first
"none" Claude cannot call any tool; text only Force a text answer while keeping tool definitions in context

Any tool_choice value can also carry "disable_parallel_tool_use": true to cap Claude at a single tool call per response (by default it may emit several tool_use blocks at once).

Fable 5.1 / Mythos 5.1 (since 2026-09-01), Opus 5.5 (since 2026-09-22), and Sonnet 5.5 (since 2026-09-28): forced tool use is gone. {"type": "any"} and {"type": "tool", "name": ...} return 400 invalid_request_error (tool_choice: type "tool" and "any" are not supported for this model) — on count_tokens and in the Batches API too. auto and none still work, and disable_parallel_tool_use still works with auto. The replacements, in order of preference:

  1. tool_choice: {"type": "auto"} plus an explicit instruction naming the tool (in the user turn, or in a mid-conversation role: "system" message when the app requires the call), with strict: true on the tool so the arguments still match your schema.
  2. Structured outputs (output_config.format, Domain 4 §4.3) when the forced call only existed to get JSON back.

Haiku 5.5 is now the only current model that accepts forced tool_choice — but a forced call skips thinking: the response starts with the tool call and carries no thinking block, so use auto plus an instruction when you want it to reason before calling. Sonnet 5, Opus 5 (both legacy), and the 4.x family also keep all four values, so the exam's "any guarantees a tool call" answer is still correct as a concept — but it now holds on only one of the four current models. Check client.models.retrieve(id).capabilities before assuming.

When to Use Each

"auto" — Most conversations. Claude decides whether a tool is needed.

"any" — When you need guaranteed structured output. Example: You have multiple extraction schemas (invoice, receipt, contract) and you want Claude to pick the right one and always return structured data.

Forced specification — When a specific tool must run first. Example: Always call extract_metadata before any enrichment step.

"none" — When a turn must produce prose (e.g., a final summarization pass) without stripping the tool definitions from the request.

# Force Claude to call a specific tool (Haiku 5.5, and legacy Sonnet 5 / Opus 5 / 4.x —
# 400 on Fable 5.1, Opus 5.5, and Sonnet 5.5)
response = client.messages.create(
    model="claude-haiku-5-5",
    messages=[...],
    tools=[...],
    tool_choice={"type": "tool", "name": "extract_metadata"}
)

2.4 MCP Server Configuration

What is MCP?

The Model Context Protocol is an open standard for connecting AI models to external data sources and tools. It provides a standardized way for Claude to interact with databases, APIs, file systems, and other services.

Architecture

MCP architecture: a host such as Claude Code runs MCP clients, each with a one-to-one connection to an MCP server like GitHub or Postgres.
  • Host: The AI application (Claude Code, an IDE) that coordinates MCP clients
  • Client: A component within the host that maintains a 1:1 connection to an MCP server
  • Server: A program that provides context (tools, resources, prompts) to the client

Transport Types

Transport type in config How It Works When to Use
stdio "stdio" (implied when the entry has command) Standard input/output Local process communication. No network overhead. Most common for local tools.
Streamable HTTP "http" (alias: "streamable-http") HTTP POST for client→server, optional SSE for streaming Remote servers, cloud-hosted tools. The only remote transport that supports OAuth
SSE (legacy) "sse" Server-Sent Events endpoint Services that still expose only an SSE endpoint
WebSocket "ws" Persistent bidirectional connection Remote servers that push events unprompted. Header-only auth, no OAuth

Config gotcha: an entry with a url but no type is a configuration error — Claude Code reads a typeless entry as a stdio server, skips it, and reports has a "url" but no "type". Always set type on remote servers.

MCP Protocol Details

  • Uses JSON-RPC 2.0 message format
  • Classic lifecycle (protocol revisions up to 2025-11-25):
    1. initialize — Capability negotiation (what the server supports)
    2. notifications/initialized — Client confirms it is ready
    3. tools/list — Client discovers available tools
    4. tools/call — Client invokes a tool

The 2026-07-28 revision makes MCP stateless, and the handshake above is gone:

2025-11-25 and earlier 2026-07-28
initialize / notifications/initialized handshake per connection No handshake — every request carries its protocol version and client capabilities in _meta (io.modelcontextprotocol/protocolVersion, .../clientCapabilities)
Version and capabilities agreed once at connect time Servers must implement server/discover (supported versions, capabilities, identity); clients may call it first, or use it as a backward-compatibility probe
Protocol-level sessions (Mcp-Session-Id header on Streamable HTTP) Sessions and Mcp-Session-Id removed; servers that need cross-call state mint explicit handles
Server-initiated requests (roots/list, sampling/createMessage, elicitation/create) Multi Round-Trip Requests: the server returns an interim result with resultType: "input_required", the client gathers the input and retries; every result carries resultType ("complete" for ordinary ones)

The same revision deprecates Roots, Sampling, and Logging (they keep working through a deprecation window of at least twelve months, but new implementations shouldn't adopt them: pass directories via tool parameters, resource URIs, or server configuration instead of Roots; call an LLM provider's API directly instead of Sampling; log to stderr or OpenTelemetry instead of Logging), reclassifies the legacy HTTP+SSE transport as Deprecated, and deprecates OAuth Dynamic Client Registration in favor of Client ID Metadata Documents. URL-mode elicitation — a server asking the client to open a browser-based flow instead of a form — dates from 2025-11-25; 2026-07-28 only removed its notifications/elicitation/complete / elicitationId plumbing, since elicitation now rides on Multi Round-Trip Requests.

Claude Code asks HTTP, stdio (every install since v2.1.292), claude.ai connector (v2.1.295), and cloud / Remote Control connector (v2.1.299) servers whether they speak 2026-07-28, uses it with those that do, and connects to the rest on the classic handshake; MCP_PROTOCOL_NEGOTIATION=legacy keeps every server on the handshake. It supports URL-mode elicitation on 2026-07-28 connections (v2.1.281) and on 2025-11-25 servers (v2.1.287 — a server that stops connecting after that can set "bareElicitationCapability": true in its entry).

Server Primitives

Primitive What It Is Example
Tools Executable functions search_database, create_issue
Resources Data sources (read-only content catalogs) File listings, database schemas
Prompts Interaction templates Pre-built prompt workflows

Client Primitives

Primitive What It Is
Sampling Server requests an LLM completion from the client — Deprecated since 2026-07-28
Elicitation Server requests user input from the client — form mode, or URL mode (open a browser flow, since 2025-11-25); on 2026-07-28 it arrives as a Multi Round-Trip input_required result
Roots Server asks the client which URIs or filesystem boundaries it may operate within — Deprecated since 2026-07-28

Installation Scopes

Three scopes, and the file a server lands in depends on the scope — not on which file you happened to edit.

Scope Loads in Shared with the team? Stored in
Local (default) Current project only No ~/.claude.json, keyed by project path
Project Current project only Yes, via version control .mcp.json in the project root
User All your projects No ~/.claude.json
Managed (admin) Every session in the org Yes, pushed by IT managedMcpServers in managed settings (v2.1.259+) — org-provided servers users can't remove

Note the naming trap: MCP local scope lives in ~/.claude.json (home directory), while general local settings live in .claude/settings.local.json (project directory). They are unrelated files.

Adding servers from the CLI:

# Remote HTTP server, shared with the team
claude mcp add --transport http --scope project shared-api https://example.com/mcp

# Remote server with a static auth header
claude mcp add --transport http secure-api https://api.example.com/mcp \
  --header "Authorization: Bearer $TOKEN"

# Local stdio server — everything after -- is passed to the server untouched
claude mcp add airtable --env AIRTABLE_API_KEY=YOUR_KEY -- npx -y @example/mcp-server

# Paste a config block written for another MCP client
claude mcp add-json example '{"type":"http","url":"https://mcp.example.com/mcp"}'

Project-Level Configuration (.mcp.json)

This file lives in the project root and is version-controlled:

{
  "mcpServers": {
    "shared-api": {
      "type": "http",
      "url": "https://example.com/mcp",
      "headers": { "Authorization": "Bearer ${API_TOKEN}" },
      "timeout": 600000
    },
    "postgres": {
      "command": "npx",
      "args": ["@modelcontextprotocol/server-postgres"],
      "env": {
        "DATABASE_URL": "${DATABASE_URL}"
      }
    }
  }
}

Key details:

  • ${DATABASE_URL} — Environment variable expansion. The actual secret is NOT in the config file. Use ${VAR:-default} when the variable may be unset.
  • command + args — How to start the MCP server process (stdio transport)
  • timeout — per-server tool execution timeout in milliseconds; overrides MCP_TOOL_TIMEOUT for that server
  • Claude Code prompts for approval before using project-scoped servers from .mcp.json in interactive sessions. claude -p, Agent SDK sessions, and cloud sessions can't show that prompt and load them without asking — use disabledMcpjsonServers or --setting-sources to keep a server out of headless runs; CLAUDE_CODE_MCP_STARTUP_WAIT_MS bounds how long the first non-interactive turn waits for servers to connect (0 = don't wait — except that servers named in --allowedTools or by an mcp_tool hook still get up to 2 seconds, v2.1.284+)
  • Some server names are reserved (workspace, claude-in-chrome, computer-use, Claude Preview, Claude Browser, and — in cloud sessions and on self-hosted runners only — widgets, v2.1.285+) and will be skipped with a warning; claude mcp add rejects them outright
  • /mcp reconnect all retries every server that failed or needs authentication (v2.1.284+)
  • Permission rules name servers exactly: since v2.1.299, mcp__a allows only the server a, not a__b or a_ — write mcp__<server>__* with the server's full normalized name

Authentication for Remote Servers

HTTP and SSE servers support OAuth — run /mcp in-session (or claude mcp add then authenticate) and Claude Code handles the browser flow and token refresh. For services without OAuth, pass a static token via headers, or generate one at connect time with headersHelper (a command whose stdout becomes the header value) when the credential is short-lived. WebSocket servers are header-only.

Tool Search Is On by Default in Claude Code

MCP tool definitions are deferred by default: only tool names and server instructions load at session start, and Claude searches for the rest on demand. That is why Claude Code imposes no per-server tool cap — the practical limit is your context budget.

ENABLE_TOOL_SEARCH Behavior
(unset) All MCP tools deferred, loaded on demand
true Force deferral (sends the beta header even through proxies)
auto / auto:N Load upfront while deferred definitions total under 10% (or N%) of the context window; defer once past it
false Load every MCP tool upfront

Set "alwaysLoad": true on a server entry to exempt it from deferral when Claude needs its tools on every turn, or "alwaysLoad": false to keep all its tools behind tool search — even ones the server's author marked to load upfront with "anthropic/alwaysLoad" in a tool's _meta (v2.1.287+; leave the key out and those marked tools load upfront). Server authors should write good server instructions — with tool search on, those instructions are how Claude decides whether to search your server at all.

Claude Code cuts long text, and Claude sees only what comes before the cut:

Text Default limit
Server instructions, and the description of a tool that loads upfront 4,096 characters (2,048 before v2.1.296)
The description of a tool loaded through tool search 16,384 characters (v2.1.295+)
Descriptions and instructions from Anthropic's first-party servers at api.anthropic.com Not cut (v2.1.299+)

Tool search matches only against the part within the shorter limit, so put the words Claude would search for — and anything critical — near the start. CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH (v2.1.280+) replaces both limits with one number — 8192, for example, raises the upfront limit but lowers the tool-search one.

Output Limits

Claude Code warns when MCP tool output exceeds 10,000 tokens and truncates at 25,000 tokens by default. Raise the ceiling with MAX_MCP_OUTPUT_TOKENS; the warning threshold is fixed. This is the mechanical reason tool-output trimming (Domain 5) matters — a chatty server can silently lose the tail of its own response.

MCP Resources

Resources expose content catalogs to reduce exploratory tool calls. Instead of Claude blindly searching, it can browse available resources:

{
  "resources": [
    {
      "uri": "file:///project/docs/api-reference.md",
      "name": "API Reference",
      "mimeType": "text/markdown"
    }
  ]
}

2.5 Built-in Tools

Claude Code's built-in tools and when to use each:

Tool Purpose When to Use
Grep Search file contents for patterns Finding function definitions, error messages, imports, string patterns
Glob Find files by name/extension Locating files: **/*.test.ts, src/**/config.*
Read Load full file contents Reading a specific file after finding it with Grep/Glob
Write Create new files Creating new files that don't exist
Edit Targeted modifications Changing specific sections of existing files (uses unique text matching)
Bash Run terminal commands git, npm, running scripts, system commands
WebSearch / WebFetch Search the web; fetch and read a URL Looking up current docs, checking an API's behavior
Agent Spawn a subagent Delegating bounded or noisy work (see Domain 1 §1.3)
Skill Invoke a skill by name Running a packaged workflow
NotebookEdit Edit Jupyter notebook cells .ipynb files
AskUserQuestion Ask the user a blocking question Decisions only the user can make
ToolSearch Load a deferred tool's schema on demand The client-side counterpart of the API's tool search — MCP and rarely used built-in tools ship as names only until Claude searches for them
SendMessage / ListAgents Message a running or completed subagent, teammate, or another session Resuming a subagent with its context intact (Domain 3 §3.4); cross-session messaging
Workflow Run a dynamic-workflow script that orchestrates many subagents Only when the user opts in ("use a workflow", ultracode) — see Domain 1 §1.3

Codebase Exploration Pattern

The correct pattern for exploring an unfamiliar codebase:

1. Grep for entry points (main functions, route definitions, exports)
2. Read the entry point files
3. Follow imports — Grep for referenced modules
4. Read those files
5. Build understanding iteratively

Anti-pattern: Reading all files upfront. This wastes context and doesn't build understanding.

When Edit Fails

The Edit tool works by matching unique text strings. If the text isn't unique in the file, the edit fails.

Fallback: Use Read to get the full file, then Write to replace the entire file with the modified version.


2.6 Anthropic-Defined and Server-Side Tools

Not every tool is one you write. Some are Anthropic-defined (schema-less: you declare a type and a name, and Claude knows the interface), and some are server-side (they execute on Anthropic's infrastructure — results come back as content blocks in the same response, with no client-side execution loop).

Tool type Client- or server-side Result block
Web search web_search_20260209 Server web_search_tool_result
Web fetch web_fetch_20260209 Server web_fetch_tool_result
Code execution code_execution_20260521 Server bash_code_execution_tool_result (.content.stdout)
Tool search (regex / BM25) tool_search_tool_regex_20251119 / ..._bm25_20251119 Server tool_search_tool_result
Memory memory_20250818 Client (you implement the file ops) standard tool_result
Bash bash_20250124 Client standard tool_result
Text editor text_editor_20250728 Client standard tool_result

Details worth carrying into an exam or a design review:

  • Version the type string. web_search_20260209 / web_fetch_20260209 add dynamic filtering and need Opus 4.6+ / Sonnet 4.6+; older models use the basic web_search_20250305 / web_fetch_20250910. Don't also declare code_execution alongside the _20260209 variants — they run code under the hood, and a second execution environment confuses the model.
  • Web fetch only fetches URLs already present in the conversation. It is not a crawler.
  • Server-tool errors do not raise. They return HTTP 200 with an error object inside the result block (e.g. {"error_code": "max_uses_exceeded"}). For web search, a success content is a list and an error content is an object — branch on that before indexing. This is the same "empty vs failed" distinction from §2.2, at the API layer.
  • pause_turn is how a server-side tool loop tells you it hit its internal cap. Send the response back to continue (Domain 1 §1.1).
  • Bash and text editor are schema-less. Declaring a custom tool of your own named "bash" with an input_schema creates a different tool.
  • Advisor tool: its model must be Sonnet 4.6 or better and at least as capable as the request's top-level (executor) model (e.g. executor claude-sonnet-5-5 → advisor claude-opus-5-5). An invalid pair returns 400 — and the matrix is per model, not a simple ranking: a Sonnet 5.5 executor rejects Opus 4.8, Opus 4.7, Sonnet 5, and Haiku 5.5 advisors that a Sonnet 5 executor accepts. Fable / Mythos 5 and 5.1, Opus 5 / 5.5, Sonnet 5.5, and Haiku 5.5 advisors return their advice encrypted, as an advisor_redacted_result block your client can't read; round-trip it verbatim and the server decrypts it into the executor's prompt next turn. Only older advisors (Opus 4.8, for example, where the executor accepts it) return the plaintext advisor_result you could log.
  • Computer use moved to a toolset. On the Claude API and Google Cloud, Claude 5.5-and-later models accept only computer_toolset_20260801 — the earlier computer_20251124 / computer_20250124 tools return 400 — while on Bedrock Opus 5.5, Sonnet 5.5, and Haiku 5.5 still accept computer_20251124. Since 2026-10-07 the Python and TypeScript SDKs have beta browser and computer toolset classes: you subclass one and write the methods against your own browser or desktop automation (or use a provider's driver); the SDK runs the tool loop, the URL and file policies you set, and your approval callback.

Parallel Tool Use

By default Claude may emit several tool_use blocks in one assistant message. Execute them concurrently and return all tool_result blocks in a single user message. Splitting them across multiple messages silently teaches Claude to stop making parallel calls. For a tool that failed, return its tool_result with is_error: true — never drop it.

Programmatic Tool Calling

Claude can call your custom tool from inside the code execution sandbox instead of round-tripping through the conversation — useful when a tool returns large results that would otherwise flood the context. Declare {"type": "code_execution_20260120", "name": "code_execution"} and set "allowed_callers": ["code_execution_20260120"] on the custom tool. When responding to a pending programmatic call, the user message must contain only tool_result blocks — no text. Not compatible with strict: true, disable_parallel_tool_use, forced tool_choice, or MCP tools.

MCP from the API Side: the MCP Connector

Domain 2's MCP material is written from Claude Code's perspective (a host that manages its own clients). The Messages API can also connect to remote MCP servers directly. It needs both halves — passing mcp_servers alone is a validation error:

client.beta.messages.create(
    model="claude-opus-5-5",
    betas=["mcp-client-2025-11-20"],
    mcp_servers=[{"type": "url", "url": "https://mcp.example.com/mcp", "name": "example"}],
    tools=[{"type": "mcp_toolset", "mcp_server_name": "example"}],
    messages=[...],
)

Set defer_loading once on the mcp_toolset entry's default_config (or per tool in configs) rather than on individual tool definitions.

Changing Tools Mid-Conversation Without Breaking the Cache

The tools array sits earlier in the cached prefix than system, so editing it between turns invalidates the cache for the whole conversation (Domain 4 §4.8). On the models that support mid-conversation system messages (Fable 5 / 5.1, Mythos 5 / 5.1, Opus 5.5, Opus 5, Opus 4.8, Sonnet 5.5, Haiku 5.5 — not Sonnet 5), declare the full tool set up front and then offer or withdraw tools with tool_addition / tool_removal blocks inside a role: "system" message (beta; each block's tool is a tool_reference by name, or mcp_tool_reference / mcp_toolset_reference for connector tools — naming a tool that isn't in tools is a 400, tool_reference_unresolved). The reference-only header mid-conversation-tool-changes-2026-07-01 works on the Claude API, Bedrock, and Google Cloud; on the Claude API, inline-tools-2026-09-15 covers both references and inline definitions (below). A tool declared with defer_loading: true stays withheld until a tool_addition surfaces it. On Fable 5.1, Opus 5.5, Sonnet 5.5, and Haiku 5.5 this is also the only history-safe way to change tools, because rebuilding tools counts as editing earlier turns and invalidates later thinking blocks.

Placement caveat on Sonnet 5.5 / Haiku 5.5: both are trained to resist prompt injection through tool results, so they can treat a system message placed right after a tool_result as untrusted text and ignore it. Relay a user's mid-turn words as a text block after the last tool_result in the same user message, and keep harness notices in a separate system message after that. Never put tool output or retrieved documents in a system message on any model — that hands third-party text operator authority.

Defining Tools in a Message (beta inline-tools-2026-09-15, since 2026-09-22)

The reference form assumes you knew every tool at the first request. The inline form lets a tool_addition block carry the full definition instead:

{
  "role": "system",
  "content": [{
    "type": "tool_addition",
    "tool": {
      "type": "tool_definition",
      "definition": {
        "name": "db_query",
        "description": "Run a read-only SQL query against the analytics database.",
        "input_schema": {"type": "object", "properties": {"sql": {"type": "string"}}, "required": ["sql"]}
      }
    }
  }]
}

Send a different definition under the same name and it replaces the earlier one from that point on — that is how you change a schema, or move a server tool to a newer version, without touching tools or the prompt cache (reusing the name of a different type of tool is a 400, tool_name_conflict). With the mcp-client-2026-09-15 header as well, the definition can be an mcp_toolset; the response then opens with one mcp_tool_listing block per server it fetched — send it back to pin that tool list, and don't read content[0] blindly. It works on every model that supports mid-conversation tool changes, Opus 5.5, Sonnet 5.5, and Haiku 5.5 included — on the Claude API, where the docs offer the inline-tools-2026-09-15 and mcp-client-2026-09-15 headers (on Bedrock and Google Cloud, stick to reference-only changes).

Rules of thumb: declare what you know up front (in tools, deferred if the model shouldn't see it yet) and define inline only what is unknown at the first request or changes later; keep at least one non-deferred tool in tools, or the first inline definition changes the start of the rendered prompt and costs a full cache miss; cache_control goes on the block or in the definition, not both, and a deferred definition can't carry one. Limits: 10,000 deferred or message-defined tools, and 4 MB of definitions sent after the first user message (available_tools_limit_exceeded). After an on-demand compaction, tool changes inside the summarized range carry over only when the compaction request also sent this header — the block then records their net effect in a tool_changes field (Domain 5 §5.7).


Domain 2 Practice Questions

Q1: An agent has 18 tools configured and is frequently selecting the wrong tool. What is the most effective solution?

  • A) Improve all 18 tool descriptions to be more detailed
  • B) Distribute the tools across specialized subagents with 4-5 tools each
  • C) Set tool_choice to "any" to force tool usage
  • D) Add examples to the system prompt showing correct tool selection
Show answer

Answer: B — The 4-5 tool maximum per agent is the key architectural principle. Even with better descriptions, 18 tools degrades selection reliability.

Q2: A search tool returns an empty array. What should the agent communicate to the user?

  • A) "The search service is currently unavailable"
  • B) "No results were found matching your query"
  • C) Check the response status to distinguish between "no results" and "search failed"
  • D) Retry the search with different parameters
Show answer

Answer: C — The agent must distinguish between a valid empty result (status 200, no matches) and a failed search (error/timeout). These require fundamentally different responses.

Q3: Where should MCP server credentials be stored in a project using .mcp.json?

  • A) Directly in the .mcp.json file
  • B) In environment variables referenced via ${VAR_NAME} syntax
  • C) In a separate .mcp-secrets.json file
  • D) In the project's package.json
Show answer

Answer: B — .mcp.json supports environment variable expansion. Credentials should never be committed to version control.

Q4: A platform team aggregates six MCP servers, exposing about 200 tools. Tool definitions consume ~55k tokens before any work starts, and tool selection has become unreliable. What is the current recommended fix?

  • A) Raise max_tokens so there is room for the definitions
  • B) Enable the tool search tool, keep it and the 3-5 most-used tools loaded, and mark the rest defer_loading: true
  • C) Set tool_choice: "any" so Claude is forced to commit to a tool
  • D) Set defer_loading: true on every tool including the search tool
Show answer

Answer: B — Tool search keeps only a small hot set plus the search tool in context and loads the rest on demand via tool_reference blocks, cutting definition tokens by ~85% while preserving prompt caching. Deferring every tool, the search tool included, is a 400 error: at least one tool must stay non-deferred. Splitting across subagents (§2.3) is still valid, but tool search is what makes a 200-tool catalog workable inside one agent.

Q5: An extraction service forces its schema with tool_choice: {"type": "tool", "name": "extract_invoice"} and is being moved from Claude Opus 5 to Claude Fable 5.1. What happens, and what is the right fix?

  • A) Nothing changes — forced tool use works on every current model
  • B) The request returns 400; switch to tool_choice: "any" instead
  • C) The request returns 400; use tool_choice: "auto" with an explicit instruction and strict: true, or structured outputs via output_config.format
  • D) The call silently falls back to text output
Show answer

Answer: C — Fable 5.1 and Mythos 5.1 reject both forced forms (any and tool) with a 400. auto plus an instruction keeps the tool call, strict: true keeps the arguments schema-valid, and structured outputs replace the pattern entirely when the tool only existed to return JSON. Opus 5.5 and Sonnet 5.5 reject it exactly as Fable 5.1 does, while Haiku 5.5 (and legacy Sonnet 5 / Opus 5) still accept it — so this is a per-model check, not a global rule, and three of the four current models fail it.