Domains
D5 · Tools and MCPs
Tool-use lifecycle, tool descriptions and schemas, tool_choice, parallel tools, client- vs server-side tools, tool search, MCP fundamentals, authoring MCP servers, and configuring MCP in Claude Code and the Messages API.
This domain is roughly 5 of 53 items. It tests whether you can define tools well (the description is the single most important lever), run the tool-use lifecycle correctly, choose between client- and server-side tools, and understand and author MCP servers. The theme: tools are how Claude acts on the world – describe them precisely and connect them safely.
Learning objectives
By the end of this page you should be able to:
- Run the tool-use / function-calling lifecycle with correct JSON.
- Write effective tool descriptions and design
input_schema. - Use
tool_choice(auto/any/tool/none) and know the Fable 5.1 restriction. - Use parallel tool calls and distinguish client-side vs server-side tools.
- Use tool search +
defer_loading, programmatic tool calling, and approval patterns. - Explain MCP fundamentals and author an MCP server in Python (FastMCP) and TypeScript.
- Configure MCP in Claude Code / Desktop and via the MCP connector in the Messages API.
5.1 The tool-use lifecycle
{ "tools": [{ "name": "get_weather", "description": "Get the current weather for a city. Use when the user asks about weather, temperature, or conditions in a named location.", "input_schema": { "type": "object", "properties": { "city": {"type": "string", "description": "City name, e.g. 'Paris'"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"} }, "required": ["city"] } }]}{ "stop_reason": "tool_use", "content": [ {"type": "text", "text": "Let me check the weather."}, {"type": "tool_use", "id": "toolu_01", "name": "get_weather", "input": {"city": "Paris"}} ]}{ "role": "user", "content": [ {"type": "tool_result", "tool_use_id": "toolu_01", "content": "18C, partly cloudy"} ]}Then call the API again; Claude produces the final end_turn answer.
Errors are returned with "is_error": true in the tool_result so Claude can recover.
A complete round-trip on the wire
The four Tabs above are the four wire messages of one round-trip. Read them as a single conversation so the tool_use_id threading is unmistakable. The request declares tools; Claude answers with stop_reason: 'tool_use'; you run the function and send a tool_result referencing the same id; Claude produces the end_turn answer.
import anthropicclient = anthropic.Anthropic()
TOOLS = [{ "name": "get_weather", "description": "Get current weather for a city. Use when the user asks about " "weather, temperature, or conditions in a named location. " "Returns a short text summary. Read-only, no side effects.", "input_schema": { "type": "object", "properties": { "city": {"type": "string", "description": "City name, e.g. 'Paris'"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}, }, "required": ["city"], },}]
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
# 1. Model turn -> Claude asks for the toolresp = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=TOOLS, messages=messages,)assert resp.stop_reason == "tool_use" # NEVER parse text to decide thismessages.append({"role": "assistant", "content": resp.content})
# 2. You run the tool for each tool_use blockfor block in resp.content: if block.type == "tool_use": result = fetch_weather(**block.input) # your real function messages.append({"role": "user", "content": [{ "type": "tool_result", "tool_use_id": block.id, # MUST match the originating id "content": result, }]})
# 3. Model turn again -> Claude gives the final answerfinal = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=TOOLS, messages=messages,)assert final.stop_reason == "end_turn"print(final.content[0].text)Loop on stop_reason, not on text
The canonical agent loop is while stop_reason == 'tool_use': run tools; call again. Parsing the assistant’s prose to decide whether to continue is anti-pattern #1. When a tool fails, return {"type": "tool_result", "tool_use_id": id, "content": "Error: timeout", "is_error": true} so Claude can retry or apologise – do not silently drop the result (anti-pattern #7).
5.2 Tool descriptions – the most important lever
The description is the primary determinant of whether Claude uses a tool correctly. Invest here more than anywhere else. A good description reads like the docstring you would write for a colleague who has never seen the function.
Good vs bad tool descriptions – four pairs
| Bad | Good |
|---|---|
get_data — ‘Gets data’ | get_order — ‘Retrieve one order by its ID. Use when the user references a specific order number. Returns status, line items and totals. Read-only.’ |
search — ‘Search’ | search_kb — ‘Full-text search of the help-centre knowledge base. Use for how-to and policy questions, NOT for live account data. Returns up to 5 article snippets with URLs.’ |
update — ‘Updates a record’ | update_shipping_address — ‘Change the shipping address on an unshipped order. Fails if the order has shipped. Side effect: writes to the orders DB. Confirm with the user first.’ |
run — ‘Runs a query’ | run_sql_readonly — ‘Execute a read-only SELECT against the analytics warehouse. Rejects INSERT/UPDATE/DELETE. Use for reporting questions. Returns at most 1000 rows as JSON.’ |
Each good description answers four questions: what it does, when to use it (and when not), what it returns, and what side effects / irreversibility it carries.
| Good description | Poor description |
|---|---|
| States what the tool does, when to use it, when not to, and what it returns | ‘Gets data’ |
| Documents each parameter with type, meaning and example | Undocumented params |
| Notes side effects and irreversibility | Silent on side effects |
Exam signal
“Claude picks the wrong tool / calls it with bad arguments” → improve the tool description and input_schema (types, enums, required, examples). Reach for the description before touching temperature or the model.
5.3 input_schema design checklist
The schema is JSON Schema. It constrains what Claude may pass and, with strict: true, guarantees the shape. Design it deliberately.
- Types — give every property a
type. Use the narrowest type (integerovernumberwhen whole). - Enums — constrain closed sets with
enumso Claude cannot invent a value:{"type": "string", "enum": ["celsius", "fahrenheit"]}. required— list every argument the tool genuinely needs. Omitted-but-required arguments are a top cause of malformed calls.- Descriptions per property — describe each field, with an example. Claude reads these.
- Nullable / optional — optional params simply omit from
required; to allow an explicit null, use{"type": ["string", "null"]}. - Bounds and formats —
minimum,maximum,pattern,format: 'date'reduce garbage input. strict: true— enable strict schema adherence so outputs conform exactly (also a way to get structured behaviour on Fable 5.1 where forced tools 400).
{ "name": "create_ticket", "description": "Open a support ticket. Use only after the user confirms they want one.", "input_schema": { "type": "object", "properties": { "priority": {"type": "string", "enum": ["low", "normal", "high", "urgent"], "description": "Urgency; default to 'normal' unless the user signals otherwise"}, "summary": {"type": "string", "description": "One-line summary, e.g. 'Cannot log in'"}, "due_date": {"type": ["string", "null"], "format": "date", "description": "ISO date, or null if none"} }, "required": ["priority", "summary"] }, "strict": true}5.5 tool_choice
| Value | Behaviour | Notes |
|---|---|---|
auto | Claude decides whether to use a tool (default) | Best general default; combine with a clear instruction |
any | Claude must use one of the tools | Forces some tool; 400 on Fable 5.1 |
{type: 'tool', name: '…'} | Force one specific tool | Deterministic single-tool extraction; 400 on Fable 5.1 |
none | Disable tools for this turn | Model answers from context only |
Fable 5.1 restriction
On Fable 5.1, tool_choice: 'any' and forced {type: 'tool'} return 400. Use auto with a clear instruction, strict: true schemas, or structured outputs (output_config.format). This is a client error — do not retry it with backoff.
5.6 Parallel tool calls
When subtasks are independent, Claude can emit multiple tool_use blocks in one response. Execute them concurrently and return all tool_result blocks in the next user message (matching each tool_use_id). If the calls are dependent (result of one feeds the next), Claude will serialise them across turns instead.
tool_uses = [b for b in resp.content if b.type == "tool_use"]results = run_concurrently(tool_uses) # independent tools in parallelmessages.append({"role": "assistant", "content": resp.content})messages.append({"role": "user", "content": [ {"type": "tool_result", "tool_use_id": u.id, "content": r} for u, r in zip(tool_uses, results)]}) # ALL results in ONE user turnReturn every result, once
You must return exactly one tool_result per tool_use block, in the same user turn, each keyed to its tool_use_id. Missing or duplicated results cause a 400. To discourage parallelism when tools are stateful, set disable_parallel_tool_use: true in tool_choice.
5.7 Client-side vs server-side tools
| Type | Runs where | Examples |
|---|---|---|
| Client-side (custom) | Your code | Your APIs, DB queries, business functions |
| Server-side (Anthropic-hosted) | Anthropic | Web search, code execution, computer use, text editor, bash, memory |
Server-side tools are enabled by declaring them; Anthropic executes them and may pause the turn (pause_turn) while running.
Server-side built-in tools
| Tool | What it does | Typical use |
|---|---|---|
| Web search | Live web queries with citations | Current events, facts past the knowledge cutoff |
| Code execution | Runs code in a sandbox | Data analysis, calculation, plotting |
| Text editor | Views and edits files via a str-replace interface | Multi-file code edits |
| Bash | Runs shell commands in a sandbox | Build/test/install steps in agentic coding |
| Memory | Cross-session persistent store | Remembering user preferences across sessions |
| Computer use | Screenshots + mouse/keyboard control | Operating a GUI when no API exists |
Exam signal
“Search the live web / run code in a sandbox / edit files / operate a computer / remember across sessions” → server-side built-in tools. “Call our internal API / database / business function” → client-side custom tool.
5.8 Tool search, defer_loading and programmatic calling
- Beyond ~10 tools, use the tool search tool with
defer_loading: trueso the full tool definitions are loaded on demand rather than all upfront — reduces context bloat and wrong-tool selection (anti-pattern #8). - Programmatic tool calling lets code invoke tools directly in a controlled loop.
- Approval patterns: require human/hook approval before irreversible tool actions (see hooks, Domains 3 and 6).
# Large catalogue: defer full definitions, expose the search tool.tools = [ {"type": "tool_search_tool_20250000", "name": "tool_search"}, # lets Claude find tools on demand {"name": "get_order", "description": "...", "input_schema": {...}, "defer_loading": True}, {"name": "issue_refund", "description": "...", "input_schema": {...}, "defer_loading": True}, # ...50 more deferred tools; only the ones Claude searches for are hydrated into context]resp = client.messages.create(model="claude-sonnet-5", max_tokens=2048, tools=tools, messages=messages)The pattern keeps the active tool set small (4–5 in context at a time) even when the catalogue is large, which is exactly the fix for anti-pattern #8.
5.9 MCP fundamentals
The Model Context Protocol is an open standard (JSON-RPC 2.0) for connecting Claude to external tools and data.
| Primitive | Controlled by | Example |
|---|---|---|
| Tools | Model | Callable functions |
| Resources | Application | Files, records the app exposes |
| Prompts | User | Reusable prompt templates |
- Transports:
stdio(local subprocess) and Streamable HTTP (remote; SSE is legacy). - Capability negotiation happens on
initialize. - OAuth 2.1 secures remote servers.
Host (Claude Desktop / Code) │ JSON-RPC 2.0 ├── stdio ──► local MCP server (subprocess) └── Streamable HTTP ──► remote MCP server (OAuth 2.1)5.10 Authoring an MCP server
A minimal but complete server: it names itself, declares one tool with a typed schema and a docstring description, handles its own errors, and runs over stdio. Both SDKs below are runnable as-is.
# weather_server.py — run: python weather_server.pyfrom mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool()def get_weather(city: str, unit: str = "celsius") -> str: """Get current weather for a city.
Use when the user asks about weather, temperature or conditions in a named location. Returns a short text summary. Read-only, no side effects. """ if not city: raise ValueError("city is required") return fetch_weather(city, unit)
@mcp.resource("config://units")def default_units() -> str: """Application-controlled resource: the default unit system.""" return "celsius"
if __name__ == "__main__": mcp.run() # stdio transport by default; use mcp.run(transport='streamable-http') for remote// weather_server.ts — run: node weather_server.jsimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';import { z } from 'zod';
const server = new McpServer({ name: 'weather', version: '1.0.0' });
server.tool( 'get_weather', 'Get current weather for a city. Use when asked about weather in a named location. Read-only.', { city: z.string().describe('City name, e.g. Paris'), unit: z.enum(['celsius', 'fahrenheit']).default('celsius') }, async ({ city, unit }) => { if (!city) throw new Error('city is required'); return { content: [{ type: 'text', text: await fetchWeather(city, unit) }] }; },);
const transport = new StdioServerTransport();await server.connect(transport); // for remote: StreamableHTTPServerTransport5.11 Configuring MCP
# stdio (local subprocess)claude mcp add weather --scope project -- python weather_server.py
# HTTP (remote server)claude mcp add --transport http docs https://mcp.example.com/mcp --scope user
# list / removeclaude mcp listclaude mcp remove weatherScopes: local (this machine, private), project (shared via .mcp.json, committed), user (all your projects).
{ "mcpServers": { "weather": { "command": "python", "args": ["weather_server.py"], "env": { "WEATHER_API_KEY": "${WEATHER_API_KEY}" } }, "docs": { "type": "http", "url": "https://mcp.example.com/mcp", "headers": { "Authorization": "Bearer ${DOCS_TOKEN}" } } }}The stdio server uses command/args; the remote server uses type: 'http' + url. Secrets come from env vars — never hard-code them here.
The MCP connector lets the Messages API call a remote MCP server directly, so a hosted server’s tools are available to the model without you re-declaring each tool.
resp = client.beta.messages.create( model="claude-sonnet-5", max_tokens=1024, messages=[{"role": "user", "content": "Search the docs for retry policy."}], mcp_servers=[{ "type": "url", "url": "https://mcp.example.com/mcp", "name": "docs", "authorization_token": "Bearer …", # OAuth 2.1 token for the remote server }], betas=["mcp-client-2025-04-04"],)5.12 MCP vs custom tool vs Skill – decision table
These three ways of extending Claude are frequently confused on the exam.
| Choose | When | Runs / lives | Controlled by |
|---|---|---|---|
| Custom tool (Messages API) | You need one or a few functions for a single app; full control of execution | Your code, per request | Your API integration |
| MCP server | You want reusable tools/resources shared across apps, hosts (Code, Desktop, API) and teams | A separate process (stdio/HTTP), reused everywhere | An open protocol |
| Skill | You want to package instructions + files + scripts that load progressively on demand in Claude Code | .claude/skills/<name>/SKILL.md, loaded when relevant | A markdown file with frontmatter |
Rule of thumb: one app, few functions → custom tools; reuse across hosts/teams → MCP; packaged know-how and helper scripts for Claude Code → Skill.
Exam signal
“Share the same tools across Claude Code, Desktop and our API” → MCP. “Bundle a workflow with reference files and helper scripts that Claude loads only when needed” → Skill. “Just call our one internal endpoint from this service” → custom tool.
5.13 MCP transports, capability negotiation and OAuth 2.1
The wire details of MCP recur in exam items. MCP is JSON-RPC 2.0; a host and server negotiate capabilities on initialize, then exchange tool/resource/prompt calls over a transport.
| Aspect | stdio | Streamable HTTP |
|---|---|---|
| Where the server runs | Local subprocess | Remote endpoint |
| Auth | Local process trust / env | OAuth 2.1 |
| Best for | Local dev tools, Desktop/Code plugins | Shared/team servers, the Messages API connector |
| Legacy note | — | SSE-only transport is legacy; prefer Streamable HTTP |
Host ──initialize──► Server (capability negotiation: tools? resources? prompts?)Host ◄─capabilities─ ServerHost ──tools/list───► Server (discover model-controlled tools)Host ──tools/call───► Server (Claude invokes a tool)Host ──resources/read► Server (application-controlled data)Host ──prompts/get──► Server (user-controlled templates)Exam signal
‘Local subprocess tool’ → stdio. ‘Remote/shared server, secure it’ → Streamable HTTP + OAuth 2.1. ‘Which primitive does the model decide to call?’ → tools (resources = application, prompts = user). Capability negotiation happens on initialize.
5.14 Approval and human-in-the-loop for irreversible tools
Some tools are irreversible (refunds, deletes, sends, deploys). The correct design gates them deterministically, not by trusting the model.
| Control | Mechanism | Notes |
|---|---|---|
| Allowlist | Only expose the tools the task needs | Least privilege; keep the irreversible ones out of routine agents |
| PreToolUse hook | Block (exit 2) and route to approval | The model cannot argue past it (anti-pattern #3) |
| Approval workflow | Human sign-off before execution | For payments/deletes/deploys |
is_error on results | Report tool failures back so Claude recovers | Never drop a result silently (anti-pattern #7) |
# Gate an irreversible tool behind approval; return is_error so Claude can adapt.if block.name == "issue_refund" and block.input["amount_cents"] > 50000: result = {"type": "tool_result", "tool_use_id": block.id, "content": "Refund over $500 requires human approval; request queued.", "is_error": True}Approval is enforcement, not a prompt
‘Ask the user before refunding’ in the tool description is guidance the model may skip. The guarantee comes from a hook/approval gate plus keeping the tool out of the allowlist where it is not needed.
5.15 Common misconceptions
| Misconception | Reality | Why it matters on the exam |
|---|---|---|
| A vague description is fine if the schema is tight | The description is the primary lever for correct tool use | Wrong-tool/bad-args questions |
tool_choice: 'any' works everywhere | Fable 5.1 rejects any/forced tools (400) | A recurring breaking-change trap |
| A 400 on forced tool choice is transient | It is deterministic; change the request, do not backoff | Distinguishes layers |
| Web search is a tool you implement | Web search and code execution are server-side built-ins | Client vs server-side sorting |
| Resources are model-controlled like tools | Resources are application-controlled; prompts are user-controlled | MCP primitive mapping |
| Parallel tool results can span multiple turns | All results go in one user turn, each keyed to its tool_use_id | Protocol-shape 400s |
| More tools make selection better | Beyond ~10, selection degrades; use tool search + defer_loading | Anti-pattern #8 |
| A Skill can share tools across hosts | Skills are Claude-Code packaged know-how; cross-host reuse is MCP | Skill/MCP/tool decision |
5.16 Scenario walkthrough: a payments tool that must be safe and portable
Scenario. A fintech wants Claude to look up orders, search a help centre, and — carefully — issue refunds, and they want the same tools available in Claude Code, Claude Desktop and a production Messages API service, maintained in one place. They are on Fable 5.1 for its reasoning quality. Early attempts force the refund tool with tool_choice: {type: 'tool', name: 'issue_refund'} (getting 400s), sometimes drop a tool result when two tools run in parallel (getting 400s), and issue refunds with no approval step.
Expert reasoning trace.
- Choose the reuse mechanism. ‘Same tools across Code, Desktop and the API, one place’ → build an MCP server and connect each host (stdio locally, Streamable HTTP for the shared/remote server secured with OAuth 2.1; the Messages API uses the MCP connector). Copying custom-tool JSON into each app is the maintenance trap.
- Fix the Fable 5.1 400s on tool choice. Fable 5.1 rejects forced/
anytool choice. Usetool_choice: 'auto'with a clear instruction, or structured outputs /strict: truefor the extraction parts. Retrying the 400 with backoff is wrong — it is deterministic. - Fix the parallel-result 400s. Return exactly one
tool_resultpertool_useblock, all in the same following user turn, each keyed to itstool_use_id; on failure returnis_error: trueso Claude can recover. Dropping one result causes the 400. - Make refunds safe. Refunds are irreversible → gate them with a PreToolUse hook / approval workflow and keep the refund tool out of any routine read-only agent’s allowlist. ‘Confirm before refunding’ in the description is not enforcement.
- Right-size the catalogue. Keep ~4–5 active tools; if the catalogue grows, use tool search +
defer_loading. - Reject the tempting alternatives. ‘Force the refund tool for determinism’ — 400 on Fable 5.1. ‘Retry the 400 with backoff’ — deterministic error. ‘Put the tools in
CLAUDE.mdto share them’ —CLAUDE.mdholds instructions, not executable tools. ‘Use a Skill to share across hosts’ — Skills are Claude-Code-only packaged know-how.
Correct decision. An MCP server (OAuth 2.1 for the remote transport) connected to all hosts; auto + instruction or structured outputs on Fable 5.1 instead of forced tool choice; correct parallel tool_result handling with is_error; refunds behind an approval hook and out of routine allowlists; tool search + defer_loading if the catalogue grows.
Exam traps in this domain
| Trap | Why it is wrong |
|---|---|
| Blaming the model when it mis-uses a tool | Fix the description and input_schema first |
| Forcing a tool on Fable 5.1 | any/forced return 400; use structured outputs / strict / auto |
| Loading 18 tools upfront | Overloads selection; use tool search + defer_loading (anti-pattern #8) |
| Returning tool errors as plain success | Mark is_error so Claude can recover |
Mismatching tool_use_id on results | Results must reference the originating tool_use_id |
| Using a client tool for web search | Web search is a server-side built-in |
| Treating MCP resources as model-controlled | Resources are application-controlled; tools are model-controlled |
| Ignoring OAuth for remote MCP | Remote servers use OAuth 2.1 |
| Parsing assistant text to decide whether to keep calling tools | Loop on stop_reason == 'tool_use' (anti-pattern #1) |
| Using a Skill when tools must be shared across hosts | Cross-host/team reuse is MCP; Skills are Claude-Code packaged know-how |
Hard-coding secrets in .mcp.json | Reference env vars (${VAR}); never commit credentials |
| Securing a remote MCP server with a static header instead of OAuth 2.1 | Remote/Streamable HTTP servers use OAuth 2.1 |
| Returning parallel tool results across multiple user turns | All results go in one user turn, each keyed to its tool_use_id |
| Relying on ‘confirm before refunding’ in the tool description | Guidance, not enforcement; gate irreversible tools with a hook/approval and least privilege |
| Retrying a forced-tool 400 on Fable 5.1 with backoff | Deterministic client error; use auto/structured outputs, do not retry |
| Using stdio for a shared, remote team server | stdio is for local subprocesses; remote/shared → Streamable HTTP + OAuth 2.1 |
Practice questions
Q1 · Claude frequently calls the wrong tool and passes malformed arguments. What is the FIRST thing to improve? (Select one)
A. Lower temperature.
B. Rewrite the tool descriptions and tighten input_schema (types, enums, required, examples, when-to-use).
C. Switch to Opus 5.
D. Increase max_tokens.
Answer: B. The description and schema are the primary levers for correct tool use. Temperature (A), model (C) and max_tokens (D) are secondary.
Q2 · A developer sets `tool_choice: 'any'` on Fable 5.1 and receives a 400. What is the correct approach? (Select one)
A. Retry with backoff.
B. Use tool_choice: 'auto' with an instruction, or structured outputs / strict: true, since Fable 5.1 rejects forced tool choice.
C. Switch to none.
D. Increase max_tokens.
Answer: B. Fable 5.1 rejects any/forced tool choice; use auto + instruction or structured outputs. It is a 400 client error, not transient (A); none (C) disables tools.
Q3 · An agent has grown to 18 tools and selection quality has dropped. What is the recommended fix? (Select one)
A. Add more descriptive tools.
B. Reduce to a focused set and use the tool search tool with defer_loading for the larger catalogue.
C. Force a tool every turn.
D. Raise temperature.
Answer: B. Too many tools is anti-pattern #8; the fix is fewer tools plus tool search + defer_loading. More tools (A) worsens it.
Q4 · Which of the following are server-side (Anthropic-hosted) tools? (Select two)
A. Web search. B. Your company’s internal orders API. C. Code execution. D. A custom database query function you wrote. E. A local shell script you maintain.
Answer: A and C. Web search and code execution are Anthropic-hosted server-side tools. Your API (B), custom DB function (D) and local script (E) are client-side.
Q5 · A team wants a remote MCP server's tools available directly from the Messages API without re-declaring each tool. What enables this? (Select one)
A. The Files API. B. The MCP connector in the Messages API. C. Prompt caching. D. Batch API.
Answer: B. The MCP connector lets the Messages API call remote MCP servers directly. The Files API (A), caching (C) and batching (D) are unrelated.
Q6 · In the tool-use loop, how should code decide whether to run another tool round-trip? (Select one)
A. Parse the assistant’s text for phrases like ‘let me check’.
B. Check whether resp.stop_reason == 'tool_use' and, if so, run the tools and call the API again.
C. Loop a fixed 5 times regardless of output.
D. Continue until the response is empty.
Answer: B. The API tells you deterministically via stop_reason. Parsing prose (A) is anti-pattern #1; a fixed cap (C) is anti-pattern #2; empty-response detection (D) is unreliable.
Q7 · A `get_order` tool returns a timeout. How should the result be sent back to Claude? (Select one)
A. Omit the tool_result so Claude ignores it.
B. Return an empty string as the content.
C. Return a tool_result with the same tool_use_id, an error message, and is_error: true.
D. Raise an exception and stop the conversation.
Answer: C. Errors must be reported back with the matching id and is_error: true so Claude can retry or apologise. Omitting (A) causes a 400; empty content (B) is a silent failure (anti-pattern #7); crashing (D) hides diagnostics.
Q8 · Which properties in an `input_schema` best prevent Claude from inventing an invalid argument value for a closed set like a priority level? (Select one)
A. A high max_tokens.
B. An enum constraining the allowed values, plus listing the field in required.
C. Setting tool_choice: 'any'.
D. Lowering temperature.
Answer: B. enum restricts the value space and required guarantees the field is provided. max_tokens (A) and temperature (D) are unrelated to schema validity; tool_choice (C) governs whether a tool is used, not its arguments.
Q9 · A support agent needs to reliably extract structured order data on Fable 5.1, but forcing a specific tool returns 400. What are the TWO valid approaches? (Select two)
A. Use tool_choice: 'auto' with a clear instruction to call the extraction tool.
B. Retry the forced call with exponential backoff.
C. Use structured outputs via output_config.format with a JSON schema.
D. Switch tool_choice to none.
E. Downgrade to Haiku 4.5 for all traffic.
Answer: A and C. Fable 5.1 rejects forced/any tool choice, so use auto + instruction or structured outputs. Backoff (B) will not fix a 400 client error; none (D) disables tools; changing model for everything (E) is an over-broad, unwarranted change.
Q10 · A developer wants the SAME set of tools available in Claude Code, Claude Desktop, and their production Messages API service, maintained in one place. What is the best mechanism? (Select one)
A. Copy the custom tool JSON into each app.
B. Build an MCP server and connect each host to it (stdio/HTTP; connector for the API).
C. Write a Skill in .claude/skills/.
D. Put the tools in CLAUDE.md.
Answer: B. MCP is the reuse-across-hosts mechanism. Copying JSON (A) duplicates maintenance; a Skill (C) is Claude-Code-only packaged know-how, not a cross-host tool server; CLAUDE.md (D) is instructions, not executable tools.
Q11 · Which MCP primitive is model-controlled, meaning Claude decides when to invoke it? (Select one)
A. Resources. B. Prompts. C. Tools. D. Transports.
Answer: C. Tools are model-controlled. Resources (A) are application-controlled, Prompts (B) are user-controlled, and transports (D) are the connection mechanism (stdio / Streamable HTTP), not a primitive Claude invokes.
Q12 · Claude asks for two independent tools (`get_weather` for Paris and `get_weather` for Tokyo) in a single response. How must the results be returned? (Select one)
A. Two separate user turns, one result each.
B. One user turn containing both tool_result blocks, each keyed to its own tool_use_id.
C. A single tool_result combining both cities.
D. Ignore one and answer the other.
Answer: B. Parallel tool_use blocks are answered with all tool_result blocks in one user turn, each matching its originating tool_use_id. Splitting turns (A), merging ids (C), or dropping a result (D) all break the protocol / cause a 400.
Q13 · You need to bundle a multi-step invoice-processing workflow with reference templates and a helper Python script so Claude Code loads it only when relevant. Which mechanism fits best? (Select one)
A. A custom Messages API tool.
B. A remote MCP server.
C. A Skill (SKILL.md with frontmatter and bundled files, loaded progressively).
D. A PreToolUse hook.
Answer: C. Skills package instructions plus files and scripts that load on demand in Claude Code — exactly this scenario. A custom tool (A) is a single function; an MCP server (B) is for cross-host tool sharing; a hook (D) is a deterministic guardrail, not a packaged workflow.
Q14 · A team must expose a shared, remote MCP server to Claude Desktop, Claude Code and the Messages API. Which transport and auth are appropriate? (Select one)
A. stdio with local process trust.
B. Streamable HTTP secured with OAuth 2.1.
C. A static bearer header hard-coded in .mcp.json.
D. SSE-only transport, which is the current recommended default.
Answer: B. Remote/shared servers use Streamable HTTP secured with OAuth 2.1. stdio (A) is for local subprocesses; a hard-coded header (C) is a secrets/hygiene problem and not the standard auth; SSE-only (D) is legacy, not the recommended default.
Q15 · Claude asks for two independent tools in one response, but the code returns each result in its own separate user turn, and requests start failing with 400. What is the fix? (Select one)
A. Merge both outputs into a single combined tool_result.
B. Return both tool_result blocks in one user turn, each keyed to its own tool_use_id.
C. Drop one result to simplify the turn.
D. Retry the 400 with exponential backoff.
Answer: B. Parallel tool_use blocks must be answered with all tool_result blocks in one user turn, each matching its originating id. Merging ids (A) or dropping a result (C) breaks the protocol; the 400 is deterministic, so backoff (D) will not help.
Q16 · An agent may issue refunds, but any refund over $500 needs human approval. Where must this be enforced? (Select one)
A. In the issue_refund tool description (‘confirm before refunding over $500’).
B. In a PreToolUse hook / approval workflow that blocks the over-threshold refund and routes to a human, with the tool kept out of routine agents’ allowlists.
C. By lowering the model’s temperature.
D. By asking the model to double-check the amount.
Answer: B. Irreversible actions are gated deterministically by a hook/approval and least privilege, not by the description or self-checks. Description text (A) and self-checks (D) are guidance the model can skip (anti-pattern #3); temperature (C) is irrelevant.
Q17 · Which MCP interaction happens on `initialize`, and how are the three primitives controlled? (Select one)
A. Capability negotiation occurs on initialize; tools are model-controlled, resources application-controlled, prompts user-controlled.
B. Authentication occurs on initialize; all three primitives are model-controlled.
C. Nothing happens on initialize; primitives are negotiated per call.
D. initialize returns the OAuth token; tools are user-controlled.
Answer: A. Hosts and servers negotiate capabilities on initialize, and the control mapping is tools (model), resources (application), prompts (user). B mislabels all primitives; C is wrong (negotiation is at init); D scrambles the mapping and misstates OAuth.
Q18 · A support agent on Fable 5.1 must reliably extract structured order data, but forcing the extraction tool returns 400. Which TWO approaches are valid? (Select two)
A. Use tool_choice: 'auto' with a clear instruction to call the extraction tool.
B. Use structured outputs via output_config.format with a JSON schema.
C. Retry the forced-tool call with backoff.
D. Switch tool_choice to none.
E. Downgrade all traffic to Haiku 4.5.
Answer: A and B. Fable 5.1 rejects forced/any tool choice, so auto + instruction or structured outputs are valid. Backoff (C) will not fix a deterministic 400; none (D) disables tools; changing every request’s model (E) is an over-broad, unwarranted change.
Q19 · A remote MCP server tool call fails with a timeout during a parallel call alongside a successful tool. How should the failed call be represented? (Select one)
A. Omit its tool_result so Claude moves on.
B. Return a tool_result with the same tool_use_id, an error message, and is_error: true, in the same user turn as the successful result.
C. Retry silently and return empty content.
D. Crash the conversation to force a restart.
Answer: B. Every tool_use needs a matching tool_result (with is_error: true on failure) in the same turn so Claude can recover. Omitting it (A) causes a 400 and hides the failure; empty content (C) is silent suppression (anti-pattern #7); crashing (D) loses diagnostics.
Key takeaways
- The tool-use lifecycle: define tools →
tool_use→ run and returntool_result(matchingtool_use_id) → final answer. - The tool description and
input_schemaare the single most important lever for correct tool use. tool_choiceisauto/any/tool/none; Fable 5.1 rejectsanyand forced tools – use structured outputs /strict/auto.- Independent tools can run in parallel; return all results in one
userturn. - Client-side tools run in your code; server-side built-ins (web search, code execution, computer use, text editor, bash, memory) run at Anthropic.
- Beyond ~10 tools, use tool search +
defer_loading; require approval for irreversible actions. - MCP is JSON-RPC 2.0 with tools (model), resources (app), prompts (user); stdio for local, Streamable HTTP + OAuth 2.1 for remote; author with FastMCP (Python) or
@modelcontextprotocol/sdk(TS). - Capability negotiation happens on
initialize; choose stdio for local subprocess tools and Streamable HTTP + OAuth 2.1 for shared/remote servers (SSE-only is legacy). - Gate irreversible tools (refunds/deletes/sends/deploys) with a PreToolUse hook / approval workflow and least privilege — the tool description is guidance, not enforcement.
- Return every parallel
tool_resultin one user turn keyed to itstool_use_id, and mark failures withis_error: trueso Claude can recover. - A forced-tool 400 on Fable 5.1 is deterministic — switch to
auto/structured outputs rather than retrying with backoff.
Last updated Sep 18, 2026