Domain 2 — Tool Design & MCP Integration
tool_choice, wire MCP servers into Claude Code and Agent SDK workflows at the right scope, and pick the right built-in tool (Read, Write, Edit, Bash, Grep, Glob) for a given job. At 18% of the exam it is the second-smallest domain, but it is a primary domain for three of the six scenarios — the Customer Support Resolution Agent, the Multi-Agent Research System, and Developer Productivity with Claude.Design effective tool interfaces with clear descriptions and boundaries
What you need to know
- Tool descriptions are the primary mechanism the model uses to select tools — treat them as production routing logic, not documentation.
- A good description states input format, output contract, example queries, edge cases, and when to use a sibling tool instead.
- Ambiguous or overlapping descriptions (analyze_content vs analyze_document) cause misrouting; fix by renaming and re-scoping, not by adding emphasis.
- A generic tool gives the model no contract to route on. Purpose-specific tools with declared inputs and outputs do.
- Keyword-sensitive system prompt wording can override a well-written tool description — audit the system prompt when routing is wrong.
Show that misrouting comes from overlapping descriptions, and that the two fixes are renaming with a scoped description and splitting a generic tool into purpose-specific tools with their own contracts. The graph converges top-down rather than splitting into a left and a right half: the two vague tools head it, the renamed and split tools sit below them, and the broken-versus-fixed contrast is carried by color — vague tools and the misrouted call in red, the purpose-specific tools and the correct call in green. The three split tools share one grouped box so the fixed side stays readable.
Worked examples
Two research tools that keep getting confused
Scenario 3 · Multi-Agent Research SystemThe research system's web-search subagent and document-analysis subagent each expose an "analyze" tool. Both descriptions read like generic content analysis, so the coordinator's calls land on the wrong one roughly half the time and the document agent receives URLs it cannot open.
The correct approach is not to add "IMPORTANT: only use for documents" to one description. It is to eliminate the functional overlap: rename the web-side tool to extract_web_results with a description that names web search result pages as its only input, and split the document-side tool into purpose-specific tools. After the change, the input format alone disambiguates the choice.
json Before: overlapping descriptions the model cannot route on
{
"tools": [
{ "name": "analyze_content", "description": "Analyzes content and returns insights." },
{ "name": "analyze_document", "description": "Analyzes a document and returns insights." }
]
}A description written to be routed on
Scenario 3 · Multi-Agent Research SystemAfter the split, each tool states input format, output contract, an example query, and an explicit boundary that names the alternative. The boundary sentence is what prevents the model from reaching for a neighboring tool when the request is phrased loosely.
json After: purpose-specific tools with contracts and boundaries
{
"name": "verify_claim_against_source",
"description": "Checks whether a single factual claim is supported by one already-loaded source document. Input: 'claim' (one sentence) and 'document_id' from load_document. Output: {supported: boolean, evidence_quote: string, confidence: number}. Example: verify that 'revenue grew 12% in 2024' is supported by document doc_418. Do NOT use this to pull numbers out of a document (use extract_data_points) or to condense it (use summarize_content). Returns supported=false with an empty evidence_quote when the document simply does not discuss the claim.",
"input_schema": {
"type": "object",
"properties": {
"claim": { "type": "string" },
"document_id": { "type": "string" }
},
"required": ["claim", "document_id"]
}
}The system prompt sabotaging a correct tool set
Scenario 1 · Customer Support Resolution AgentThe support agent has four well-described MCP tools: get_customer, lookup_order, process_refund, and escalate_to_human. Descriptions are precise, yet the agent keeps calling lookup_order for pure account questions.
The cause is in the system prompt: "For any customer issue, first look up the relevant order." That keyword-sensitive instruction creates an unintended association between every request and lookup_order, overriding the tool descriptions. The fix is to rewrite the instruction so it is conditional on the request type — "when the request concerns a purchase, shipment, or refund, look up the order first" — rather than weakening or padding the tool descriptions.
Anti-patterns
- Shipping a one-line description ("Analyzes content") instead of stating input format, output contract, examples, and boundaries because minimal descriptions make selection among similar tools unreliable.
- Keeping two near-identically described tools and adding emphatic wording ("ALWAYS use this for documents") instead of renaming and re-scoping them because the overlap itself — not the volume — is what causes misrouting.
- Leaving one generic tool (analyze_document) instead of splitting it into purpose-specific tools with defined input/output contracts because a generic tool gives the model no contract to route on.
- Rewriting tool descriptions again instead of auditing the system prompt for keyword-sensitive instructions because prompt keywords can create tool associations that override even a well-written description.
How it is examined
- Stems typically describe an agent calling the wrong one of two minimally described tools and ask for the most effective first step. The credited answer is description quality: expand each tool's description with the input formats it handles, example queries, edge cases, and boundaries explaining when to use it versus the similar tool. Two distractor families sit either side of it — prompt-side fixes (few-shot examples, emphasis, priority language, a "choose carefully" instruction), which add tokens without addressing the root cause, and jumping straight to restructuring the tool set (consolidating the two tools, or adding a routing layer), which is more effort than a "first step" warrants.
- When the stem says descriptions are already detailed and correct but routing is still wrong, the answer is almost always in the system prompt — look for a keyword-sensitive instruction creating an unintended tool association.
- Watch for distractors that "solve" overlap by consolidating the two tools into one generic tool. The guide calls that "a valid architectural choice" — it is legitimate architecture, just the wrong first response when the immediate problem is inadequate descriptions. Renaming, re-scoping and splitting are the right moves once the overlap is genuinely structural rather than a description gap.
Why this is true
The description is the routing table
An LLM never reads your implementation — it reads the tool's name, description, and input schema. That text is the routing logic. A one-line description like "Analyzes content" tells the model nothing about boundaries, so as soon as two similar tools are in scope, selection becomes close to arbitrary. Minimal descriptions do not fail loudly; they fail as intermittent misrouting that looks like model unreliability.
A description that routes reliably carries four things beyond a summary of purpose:
- Input format — precisely what the arguments are (a web URL, an order ID, a document ID, a claim string), and their expected shape.
- Output contract — what comes back, so the model can plan the next step instead of guessing.
- Example queries — one or two realistic requests for which this tool is the right answer.
- Boundaries and edge cases — when not to use it, and which sibling tool to use instead.
Overlap is the canonical failure
The exam's worked failure is analyze_content versus analyze_document with near-identical descriptions. Nothing in either text tells the model which one owns web pages and which owns uploaded files, so calls land on whichever tool the sampled tokens favor. There are two structural fixes, and both are testable skills:
- Rename and re-scope. Rename
analyze_contenttoextract_web_resultsand give it a web-specific description. The name itself now carries a boundary, and the description reinforces it. - Split the generic tool. Break
analyze_documentinto purpose-specific tools with defined input/output contracts:extract_data_points,summarize_content, andverify_claim_against_source. Each has a distinct output shape, which makes the choice mechanical rather than interpretive.
Note the direction of travel: fewer overlapping tools, more purpose-specific ones. Splitting is not the same as bloating the tool set — see 2.3 for the count constraint that bounds it.
The system prompt can override a good description
Tool selection is influenced by the whole prompt, not just the tool block. Keyword-sensitive instructions create unintended associations: a system prompt that says "always start by searching" biases the model toward any tool whose name or description contains "search", even when a better-matched tool exists. When a well-described tool is still being bypassed, review the system prompt for keyword-sensitive wording before rewriting the description again.
Exam guide, verbatim — what is measured
Knowledge of
- Tool descriptions as the primary mechanism LLMs use for tool selection; minimal descriptions lead to unreliable selection among similar tools
- The importance of including input formats, example queries, edge cases, and boundary explanations in tool descriptions
- How ambiguous or overlapping tool descriptions cause misrouting (e.g., analyze_content vs analyze_document with near-identical descriptions)
- The impact of system prompt wording on tool selection: keyword-sensitive instructions can create unintended tool associations
Skills in
- Writing tool descriptions that clearly differentiate each tool's purpose, expected inputs, outputs, and when to use it versus similar alternatives
- Renaming tools and updating descriptions to eliminate functional overlap (e.g., renaming analyze_content to extract_web_results with a web-specific description)
- Splitting generic tools into purpose-specific tools with defined input/output contracts (e.g., splitting a generic analyze_document into extract_data_points, summarize_content, and verify_claim_against_source)
- Reviewing system prompts for keyword-sensitive instructions that might override well-written tool descriptions
Implement structured error responses for MCP tools
What you need to know
- MCP signals failure with the isError flag, but the flag alone carries no recovery information — the structured metadata you attach does.
- Return errorCategory (transient / validation / permission), an isRetryable boolean, and a human-readable description on every failure.
- Business rule violations get retriable: false plus a customer-friendly explanation the agent can relay, so it stops retrying and communicates the policy. The guide spells the flag both isRetryable and retriable — recognize either.
- Uniform "Operation failed" responses force the agent to guess, which produces wasted retries or premature give-up.
- Subagents recover from transient failures locally and propagate to the coordinator only what they cannot resolve — with the four elements of structured error context: failure type, what was attempted, partial results, and alternative approaches.
- A successful query with no matches is a valid empty result, not an error; conflating it with an access failure triggers pointless retries.
Let the learner read off, for each of the four MCP error categories, whether the call is retryable and what the agent should do next. Draw it as a 4-row by 2-column grid: the four categories are the rows, the two columns are isRetryable and the agent recovery action, and the relation labels are the cell values. The point is that the category is what makes recovery mechanical.
Show the decision path from an MCP tool result to either local recovery or propagation, and show that a valid empty result bypasses error handling entirely.
Worked examples
process_refund declines on policy, not on failure
Scenario 1 · Customer Support Resolution AgentA customer asks for a refund 90 days after purchase; the return window is 30 days. If process_refund returns isError with "Refund failed", the agent will typically retry two or three times and then escalate a case that never needed a human.
The correct response marks the outcome as a business error with retriable: false and includes a customer-friendly explanation. The agent then does the right thing on the first pass: it tells the customer why the refund cannot be processed and offers the next step, which supports the 80%+ first-contact resolution target instead of burning an escalation.
json MCP tool result: business rule violation
{
"isError": true,
"errorCategory": "business",
"retriable": false,
"code": "REFUND_WINDOW_EXPIRED",
"customerExplanation": "This order was delivered on 12 March, which is outside our 30-day refund window. I can offer store credit or connect you with a specialist to review an exception.",
"suggestedNextAction": "offer_store_credit_or_escalate",
"content": [{
"type": "text",
"text": "Order ORD-88213 was delivered 90 days ago; the refund window is 30 days."
}]
}Transient versus validation on the same tool
Scenario 1 · Customer Support Resolution Agentlookup_order can fail in three genuinely different ways, and the agent's correct behavior differs in each. Returning the same shape with different metadata is what makes the difference actionable: retry the timeout, fix the malformed ID, and treat "no orders" as an answer rather than a fault.
json Three outcomes from one tool, distinguished by metadata
// 1. Transient — retry is worthwhile
{ "isError": true, "errorCategory": "transient", "isRetryable": true,
"message": "Order service timed out after 5s.", "retryAfterMs": 1000 }
// 2. Validation — retrying the same call is wasted; fix the argument
{ "isError": true, "errorCategory": "validation", "isRetryable": false,
"message": "order_id must match ORD-<6 digits>; received 'last one'.",
"field": "order_id" }
// 3. Valid empty result — NOT an error
{ "isError": false, "orders": [], "message": "Customer CUS-4471 has no orders." }Subagent absorbs the blip, escalates the rest
Scenario 3 · Multi-Agent Research SystemThe web-search subagent hits a rate limit on its third of eight queries. It should retry locally with backoff rather than telling the coordinator the research failed — the coordinator has no more information than the subagent does, and a round trip costs context on both sides.
If the failure survives local recovery, the subagent propagates it upward with the partial results it did gather, a record of what it attempted, and the alternative approaches it can see. The coordinator can then decide to proceed with six of eight sources, re-delegate the remaining two, or flag reduced coverage in the final report — decisions it cannot make from a bare "search failed". Naming those options in an alternatives field is what turns the report from a complaint into a decision brief.
json Subagent → coordinator report after exhausting local recovery
{
"status": "partial",
"errorCategory": "transient",
"isRetryable": true,
"message": "Search provider rate-limited queries 3 and 7 after 3 local retries.",
"attempted": [
"3 retries with exponential backoff (1s, 2s, 4s)",
"fallback to cached results — none available"
],
"partialResults": {
"completedQueries": 6,
"totalQueries": 8,
"sources": ["src_101", "src_102", "src_105", "src_107", "src_110", "src_114"]
},
"alternatives": [
"proceed with the 6 completed queries and flag reduced coverage",
"re-delegate queries 3 and 7 after the rate-limit window resets",
"substitute the document-analysis subagent for the two missing topics"
]
}Anti-patterns
- Returning a uniform "Operation failed" for every tool failure instead of structured metadata because the agent cannot make an appropriate recovery decision from an undifferentiated error.
- Marking a policy violation as a generic retryable error instead of business with retriable: false and a customer-friendly explanation because the agent will burn retries and then escalate a case it could have resolved by explaining the policy.
- Propagating every transient subagent failure to the coordinator instead of recovering locally first because it wastes coordinator context on blips the subagent could have absorbed.
- Returning isError for a query that legitimately found nothing instead of a valid empty result because the agent then retries a call that already answered correctly.
How it is examined
- When a stem shows an agent retrying a doomed operation or apologizing instead of explaining, the credited fix is structured error metadata — errorCategory plus isRetryable / retriable — not more retry logic or a longer system prompt.
- Distractors often propose a single global retry policy (for example "retry all failures three times"). The guide's position is that the retry decision belongs in the error payload, per category.
- Multi-agent stems test the propagation rule: recover transient failures locally, and escalate only what cannot be resolved locally — always with partial results and what was attempted. An option that escalates everything, or one that swallows the failure silently, is wrong.
Why this is true
An error is a message to a reasoning system
MCP communicates tool failure back to the agent with the isError flag on the tool result. That flag says only "this call failed" — it carries no recovery information by itself. Everything the agent needs in order to choose a next action has to be in the payload you attach to it.
This is why uniform error responses are a design defect rather than a style choice. If every failure returns "Operation failed", the agent cannot distinguish a two-second network blip from an invalid order ID from a refund the policy forbids. Faced with an undifferentiated failure, the model does the only thing available: it retries, or it apologizes and stops. Both are wrong most of the time.
Four categories, one retry decision
The guide separates failures into four kinds, and the practical purpose of the taxonomy is to answer one question — should the agent call this tool again?
| Category | What went wrong | Retryable? | What the agent does next |
|---|---|---|---|
| Transient | Timeout, service unavailable | Yes | Retry — the same call may well succeed |
| Validation | Invalid input | Not as-is | Fix the arguments, then call again |
| Business | Policy violation, such as a refund outside the return window | No | Relay the customer-friendly explanation; the outcome is a decision, not a fault |
| Permission | The caller lacks rights | Not by the agent | Take a different path, typically escalation |
Encode this explicitly: return errorCategory (transient / validation / permission), an isRetryable boolean, and a human-readable description. For business rule violations add retriable: false together with a customer-friendly explanation the agent can relay verbatim, so it explains the policy instead of inventing one or looping on retries.
A wording note: the guide is inconsistent about the spelling of this flag — isRetryable where it describes the general error metadata, retriable where it describes business rule violations. Recognize either on the exam, carry one of them in a given payload rather than both, and treat the retryability concept as the thing being tested.
Local recovery, then honest propagation
In a multi-agent system, subagents should absorb transient failures locally — retry within the subagent rather than surfacing every blip to the coordinator. Only errors that cannot be resolved locally go up, and when they do they must travel with the full structured error context: failure type, what was attempted, partial results, and alternative approaches. That last element is the one authors forget — naming the alternatives (proceed with reduced coverage, re-delegate the failed queries later) is what lets the coordinator reroute or degrade gracefully instead of restarting the work.
Empty is not broken
Finally, distinguish an access failure (which needs a retry decision) from a valid empty result (a successful query with no matches). lookup_order finding no orders for a customer is a success with an empty list, not an isError. Conflating the two makes agents retry queries that already answered correctly.
Exam guide, verbatim — what is measured
Knowledge of
- The MCP isError flag pattern for communicating tool failures back to the agent
- The distinction between transient errors (timeouts, service unavailability), validation errors (invalid input), business errors (policy violations), and permission errors
- Why uniform error responses (generic "Operation failed") prevent the agent from making appropriate recovery decisions
- The difference between retryable and non-retryable errors, and how returning structured metadata prevents wasted retry attempts
Skills in
- Returning structured error metadata including errorCategory (transient/validation/permission), isRetryable boolean, and human-readable descriptions
- Including retriable: false flags and customer-friendly explanations for business rule violations so the agent can communicate appropriately
- Implementing local error recovery within subagents for transient failures, propagating to the coordinator only errors that cannot be resolved locally along with partial results and what was attempted
- Distinguishing between access failures (needing retry decisions) and valid empty results (representing successful queries with no matches)
Distribute tools appropriately across agents and configure tool choice
What you need to know
- Giving an agent 18 tools instead of 4–5 degrades selection reliability by increasing decision complexity on every turn.
- Agents with tools outside their specialization misuse them — a synthesis agent with web search will start searching instead of synthesizing.
- Scope each agent to its role, then add only limited cross-role tools for specific high-frequency needs (for example verify_fact for the synthesis agent).
- Replace generic tools with constrained alternatives: load_document that validates document URLs instead of an open fetch_url.
- tool_choice "auto" lets the model decide, "any" guarantees some tool is called, and {"type": "tool", "name": "..."} forces one specific tool.
- Forced tool_choice pins the first step only — sequence the remaining steps across follow-up turns, and route complex cross-role cases through the coordinator.
Show that each subagent holds only a few role-relevant tools, that one scoped cross-role tool is a deliberate exception, and that complex cross-role work is routed back through the coordinator instead of widening a tool set.
Worked examples
Splitting an 18-tool monolith across four subagents
Scenario 3 · Multi-Agent Research SystemThe research system started as one agent holding every tool: web search, document loading, extraction, summarization, citation lookup, report rendering, and more — eighteen in total. Selection accuracy was poor and the agent frequently re-searched material it had already analyzed.
The fix distributes the same capabilities across the four specialized subagents, each holding four to five tools relevant to its role. The synthesis agent gets one deliberate exception: a scoped verify_fact tool, because fact-checking is a high-frequency need during synthesis. Anything more involved than a single fact check goes back through the coordinator rather than expanding the synthesis agent's tool set.
typescript Scoped tool sets per subagent
const toolsByRole = {
webSearch: ['search_web', 'extract_web_results', 'rank_sources'],
documents: ['load_document', 'extract_data_points', 'summarize_content'],
// one scoped cross-role tool for a high-frequency need:
synthesis: ['merge_findings', 'detect_contradictions', 'verify_fact'],
reporting: ['render_report', 'cite_source', 'format_citations'],
};
// load_document replaces a generic fetch_url: it validates that the URL
// points at a document before the agent can reach arbitrary endpoints.Forcing the first tool call, then continuing in follow-up turns
Scenario 3 · Multi-Agent Research SystemEnrichment tools are only meaningful once metadata has been extracted, but the model sometimes skips straight to enrichment. Forcing extract_metadata with tool_choice guarantees the first call, and the rest of the pipeline proceeds on subsequent turns under "auto".
Note what tool_choice cannot do: it does not express a multi-step ordering. If a stem asks how to guarantee a sequence, the answer is a forced first turn plus follow-up turns — not a single configuration value.
json Turn 1 forces one tool; later turns return to auto
// Turn 1 — guarantee metadata extraction happens first
{ "tool_choice": { "type": "tool", "name": "extract_metadata" } }
// Turn 2+ — let the model choose among the enrichment tools
{ "tool_choice": { "type": "auto" } }
// Alternative: guarantee *some* tool is called rather than a chat reply
{ "tool_choice": { "type": "any" } }Guaranteeing a tool call instead of a conversational reply
Scenario 1 · Customer Support Resolution AgentThe support agent occasionally answers a billing question conversationally — plausibly, and without ever calling get_customer or lookup_order. That is a correctness problem: the reply is ungrounded in backend data.
Setting tool_choice: "any" on the classification turn guarantees the model calls a tool rather than returning conversational text, forcing the agent to ground itself in get_customer or lookup_order before it composes an answer. Keep the tool set small — four tools (get_customer, lookup_order, process_refund, escalate_to_human) is squarely in the reliable range, which is part of why this agent routes well in the first place.
Anti-patterns
- Giving one agent every tool it might conceivably need instead of scoping to 4–5 role-relevant tools because decision complexity degrades selection reliability.
- Granting an agent tools outside its specialization "just in case" instead of routing those needs through the coordinator because agents reliably misuse out-of-role tools.
- Exposing a generic fetch_url instead of a constrained load_document that validates document URLs because the generic tool invites uses the role never needed.
- Trying to enforce a multi-step tool order with a single tool_choice setting instead of forcing the first tool and continuing in follow-up turns because forced selection applies to one turn only.
How it is examined
- A stem that reports declining tool-selection accuracy alongside a growing tool count is testing the 4–5 versus 18 principle. The credited answer restricts tool sets per role; distractors improve descriptions or add prompt instructions instead.
- Know all three tool_choice values by their exact spelling: "auto", "any", and {"type": "tool", "name": "..."}. Items frequently hinge on choosing "any" (guarantee a tool call) versus forced selection (guarantee a specific tool).
- Cross-role access questions have a two-part answer: a narrow scoped tool for the high-frequency case, and coordinator routing for the complex case. An option that grants full cross-role access, or one that refuses any exception, is incomplete.
Why this is true
Every extra tool is a decision the model has to get right
Tool selection reliability degrades as the tool set grows. An agent holding 18 tools instead of 4–5 is not more capable — it faces higher decision complexity on every turn, and picks wrong more often. The exam frames this as a hard design principle, not a tuning preference: scope each agent's tool set to its role.
The second failure this prevents is out-of-specialization misuse. An agent given tools outside its job will use them. A synthesis agent that can reach a web-search tool starts doing its own searches instead of synthesizing what the search agent already gathered — duplicating work, spending context, and producing findings the coordinator never asked for.
Scoped access with deliberate exceptions
The pattern is scoped tool access: each agent gets only what its role needs, plus a small number of scoped cross-role tools for specific high-frequency needs. The worked example is a synthesis agent that legitimately needs to check facts often; give it a narrow verify_fact tool rather than the full research toolkit, and route complex cases back through the coordinator. That keeps the common path cheap and the rare path correct.
Constraining tools also means replacing generic ones with bounded alternatives. Swapping a wide-open fetch_url for a load_document that validates document URLs removes a whole class of misuse without removing the capability the agent actually needs.
tool_choice: making invocation deterministic where it matters
tool_choice controls whether and which tool is called on a turn:
tool_choice |
Guarantee | Who picks the tool | Use it when |
|---|---|---|---|
"auto" |
May call a tool, may return plain text | Model | Default agentic behavior |
"any" |
Must call some tool | Model | The turn has to produce a tool call rather than conversational text |
{"type": "tool", "name": "..."} |
Must call that tool | You | One specific call has to happen first, such as extract_metadata before enrichment |
Forced selection guarantees which tool is called on this turn — that and nothing more. It is enough to make one specific call happen first: force extract_metadata on the opening turn, then let the enrichment steps proceed on follow-up turns under "auto". It is not a pipeline mechanism. Because forcing binds a single turn, ordering across steps is expressed as a sequence of turns, not as one clever configuration value. A genuine multi-step prerequisite — "never call process_refund before get_customer has returned a verified ID" — needs a programmatic gate instead. That is what gates are for; see 1.4.
Exam guide, verbatim — what is measured
Knowledge of
- The principle that giving an agent access to too many tools (e.g., 18 instead of 4-5) degrades tool selection reliability by increasing decision complexity
- Why agents with tools outside their specialization tend to misuse them (e.g., a synthesis agent attempting web searches)
- Scoped tool access: giving agents only the tools needed for their role, with limited cross-role tools for specific high-frequency needs
- tool_choice configuration options: "auto", "any", and forced tool selection ({"type": "tool", "name": "..."})
Skills in
- Restricting each subagent's tool set to those relevant to its role, preventing cross-specialization misuse
- Replacing generic tools with constrained alternatives (e.g., replacing fetch_url with load_document that validates document URLs)
- Providing scoped cross-role tools for high-frequency needs (e.g., a verify_fact tool for the synthesis agent) while routing complex cases through the coordinator
- Using tool_choice forced selection to ensure a specific tool is called first (e.g., forcing extract_metadata before enrichment tools), then processing subsequent steps in follow-up turns
- Setting tool_choice: "any" to guarantee the model calls a tool rather than returning conversational text
Integrate MCP servers into Claude Code and agent workflows
What you need to know
- Project-scoped .mcp.json is for shared team tooling; user-scoped ~/.claude.json is for personal or experimental servers.
- Use environment variable expansion in .mcp.json (for example ${GITHUB_TOKEN}) so the config can be committed without the secret.
- Tools from all configured MCP servers are discovered at connection time and are available to the agent simultaneously.
- Write detailed MCP tool descriptions covering capabilities and outputs, or the agent will prefer built-in tools like Grep over a more capable MCP tool.
- Prefer an existing community MCP server for standard integrations such as Jira; build custom servers only for team-specific workflows.
- Expose content catalogs (issue summaries, documentation hierarchies, database schemas) as MCP resources to eliminate exploratory tool calls.
Show which config file serves which audience, where environment variable expansion injects credentials, and that tools and resources from every configured server are discovered at connection time into one list the agent sees.
Trace one tool call from the agent through the MCP client and server to the backend and back, so the learner sees where description-based selection happens, where credentials are applied, and where the isError payload originates. Every hop stays on an adjacent layer — the agent never talks to the server directly.
Worked examples
Shared team server in .mcp.json with a token from the environment
Scenario 4 · Developer Productivity with ClaudeThe productivity team wants every engineer to have the GitHub MCP server available the moment they clone the repo, without anyone pasting a personal access token into version control.
Register the server in project-scoped .mcp.json and reference the credential through environment variable expansion. The file is committed; each developer exports GITHUB_TOKEN locally and CI injects it from its secret store. A developer who additionally wants to try an unreleased server puts that one in ~/.claude.json so it stays personal.
json .mcp.json — project scope, no secrets committed
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" }
},
"jira": {
"command": "npx",
"args": ["-y", "mcp-server-jira"],
"env": { "JIRA_API_TOKEN": "${JIRA_API_TOKEN}", "JIRA_SITE": "${JIRA_SITE}" }
}
}
}The agent keeps using Grep instead of the code-index MCP tool
Scenario 4 · Developer Productivity with ClaudeThe team ships a custom MCP server exposing find_symbol_references, which walks a pre-built symbol index and returns callers with file, line, and enclosing function. The agent ignores it and runs Grep instead, then wades through comments and string literals.
The cause is the description: "Finds references." That gives the model no reason to prefer it over a text search it already trusts. Rewriting the description to spell out the capability and the output shape — index-backed, resolves re-exports, returns structured call sites — is what shifts selection. This is the same mechanism as 2.1, applied specifically to MCP tools competing against built-ins.
typescript Description rewritten so the model prefers it over Grep
{
name: 'find_symbol_references',
description:
'Finds every call site of an exported symbol using a pre-built symbol index, ' +
'not text matching. Resolves re-exports and wrapper modules, so it finds ' +
'callers that reference the symbol under an aliased name — which Grep cannot. ' +
'Ignores comments, strings and generated files. ' +
'Input: symbol name plus optional package scope. ' +
'Output: array of {file, line, enclosingFunction, isTest}. ' +
'Prefer this over Grep for "who calls X" questions across the repo.',
}A resource catalog instead of exploratory tool calls
Scenario 4 · Developer Productivity with ClaudeBefore answering a question about the payments service, the agent used to issue five or six speculative calls just to learn which schemas and docs existed. Each call cost a turn and filled context with dead ends.
Exposing the catalog as MCP resources — the database schema list, the documentation hierarchy, open issue summaries — lets the agent read one index and go straight to the right target. This is the guide's stated purpose for resources: give agents visibility into available data without requiring exploratory tool calls.
json Resources advertised by the server at connection time
{
"resources": [
{ "uri": "repo://schemas/index",
"name": "Database schemas",
"description": "All table and column definitions for payments, orders and accounts." },
{ "uri": "repo://docs/tree",
"name": "Documentation hierarchy",
"description": "Titles and paths of every ADR and runbook, grouped by service." },
{ "uri": "jira://issues/open/summary",
"name": "Open issue summaries",
"description": "Key, title and component for every open issue in the PAY project." }
]
}Anti-patterns
- Putting a literal API token — or an experimental personal server — in project-scoped .mcp.json instead of using environment variable expansion and user-scoped ~/.claude.json because the project file is committed and shared, so the secret leaks and the unstable dependency is imposed on the whole team.
- Writing a thin MCP tool description instead of detailing capabilities and outputs because the agent will keep preferring built-in tools like Grep over the more capable MCP tool.
- Building a custom MCP server for a standard integration such as Jira instead of adopting an existing community server because you take on permanent maintenance for no differentiated capability.
- Letting the agent discover available data through exploratory tool calls instead of exposing content catalogs as MCP resources because every probe costs a turn and pollutes context.
How it is examined
- Expect a direct scoping item: shared team tooling goes in project-level .mcp.json, personal or experimental servers in user-level ~/.claude.json. Memorize both file names exactly.
- When a stem says a custom MCP tool exists but the agent still uses Grep, the answer is to enhance the MCP tool description — not to remove or disable the built-in tool. Options that strip built-ins are distractors.
- A stem describing many exploratory calls before real work begins is pointing at MCP resources as content catalogs. Distractors usually propose more tools or a bigger system prompt instead of exposing the catalog.
Why this is true
Where the config lives decides who gets the tools
MCP servers are registered by scope, and the scope is a collaboration decision:
- Project-level
.mcp.json— checked into the repository. Use it for shared team tooling, so every engineer who clones the repo picks up the same servers. - User-level
~/.claude.json— per-developer. Use it for personal or experimental servers that should not be imposed on the team.
Because project config is committed, it must never contain secrets. .mcp.json supports environment variable expansion — write ${GITHUB_TOKEN} in the config and let each developer or CI runner supply the value from their own environment. The config is shareable; the credential is not.
Discovery is at connection time, and it is cumulative
Tools from all configured MCP servers are discovered when the client connects, and they are all available to the agent simultaneously. Two consequences follow. First, you do not select a server per request — everything configured is in play, which is exactly why 2.3's tool-count discipline matters when you add servers. Second, a server that fails to start simply contributes no tools, so a "missing tool" symptom is usually a connection or credential problem rather than a selection problem.
Competing with the built-in tools
An MCP tool sits alongside the built-ins (Read, Grep, Glob, and the rest), and the model picks between them on description alone. A thin description loses: the agent falls back to Grep even when a purpose-built MCP tool would answer the question in one call with better structure. Enhance MCP tool descriptions to explain capabilities and outputs in detail — say what the tool indexes, what it returns, and why it beats a raw text search — or the more capable tool goes unused.
Buy before you build, and publish a catalog
For standard integrations, choose an existing community MCP server — Jira is the guide's example — and reserve custom servers for team-specific workflows the community cannot know about. Custom servers are maintenance you own forever.
Finally, expose content catalogs as MCP resources: issue summaries, documentation hierarchies, database schemas. A resource gives the agent visibility into what data exists without a sequence of exploratory tool calls to find out — it replaces search-to-discover with read-the-index.
Exam guide, verbatim — what is measured
Knowledge of
- MCP server scoping: project-level (.mcp.json) for shared team tooling vs user-level (~/.claude.json) for personal/experimental servers
- Environment variable expansion in .mcp.json (e.g., ${GITHUB_TOKEN}) for credential management without committing secrets
- That tools from all configured MCP servers are discovered at connection time and available simultaneously to the agent
- MCP resources as a mechanism for exposing content catalogs (e.g., issue summaries, documentation hierarchies, database schemas) to reduce exploratory tool calls
Skills in
- Configuring shared MCP servers in project-scoped .mcp.json with environment variable expansion for authentication tokens
- Configuring personal/experimental MCP servers in user-scoped ~/.claude.json
- Enhancing MCP tool descriptions to explain capabilities and outputs in detail, preventing the agent from preferring built-in tools (like Grep) over more capable MCP tools
- Choosing existing community MCP servers over custom implementations for standard integrations (e.g., Jira), reserving custom servers for team-specific workflows
- Exposing content catalogs as MCP resources to give agents visibility into available data without requiring exploratory tool calls
Select and apply built-in tools (Read, Write, Edit, Bash, Grep, Glob) effectively
What you need to know
- Grep searches file contents for patterns; Glob matches file paths by name or extension pattern — content versus path is the dividing line.
- Read and Write handle full-file operations; Edit makes targeted modifications and requires unique anchor text.
- When Edit fails because the anchor text is not unique, fall back to Read followed by Write rather than retrying Edit.
- Build codebase understanding incrementally: Grep to find entry points, then Read to follow imports and trace flows — do not read every file upfront.
- To trace a function through wrapper modules, first identify all exported names, then search for each name across the codebase.
Give the learner a cheat-sheet grid: the six built-in tools are the rows, and the two columns are what the tool operates on and the stem wording that makes it the right choice. Relation labels are the cell values. Reading down the second column is what makes the scored Grep/Glob (content versus path) and Edit/Write (targeted versus whole-file) distinctions decidable from a question stem.
Show that Edit is gated on anchor-text uniqueness, and that the recovery path when it is not unique is Read followed by Write rather than another Edit attempt.
Worked examples
Exploring an unfamiliar legacy service
Scenario 4 · Developer Productivity with ClaudeAn engineer asks the agent how order cancellation works in a service nobody on the team wrote. The wrong instinct is to Read the whole orders/ directory into context.
The incremental path is much cheaper and more accurate: Grep for the handler name or the user-visible error string to find the entry point, Glob when the question is about which files exist rather than what they contain, then Read only the files the trace actually reaches, following imports one hop at a time. Bash covers verification once the flow is understood.
bash Grep to locate, Glob to enumerate, Read to follow
# 1. Find the entry point by searching CONTENTS
Grep "cancelOrder" # function definition and call sites
Grep "Order cannot be canceled" # the user-facing error message
# 2. Enumerate files by PATH pattern when that is the question
Glob "src/orders/**/*.ts"
Glob "**/*.test.tsx" # every test file, by naming pattern
# 3. Read only what the trace touches, following imports one hop at a time
Read src/orders/cancel.ts
Read src/orders/policy/refundWindow.ts
# 4. Verify behavior
Bash "npm test -- orders/cancel"Edit fails on a repeated line, so Read + Write finishes the job
Scenario 4 · Developer Productivity with ClaudeThe agent tries to change a timeout inside one function, but the anchor snippet timeout: 5000 appears in four places in the file. Edit cannot determine which occurrence is meant, so it fails on the non-unique match.
The correct recovery is the documented fallback: Read the full file, apply the change in the intended location, and Write the file back. Repeatedly retrying Edit with slightly different snippets is the tempting wrong move — each attempt is another guess at uniqueness, and it may silently modify the wrong occurrence if a guess happens to match.
text The failure and the fallback
Edit src/http/client.ts
old_string: "timeout: 5000"
new_string: "timeout: 15000"
-> FAILED: 4 matches found; old_string must be unique.
Fallback (reliable, no anchor required):
Read src/http/client.ts # load the full contents
Write src/http/client.ts # write back with the one intended changeTracing a function through wrapper modules
Scenario 4 · Developer Productivity with ClaudevalidatePayment is defined in payments/core, re-exported from payments/index.ts as validate, and re-exported again from a legacy compatibility shim as checkPayment. A single Grep for validatePayment finds three call sites and misses a dozen.
The prescribed technique is two-pass: first identify all exported names, then search for each name across the codebase. That is the only way to reach call sites that never mention the original identifier.
bash Pass 1 — enumerate exported names; pass 2 — search each one
# Pass 1: find every name the function is exported under
Grep "export .*validatePayment" # -> also exported as `validate`
Grep "export .*validate" # -> legacy shim re-exports as `checkPayment`
# Pass 2: search the codebase for EACH exported name
Grep "validatePayment"
Grep "\bvalidate\b"
Grep "checkPayment"
# Then Read the files that matter, following imports from each call siteAnti-patterns
- Using Grep to find files by name pattern instead of Glob (or Glob to find code by content instead of Grep) because content search and path matching are different jobs and the wrong tool returns the wrong shape of answer.
- Retrying Edit with a slightly different snippet after a non-unique match instead of falling back to Read plus Write because each retry is another guess at uniqueness and may modify the wrong occurrence.
- Reading every file in a directory upfront instead of using Grep to find entry points and then Read to follow imports because it fills context with code the trace never reaches.
- Running a single Grep for the original function name when wrapper modules re-export it under aliases, instead of first identifying all exported names and then searching for each because aliased call sites never mention the original identifier.
How it is examined
- Grep-versus-Glob items hinge on one word in the stem: "contents", "pattern in code", "error message", or "callers" means Grep; "file name", "extension", or a glob pattern like **/*.test.tsx means Glob.
- If a stem says Edit failed because the text was not unique, the credited answer is Read then Write. Distractors add more surrounding context to the anchor, or suggest a regex or line-number edit.
- Codebase-exploration items reward the incremental Grep → Read path and penalize reading everything upfront. For wrapper modules, the two-pass answer (enumerate exported names, then search each) beats any single-search option.
Why this is true
Six tools, six jobs
The built-in tools are not interchangeable, and the exam tests whether you reach for the right one first.
Grep— search file contents for a pattern: a function name, an error message string, an import statement. This is the tool for "who calls this?" and "where does this message come from?"Glob— match file paths by name or extension pattern. This is the tool for "find every test file", e.g.**/*.test.tsx. Glob answers questions about which files exist; Grep answers questions about what is inside them.Read— load a file's full contents. Use it once you know which file matters.Write— write a whole file.Edit— make a targeted modification by matching a unique string in the file.Bash— run commands: build, test, git, anything the shell can do.
Edit's precondition, and the fallback when it fails
Edit works by unique text matching. If the anchor text appears more than once — or not at all — the edit cannot be applied unambiguously and it fails. The prescribed recovery is Read the full file, then Write it back with the change applied. That path is reliable because it does not depend on locating a unique anchor at all. Recognizing this as the fallback (rather than retrying Edit with a slightly different snippet, or guessing at more context) is a scored skill.
Explore incrementally, not exhaustively
The intended workflow for an unfamiliar codebase is deliberately incremental: start with Grep to find entry points, then Read to follow imports and trace flows. You do not read every file up front. Reading broadly burns context on code that turns out to be irrelevant, while the Grep-then-Read loop keeps only what the trace actually touches.
A specific variant is worth memorizing because wrapper modules defeat naive search: to trace how a function is used across re-exporting layers, first identify all exported names, then search for each name across the codebase. A single Grep for the original function name misses every call site that reaches it through an alias. Two passes — enumerate the names, then search each one — is what gives complete coverage.
Exam guide, verbatim — what is measured
Knowledge of
- Grep for content search (searching file contents for patterns like function names, error messages, or import statements)
- Glob for file path pattern matching (finding files by name or extension patterns)
- Read/Write for full file operations; Edit for targeted modifications using unique text matching
- When Edit fails due to non-unique text matches, using Read + Write as a fallback for reliable file modifications
Skills in
- Selecting Grep for searching code content across a codebase (e.g., finding all callers of a function, locating error messages)
- Selecting Glob for finding files matching naming patterns (e.g., **/*.test.tsx)
- Using Read to load full file contents followed by Write when Edit cannot find unique anchor text
- Building codebase understanding incrementally: starting with Grep to find entry points, then using Read to follow imports and trace flows, rather than reading all files upfront
- Tracing function usage across wrapper modules by first identifying all exported names, then searching for each name across the codebase