Domain 2 is worth 18% of the CCA-F exam, and it rewards a specific mindset: a tool is a contract, not just a function. Claude never runs your code directly — it reads a tool's name, description, and input schema, decides whether to call it, emits a tool_use request, and then reasons over whatever tool_result you send back. Every design choice you make in that contract — how you word a description, how you shape an error, which MCP scope you pick — changes how reliably the model selects and recovers from that tool.
The Model Context Protocol (MCP) is the connective tissue for this domain. MCP is an open standard (originated by Anthropic) for connecting AI applications to external systems — data sources, tools, and workflows — through a client-server architecture. The official docs describe it as "a USB-C port for AI applications": build a server once and any MCP-capable client (Claude Code, the Claude apps, and many third-party tools) can use it. MCP servers expose three things — tools (actions), resources (data), and prompts (reusable workflows).
This article walks the five D2 objectives in order. Each one is scenario-based on the exam, so for every objective you get the core concept, why it matters, a concrete example drawn from support and data-extraction agents, and the trap the exam likes to set. Ground your answers in how tool use and MCP actually behave — not in how you wish they behaved.
Surfacing structured, recoverable tool errors
Core concept. When a tool fails, you return a tool_result with is_error: true. That flag tells Claude the call failed — but the content of that result is what determines whether the agent can recover. A generic string like "Error: request failed" gives the model nothing to reason over. A structured, type-specific error tells it what went wrong, whether the condition is recoverable, and what to try next.
Why it matters. Claude treats a tool_result as data to reason over, not as a fatal exception. A well-formed error result lets the model self-correct — retry after a delay, fix a malformed argument, or fall back to a different tool — without a human in the loop. A vague error usually produces one of two bad outcomes: the model gives up, or it blindly retries the identical failing call. On the exam, the answer that turns a failure into an actionable next step is almost always correct.
Concrete example. A customer-support agent calls an order_lookup tool that hits a rate limit. Instead of returning "failed", return a payload the model can act on:
{
"type": "tool_result",
"tool_use_id": "toolu_01A7b3",
"is_error": true,
"content": "{\"error_type\": \"rate_limited\", \"recoverable\": true, \"retry_after_seconds\": 30, \"suggested_action\": \"Wait 30s and retry the same call, or reduce the page size.\"}"
}Contrast that with a non-recoverable error — an invalid order ID — where the right suggested_action is to ask the user for a corrected ID rather than retry. Encoding recoverable: false stops the model from looping on a call that can never succeed.
- error_type — a machine-readable category (
rate_limited,not_found,invalid_argument,auth_expired) the model can branch on. - recoverable — a boolean that tells the model whether retrying could ever help.
- suggested_action — the concrete next step, so the model doesn't have to guess.
- Keep
is_error: trueon the block itself — don't hide the failure inside a success payload, or the model may treat garbage as valid data.
Writing tool descriptions that drive reliable selection
Core concept. Claude picks tools almost entirely from their name and description. The description is prompt real estate, not documentation — it should be *prescriptive about when to call the tool*, specify the exact input format, give a use-case example, and disambiguate the tool from any semantically similar sibling.
Why it matters. Recent Claude models reach for tools more conservatively and follow instructions more literally than older ones. A thin description like "Search records" produces missed calls and wrong-tool selection. Adding an explicit trigger condition ("Call this when the user asks about current prices or recent events") gives a measurable lift in should-call accuracy. When two tools sound alike — search_orders vs search_products — the model needs an explicit boundary or it will confuse them.
Concrete example. A data-extraction agent has two lookup tools. Weak descriptions cause cross-firing; strong ones separate them cleanly:
{
"name": "lookup_customer",
"description": "Fetch a customer profile by their exact account ID. Call this when you have an account ID (format: 'ACC-' followed by 8 digits, e.g. 'ACC-10482233') and need contact or plan details. Do NOT use this to search by name or email — use search_customers for that. Example: to answer 'what plan is ACC-10482233 on', call lookup_customer with account_id='ACC-10482233'.",
"input_schema": {
"type": "object",
"properties": {
"account_id": {
"type": "string",
"description": "Exact account ID, format ACC-XXXXXXXX (8 digits)."
}
},
"required": ["account_id"]
}
}- Use-case example — show one concrete input the model should produce, inline in the description.
- Input format specification — state the exact shape (
ACC-+ 8 digits) in both the description and each property'sdescription. - Disambiguation — name the sibling tool and the boundary between them ("use search_customers for name/email").
- Use
enumfor fixed value sets and mark only truly required fields asrequired— a tight schema is itself a selection signal.
Integrating MCP servers into Claude Code and agents
Core concept. Integrating an MCP server has three moving parts: (1) pick the right scope so the server loads where you need it, (2) configure authentication without hard-coding secrets — using environment-variable expansion — and (3) verify tool discovery so you know Claude actually sees the tools before you rely on them.
Why it matters. A server that's configured but never connects is invisible to the model; a server with a leaked token in a committed config is a security incident. The integration workflow exists to avoid both. In Claude Code you add a server with claude mcp add, choosing a transport (http, sse, or a local stdio process) and a scope.
# Add a shared, project-scoped server (writes .mcp.json)
claude mcp add --transport http support-db --scope project https://mcp.example.com/mcp
# List servers and their status; then use /mcp inside a session
claude mcp list
claude mcp get support-dbAuthentication via environment-variable expansion. In .mcp.json, Claude Code expands ${VAR} (the value of VAR) and ${VAR:-default} (value if set, otherwise default). Expansion works in command, args, env, url, and headers — so the secret stays in the environment, and only its name is committed to version control.
{
"mcpServers": {
"support-db": {
"type": "http",
"url": "${SUPPORT_API_URL:-https://api.example.com}/mcp",
"headers": {
"Authorization": "Bearer ${SUPPORT_API_KEY}"
}
}
}
}Verify tool discovery. Run /mcp inside a session — the panel shows each connected server with its tool count and flags servers that advertise a tools capability but expose none. claude mcp list and claude mcp get <name> report connection status from the command line, and are where you authenticate OAuth servers and see any missing-variable warnings.
Guaranteeing tool invocation and sequencing dependent calls
Core concept. The tool_choice parameter controls whether and which tool Claude calls. When you need structured output — a classification, an extracted record — forcing a specific tool guarantees the model returns arguments matching that tool's schema instead of free-form prose.
| tool_choice | Behavior |
|---|---|
{"type": "auto"} | Model decides whether to use tools (default). |
{"type": "any"} | Model must call at least one of the provided tools. |
{"type": "tool", "name": "..."} | Model must call the named tool — guarantees invocation. |
{"type": "none"} | Model cannot call any tool. |
Why it matters. For a data-extraction agent that must always emit a record object, tool_choice: {"type": "tool", "name": "record"} makes the call non-optional — you get schema-valid arguments every time. Any tool_choice can also set disable_parallel_tool_use: true to cap the response at one tool call.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[extract_ticket_tool],
tool_choice={"type": "tool", "name": "extract_ticket"},
messages=[{"role": "user", "content": ticket_text}],
)Sequencing multi-tool workflows. A dependent workflow must obtain prerequisite data before the tool that consumes it. To answer "what are this customer's recent orders," the agent needs the account ID (from lookup_customer) before it can call list_orders(account_id=...). Parallel tool use is only safe for independent calls — a data dependency is inherently sequential. The usual pattern is to let auto chain the calls turn by turn, feeding each tool_result back before the next step, rather than forcing everything at once.
Choosing the right scope for servers and settings
Core concept. Claude Code configures MCP servers at three scopes, and the choice controls which projects the server loads in and whether the config is shared with your team. The guiding rule: project scope for shared team tooling, user scope for personal or experimental configurations.
| Scope | Loads in | Shared with team | Stored in |
|---|---|---|---|
| Local (default) | Current project only | No | ~/.claude.json |
| Project | Current project only | Yes, via version control | .mcp.json in project root |
| User | All your projects | No | ~/.claude.json |
Why it matters. Put a server your whole team needs — the shared issue tracker, the project database — at project scope so it's committed in .mcp.json and everyone gets the same tooling on checkout. Keep a personal utility, an experimental server, or anything with credentials you don't want in version control at user scope (available across all your projects) or local scope (default; just this project, private to you). This same project-vs-user reasoning applies to settings generally: shared configuration belongs in version-controlled project files; personal preferences belong in your user configuration.
Concrete example. A team standardizes on a shared support-data server, while one engineer trials a new analytics server privately:
# Shared with the team, committed to .mcp.json
claude mcp add --transport http support-db --scope project https://mcp.example.com/mcp
# Personal, available across all of your projects, not committed
claude mcp add --transport http analytics-beta --scope user https://beta.example.com/mcpWhen the same server name is defined at multiple scopes, Claude Code connects once, using the highest-precedence source: local → project → user (then plugin-provided servers, then connectors). The whole entry from the winning scope is used; fields are not merged across scopes.
Key takeaways
- 01A tool is a contract: Claude selects and calls tools from their name, description, and input schema, then reasons over the tool_result you return.
- 02Return failures as structured tool_result content with is_error: true, encoding error_type, recoverability, and a suggested next action — never an opaque failure or a dropped call.
- 03Descriptions that state WHEN to call a tool, specify input format, give an example, and disambiguate similar tools measurably improve selection reliability.
- 04MCP is an open, Anthropic-originated standard; servers expose tools, resources, and prompts, and integrate via scope selection, env-var-based auth, and verified tool discovery.
- 05tool_choice options are auto, any, tool (force a named tool), and none — forcing a tool guarantees structured output at a known step.
- 06Sequence dependent workflows so prerequisite data is fetched before dependent tools; parallel tool use is only for independent calls.
- 07Use project scope (.mcp.json, version-controlled) for shared team tooling and user/local scope for personal or experimental configs.
Common mistakes
Returning a generic 'operation failed' string (or raising an exception) instead of a structured error the model can act on.
Return a tool_result with is_error: true whose content carries error_type, recoverable, and suggested_action so Claude can retry, fix the argument, or fall back.
Writing thin tool descriptions like 'Search records', causing missed calls or confusion between similar tools.
Make the description prescriptive about when to call it, specify the input format, give a usage example, and name the sibling tool to disambiguate.
Hard-coding API keys directly in .mcp.json and committing them to version control.
Use environment-variable expansion (${API_KEY}, ${URL:-default}) in url/headers/env/command/args so only the variable name is committed and the secret stays in the environment.
Forcing tool_choice: any or a specific tool at the start of a workflow that first needs to gather prerequisite data.
Use auto while the model gathers prerequisites and reserve forced tool_choice for the step where you want a guaranteed structured result; sequence dependent calls so data is obtained first.
Putting a team-wide server at user or local scope (or a personal experiment at project scope).
Match scope to audience: project scope in .mcp.json for shared tooling, user/local scope for personal or experimental servers.
Frequently asked
What is MCP, in one sentence?
The Model Context Protocol is an open-source standard (originated by Anthropic) that connects AI applications to external systems — data sources, tools, and workflows — through a client-server architecture, so an MCP server built once works across any MCP-capable client. Servers expose tools, resources, and prompts.
How is is_error different from tool_choice?
They operate at different points. tool_choice is a request parameter that controls whether and which tool Claude calls (auto, any, tool, none). is_error is a field on the tool_result you send back after execution, telling the model the call failed so it can reason about recovery.
Does forcing a tool with tool_choice guarantee valid structured output?
Forcing tool_choice: {type: 'tool', name: '...'} guarantees the model calls that tool with arguments matching its input_schema, which is the standard way to get reliable structured output. Give the tool a tight schema (types, enums, required fields) so the arguments are exactly the shape you need.
Which Claude Code MCP scope should shared team tooling use?
Project scope. It stores the server in a .mcp.json file at the project root that you check into version control, so every teammate gets the same tools on checkout. Use user scope (all your projects) or local scope (default, this project only) for personal or experimental servers, especially those with credentials you don't want committed.
What happens if an environment variable referenced in .mcp.json isn't set?
Claude Code still loads the config but emits a missing-variable warning (visible in claude mcp list) and passes the literal ${VAR} text through unexpanded. The server then starts with a broken value, so always set the variable or provide a ${VAR:-default} fallback.
Independent, unofficial study material. Not affiliated with, endorsed by, or authorized by Anthropic. Every example is original and written to teach the public exam objectives — no real exam questions are reproduced. Technical details reflect Claude, the Anthropic API, Claude Code, and MCP as of July 25, 2026; always confirm specifics against current official documentation.
Test yourself
Turn what you just read into answers you can check.