Tool Design and MCP Integration
How to shape tool contracts so Claude chooses the right tool, recovers cleanly from failures, sees only the tools it needs, and connects to external systems through MCP without leaking secrets or wasting turns.
Tool design and MCP integration is where the exam tests whether Claude can act effectively in the world through the contracts you publish. The model never sees your backend code. It sees the tools array you send with each Messages request, where every tool carries a name, a description, and an input_schema that together determine both selection and execution. When those contracts are vague, overlapping, or too numerous, selection degrades, errors become unbranchable, and the agent burns turns recovering from misrouting that a few precise sentences would have prevented.
The domain is the tooling counterpart to agentic orchestration. Orchestration asks how you sequence capabilities and route information between agents. Tool design asks which capabilities you expose at all, how you shape their schemas so they are distinguishable, how you handle the four classes of failure so the agent knows whether to retry or to pivot, how you distribute tools across agents at four to five per turn, and how MCP servers are integrated so that shared configuration stays credential free and standard systems are not rebuilt as custom first steps. Expect scored scenarios that blend a minimal description with a plausible classifier, a generic error string with a retry loop, or an overloaded single agent with a second server that does not actually reduce the flat list.
This guide follows the five tasks exactly as tested. Task 2.1 makes selection reliable by writing production grade descriptions and fixing system prompt coupling. Task 2.2 gives every failure a structured envelope that separates access failure from valid emptiness and transient from validation, business, and permission. Task 2.3 reduces choice complexity by scoping per role, consolidating homogeneous variants into one parameterised tool, and using tool_choice modes correctly. Task 2.4 places MCP configuration where it belongs, keeps secrets out of the shared file with expansion, prefers community servers for standard systems, and makes MCP tools selectable through richer descriptions and Resources. Task 2.5 enforces the built in hierarchy of Grep for inside, Glob for names, and Edit with a unique anchor before Read plus Write, all through incremental discovery rather than bulk reading.
Tool Schema Design
Write production grade tool descriptions and strict schemas so Claude selects the right tool without guessing and executes it without type errors.
What you need to know
Tool descriptions are the primary mechanism through which Claude selects which tool to call for a given user request, and the interface definition as a whole determines whether that selection can be executed without error. The model never sees your backend implementation. It sees the JSON Schema you publish in the tools array of the Messages request, where each tool carries a name, a description, and an input_schema that describes its typed parameters. Selection and parameter synthesis flow from those three fields, and description quality dominates the selection half while schema hygiene dominates the execution half.
In current deployments, before any tool call is issued the model receives the full list of tool definitions alongside the conversation history and the system prompt. Claude's tool use reference describes the tools parameter as a list of tool definitions with name, description, and input_schema, and notes that while the schema is required for inference the descriptions are where you steer the model toward precise behaviour. When two tools have overlapping capability and their descriptions are minimal, for example get_customer described only as Retrieves customer information and lookup_order described only as Retrieves order details, the model cannot differentiate which to use when the user says check my order number 12345. Both read as retrieval tools over entities identified by an identifier like value, and with no distinguishing input formats, example queries, or boundary statements, selection defaults to plausibility rather than correctness, leading to systematic misrouting where order queries are dispatched to the customer tool.
A production grade description removes the guesswork with five elements, what the tool does stated unambiguously, what inputs it expects including formats and required versus optional fields, concrete example queries the tool handles well that anchor pattern matching, edge cases and limitations including what the tool does not do, and explicit boundaries that say when to use this tool versus any similar one in the set. Our material illustrates the difference directly. The minimal get_customer line gives no identifier type, no return shape, and no boundary, while the production variant states that it looks up a customer account by email, phone, or customer identifier, returns name, contact details, account status and loyalty tier, is for identity verification, and explicitly says do not use it for order queries where lookup_order should be used instead. The production variant for lookup_order mirrors that shape, stating that it requires order number in format number NNNNN or tracking identifier, returns order status, items, shipping details and refund eligibility, and excludes identity verification work.
Beyond individual description quality, the distribution of overlap matters. A generic analysis tool such as analyze_document that claims to analyse a document and return results is a single overloaded entry point for at least three distinct jobs that deserve distinct contracts, extracting structured data fields, summarising key arguments, and verifying whether a claim is supported by the source. Splitting the generic tool into extract_data_points, summarize_content, and verify_claim_against_source, each with a narrow description and typed input output contract, makes the selection decision crisp because the task description now aligns with one tool rather than with a vague umbrella. Renaming is a lighter version of the same fix. When two tools have confusingly similar names, giving one a behaviour anchored name such as extract_web_results and rewriting its description to be web specific resolves overlap at the interface without touching implementation.
System prompt wording can silently override good descriptions, which is why interface design cannot be reviewed in isolation. If the system prompt contains keyword sensitive instructions such as always check customer details before proceeding, the model may route any customer phrasing to get_customer regardless of retrieval context, creating an unintended tool association that no description broadening can correct without also revising the prompt. After improving descriptions, the correct step is to review the prompt for such cues and remove or clarify them, ensuring prompt and interface convey the same routing policy.
The decision framing that extends beyond this task is equally important. Descriptions are the fix when the agent has a workable number of tools and simply cannot tell two apart. When the toolkit is large, rewriting descriptions does not rescue selection because the agent is past the point where decision quality depends on wording and must instead have its tool count reduced, which is the subject of Task 2.3. Recognising which regime you are in prevents description work where distribution work is needed and vice versa.
How tool definitions travel to Claude
Tool definitions are transmitted in the Messages API request under the tools key as an array of objects, each containing name as a unique snake case identifier, description as free form natural language that the model uses to decide whether to call this tool, and input_schema as a JSON Schema object constraining what arguments Claude can pass. The input_schema uses standard JSON Schema keywords including type object, properties with per property type, description, enum, format, pattern, minimum, maximum, additionalProperties, and required as an array of keys that must be present. Optional flags on the tool object include strict as a top level boolean alongside name, description, and input_schema, which when set to true constrains token sampling so the resulting input field in the tool_use block is guaranteed to match the declared schema, with only the shape guaranteed not business validity.
The model response for a tool call is a message whose content array contains one or more tool_use blocks, each with type tool_use, id, name, and input matching the schema, and the stop_reason is tool_use when at least one such block is present. Claude returned tool results are sent back as user role messages containing tool_result blocks with tool_use_id copied from the originating block and content as a string, optionally with is_error when the tool signalled failure. This surrounding contract matters to schema design because the description and property level description fields are the only content the model sees before deciding to emit a tool_use block. Precision in those fields is therefore not documentation polish, it is the runtime control plane.
Schema hygiene that prevents execution failure
Every property should carry its own description with format guidance and examples, for example User identifier in USR dash XXXXX format such as USR dash 48721, and any parameter with a fixed set of valid values should use enum rather than relying on prose, because without an enum the model may invent values outside the allowed set. Required arrays should list only truly required fields, optional parameters with sensible defaults should be omitted from required so Claude can skip them, integer and number should be distinguished precisely, nesting should remain shallow with no more than two levels, and additionalProperties should be closed when stray keys would break backend validation.
A well defined schema that is also vague in description still misroutes, and a well described tool with a loose schema still fails at execution with type mismatches or missing fields. Strict tool mode as grammar constrained sampling guarantees that the input in the tool_use block matches the input schema exactly, including enum and type correctness, but it does not guarantee business validity. Hygiene and description are therefore a pair, and either alone leaves one half of the correctness equation uncovered.
From one vague tool to crisp selection via splitting and renaming
When a single tool must cover extracting data points, summarising, and verifying claims, its description cannot be crisp for any one of those jobs. The tool selection problem is then structural. Splitting replaces one generic tool with several narrow, well named tools, each with its own contract. After splitting, a summary task aligns with summarize_content and an extraction task aligns with extract_data_points, so selection is a match rather than a guess. This is a low infrastructure fix compared with adding a routing classifier that bypasses model judgement entirely.
Renaming is the lightest version of the same interface repair. When two tools have confusingly similar names, giving one a behaviour anchored name such as extract_web_results and rewriting its description to be web specific changes the lexical trigger the model pattern matches on. Keeping the original name and lengthening the description leaves the name ambiguity in place, so the fix must include the name change rather than only prose growth.
Prompt coupling and when wording stops rescuing selection
System prompt wording can reintroduce the ambiguity a description rewrite just removed. Keyword sensitive instructions that mention customer details or always check a particular entity create an unintended association that persists even after the description adds an explicit boundary. The interface fix must therefore be validated against prompt wording before being declared complete, with conflicting prompt phrasing removed or narrowed and stability remeasured on the same benchmark.
The four to five tool ideal and the hard ceiling beyond eighteen tools for a single agent establish a second regime. Where tools are few enough to handle yet two read alike, sharpen the descriptions as described above. Where many tools share a shape such as data in plus operation plus data out, consolidate into one parameterised tool rather than rewriting eighteen descriptions. Moving servers without merging does not change the flat list the model reasons over, so distribution must be evaluated on the merged prompt rather than per server.
Mechanism and API surface
- Messages API tools array
- Transmit tools as an array of name, description, and input_schema on every request. The model receives this list alongside system and history and emits tool_use blocks with type tool_use, id, name, and input when it chooses to act.
- JSON Schema keywords for input_schema
- Use type object, properties with per property type and description, enum for fixed sets, pattern and format for identifier shapes, minimum and maximum for ranges, additionalProperties false to close stray keys, and required listing only truly required fields.
- Strict mode as sampling constraint
- Set strict true alongside name, description, and input_schema to guarantee the emitted input matches the schema shape, including enum and type correctness. Shape is guaranteed, business validity remains host side.
- Five part description template
- State what the tool does, what inputs it expects with formats and required versus optional, concrete example queries it handles well, edge cases and what it does not do, and explicit when to use this tool versus its neighbor.
- Property level guidance and examples
- Every property carries its own description with format and example such as USR dash XXXXX with USR dash 48721 as example. Fixed sets use enum so the model does not invent values outside the allowed set.
- Splitting and renaming as interface repair
- Replace one generic umbrella with narrow typed tools such as extract_data_points, summarize_content, and verify_claim_against_source. Where names confuse, rename to a behaviour anchored form such as extract_web_results and rewrite the description to be scope specific.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| Production grade description with boundaries and examples | Minimal single sentence description | The production form carries purpose, expected inputs with formats, example queries, edge cases, and explicit when not to use guidance, while the minimal form supplies only a headline that two plausible tools share. |
| Tool splitting for overlapping single tool | Prompt level clarification for overlap | Splitting replaces one generic tool with several narrow, well named tools each with its own contract. Prompt clarification keeps the generic tool and tries to steer selection in text, which fails when the single entry point must cover distinct jobs. |
| Description driven fix | Routing classifier as first step | The description fix addresses why the model is confused between two tools in a small set. A classifier bypasses model judgement and adds infrastructure, and is over engineered as the first response when sentence level clarity would suffice. |
| Renaming with description rewrite | Keeping confusing names with longer prose | Renaming changes the lexical trigger the model pattern matches on. Keeping the original name and lengthening the description leaves the name ambiguity in place. |
| Interface scope for selection | Enforcement logic for compliance | Interfaces guide the model toward the right retrieval. Compliance such as refunds is enforced programmatically in executor code and not in description wording, so no description variant can replace a gate. |
Traps
The tempting answer. Add a few shot example block to patch selection when descriptions are the root cause because examples feel tangible.
Why it fails. Examples improve parameter ambiguity such as date formats or prefix conventions, but they impose token overhead and do not address the confusion between two tools on the selection axis, which is driven by what the descriptions say about when each tool applies.
What is correct. Rewrite both descriptions to the five part template with formats, example queries, and explicit boundaries, then remeasure routing before adding examples.
The tempting answer. Introduce a deterministic classifier that picks the tool before the model sees the prompt, guaranteeing selection between two similarly described tools.
Why it fails. That guarantee is premature when the underlying problem is sentence level vagueness and it adds a system dependency the exam treats as disproportionate early compared with a low effort description rewrite.
What is correct. Revise descriptions and system prompt coupling first, then evaluate whether a classifier is still needed under distribution pressure.
The tempting answer. Merge get_customer and lookup_order into one lookup with polymorphic identifier to remove the choice being made incorrectly.
Why it fails. Consolidation removes the choice but collapses distinct return shapes and business boundaries, leaving the model with one large schema where two focused ones would have been easier to choose between, with larger implementation cost than revising descriptions.
What is correct. Keep two focused tools with explicit format and boundary guidance and review prompt wording, using consolidation only for homogeneous overloads such as nineteen transform variants.
The tempting answer. Ship the description fix alone even where the prompt contains instruction templates such as always check customer details before proceeding.
Why it fails. Keyword sensitive prompt instructions can create unintended tool associations that override even well crafted descriptions, so prompt and interface must convey the same routing policy and the fix is incomplete without that alignment.
What is correct. Inspect the system prompt for triggers, remove or narrow the conflicting phrasing, and remeasure stability on the same benchmark.
The tempting answer. Keep analyze_document as one tool and lengthen its umbrella description rather than splitting into bounded tools because one tool feels simpler to maintain.
Why it fails. A generic analyser that returns results cannot simultaneously specify extraction, summary, and verification contracts crisply, and selection error persists until each job has its own typed entry point with narrow description and schema.
What is correct. Split the generic tool into extract_data_points, summarize_content, and verify_claim_against_source each with a focused description and typed contract.
Structured Error Responses
Return isError plus errorCategory and isRetryable so the agent can distinguish access failure from valid emptiness and retry only what is retryable.
What you need to know
When an MCP tool fails, the error response it returns to the agent carries more weight than the failure itself, because the agent's next decision is driven by that response, not by the exception you caught on the server. A generic message such as Operation failed contains no signal about what went wrong, whether the input was malformed, whether a policy blocked the request, whether credentials are missing, or whether a retry might succeed, and the model given no structured field to route on either retries blindly, escalates prematurely, or treats an error as a successful empty result. The MCP protocol provides an isError flag for exactly this purpose on tool_result like responses, signalling that execution failed so the model can reason about recovery rather than interpreting the error text as data. The flag alone is coarse, though, and production systems extend it with a small taxonomy that lets the model branch to the right recovery action.
Four categories describe the space of tool failures the exam tests, each demanding a distinct recovery strategy that only works when the agent can distinguish the category from structured metadata rather than from free text. Transient errors such as timeouts, overload, and rate limits are the one category where the underlying system is temporarily unreachable yet the request itself is valid, so the correct action is to retry the same call after a delay, ideally with exponential backoff and jitter at the executor layer before the model retries itself. Validation errors such as invalid formats, missing required fields, or out of range enum values reflect a malformed request, and the correct recovery is to reformulate the input, not to resend the same payload, so the error must include the accepted format and an example. Business errors such as refund exceeds policy limit represent requests that are technically correct but violate business constraints, and the correct action is to take an alternative path or escalate rather than retry. Permission errors such as access denied reflect missing credentials or insufficient scope on the caller, and again the correct action is to retry only as a different principal or to escalate, never to resend the same call and expect a different outcome.
A narrow convention clarifies how two fields interact. isRetryable answers one question only, will resending this exact request with no change in input, authentication, or business context succeed. Only transient failures get true on that field, because the request was valid and the system was briefly unavailable. Validation, business, and permission are false on that field, because something must change first, the input for validation, the request strategy or an escalation decision for business, and the caller for permission. The surrounding errorCategory field is where the recovery instruction lives when the retryable answer is false, telling the agent to correct an identifier, restructure a call, or seek an alternative workflow, and the distinction inside the false rows matters most, with validation recoverable by the agent alone and business and permission requiring a different path or a different principal. Reading isRetryable to decide whether to resend as is, and then reading errorCategory to decide what to do when retry is not the answer, is the token budget safe branching the exam expects.
A closely related distinction the exam tests directly is between an access failure and a valid empty result, which share no recovery logic yet are trivially confused when a tool returns an empty array for both cases. An access failure means the tool could not reach the data source because of a timeout, authentication failure, or service outage, so whether the data exists remains unknown and a retry may help. A valid empty result means the tool reached the source, ran the query, and found no matches, so the request succeeded, the absence is meaningful, and a retry will return the same absence. A scenario tested repeatedly illustrates the failure mode. A lookup tool returns an empty array for a customer that does not exist, and because the envelope cannot distinguish could not fetch from found nothing, the agent retries three times and then escalates, while a reviewer shows the account never existed and the data shape misled the model into treating a successful zero match as a retriable failure. The fix is to structure successful empties so the empty payload is materially distinct from an access failure, with isError false and resultCount zero plus descriptive text rather than an undifferentiated empty array on isError true.
In multi agent orchestration, the same taxonomy gains a local recovery dimension. Subagents handle transient failures themselves before involving the coordinator, propagating only unresolved failures upward with partial results and a record of what was tried, so the coordinator can distinguish found nothing from could not search and decide whether synthesis should proceed, retry, or escalate. That layering keeps retries cheap and local while preserving the signal the coordinator needs for global decisions without silently suppressing errors as empty successes.
The envelope and how isRetryable narrows to one meaning
At the protocol level, the standard MCP error representation for an unsuccessful tool call uses isError true in the tool response alongside content as a human readable message, while a successful call with no data returns isError false with a descriptive text payload and a typed field such as resultCount. The structured envelope that extends isError includes at minimum errorCategory as an enum string and isRetryable as a boolean, plus a message and an optional technicalDetail whose content is server side rather than model facing, and a context object that may carry resource, sanitised input, attemptNumber, retryAfterMs, or a suggestion the model can act on.
Category values may vary by codebase, references include transient, validation, business, permission, and in richer implementations rate-limit, auth, not-found, and permanent as distinct codes with matching retry semantics, where transient and rate-limit are retryable after delay and the rest are not. HTTP idioms align loosely but not perfectly with this taxonomy, which is why the envelope matters more than status code alone. A 503 behind a transient error suggests retry after backoff, a 422 on a validation error says correct the input, a 403 on a permission error says elevate or escalate rather than retry, and business errors rarely map to a single status code at all, often returning 200 with a domain reason.
Why valid emptiness must not look like failure
A lookup tool that returns an empty array for a customer that does not exist and also for a store timeout shares a data shape across two recovery branches that must not share. The agent parsing the phrase not found versus unavailable is fragile. The robust fix is to put valid emptiness on the success side of the envelope, with isError false, a text field that says the query executed successfully but found no matches, and a numeric resultCount of zero, so the empty payload is materially distinct from an access failure that carries isError true, errorCategory transient, and isRetryable true.
That split also prevents wasteful retry counts. A successful zero match will return the same zero on every retry, so branching on the envelope suppresses retries that string parsing would have triggered. The pattern extends to graceful degradation where a tool backed by several services returns what it can with per section degraded versus unavailable statuses, exposing degradedSections and availableSections to the agent so a partial dashboard still renders rather than failing the whole workflow.
Retry timing and where it belongs
In tool level retry wrappers, transient detection is encoded explicitly, for example checking status greater than or equal to 500, code equals TIMEOUT, or code equals RATE_LIMITED before choosing to retry, and distinguishing that detection from the outer isRetryable flag that the model reads in the tool result block. The executor layer owns exponential backoff with jitter, caps attempts, enforces maxDelayMs, and only then surfaces a structured failure to the model, rather than letting the model retry immediately and hammer a failing backend many times in rapid succession.
A backoff configuration object with maxAttempts, baseDelayMs, maxDelayMs, and useJitter, with a full jitter algorithm that scales baseDelayMs exponentially and caps at maxDelayMs, matches the executor timing principles in the loop harness rather than leaving cadence to the model. That separation is why transient is the only isRetryable true case. The request was valid, the system was briefly unavailable, and the same request after a delay has a chance to succeed without changing inputs or principals.
Branching table the agent follows after the error
Transient and rate-limit are retryable after delay, with the agent either retrying locally a few times or the executor handling the first retries before the model sees the result. Validation is not retryable as is but agent recoverable via correction, where the response names the expected format such as USR dash XXXXX and provides a suggestion to correct the prefix style. Business and permission are not retryable and not agent recoverable alone, requiring an alternative path or escalation because the policy or credential constraint will block every resend of the same payload.
Interpreting isRetryable false as abandon the task conflates three distinct non retryable branches. The correct reading is cannot retry this exact call as written, then consult errorCategory for what to do next, whether to correct the identifier, to restructure the call, to seek approval, or to escalate. That preserves budget and avoids both infinite retry loops on business policy errors and silent suppression that hides failure from the coordinator.
Mechanism and API surface
- MCP error envelope fields
- Return isError boolean, errorCategory enum such as transient, validation, business, permission, isRetryable boolean, message for the model, optional technicalDetail hidden from the user, and context with resource, sanitised input, attemptNumber, retryAfterMs, and suggestion.
- Transient and rate-limit as retryable
- System temporarily unavailable such as timeout, 503, or rate limit. The request is valid. Retry the same call after exponential backoff with jitter. Only this class gets isRetryable true.
- Validation as correction not retry
- Invalid format, missing required field, or out of range enum such as USR dash XXXXX expected. isRetryable false. The response includes expected format and example so the agent can reformulate the input rather than resending the same payload.
- Business and permission as alternative path
- Policy violation such as refund over threshold or access denied due to missing scope. isRetryable false. The agent must take an alternative workflow or escalate as a different principal rather than looping on the same amount or caller.
- Valid empty versus access failure
- Valid empty is isError false with descriptive text and resultCount zero, reached the source and found no match, never retried. Access failure is isError true with category transient and isRetryable true, did not reach the source.
- Executor owned backoff and jitter
- Tool level detection on status greater than or equal to 500 or TIMEOUT or RATE_LIMITED, retry one to three attempts with baseDelayMs scaled exponentially and capped at maxDelayMs with jitter, then surface structured failure to the model rather than model side immediate retry.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| Access failure | Valid empty result | Access failure did not reach the source and is isError true with category transient and retryable true. Valid emptiness reached the source, found no match, and is isError false with resultCount zero. |
| Transient | Validation | Transient is retryable as is after backoff. Validation is retryable only after reformulating the input, so isRetryable is false and the response names the expected format. |
| Validation | Business | Both are not retryable, but validation is recoverable by the agent alone by correcting fields, while business requires an alternative path or escalation because the policy will block every resend. |
| isRetryable true | errorCategory recovery | The boolean answers should I resend this exact call. The category answers what to do when I cannot, including correcting input, seeking approval, or trying a different workflow. |
| Structured envelope | Generic error string | The envelope carries isError, errorCategory, isRetryable, and a suggestion or context the model can act on. The generic string carries no branchable metadata and forces the model to guess. |
Traps
The tempting answer. Retry when the tool returns an empty array for a customer that does not exist because emptiness looks like failure.
Why it fails. Both cases produce empty data yet only access failures are retryable, and a successful query that finds nothing will return the same emptiness on every retry, burning turns before escalating to a human who has no better information.
What is correct. Treat isError false with resultCount zero as success with absence, report not found without retry, and keep retry for isError true with transient.
The tempting answer. Set isRetryable true for a validation error because the agent can correct the input and succeed, so it feels recoverable.
Why it fails. isRetryable answers whether this exact request would succeed on resend, and a string failing a format check will fail the same check every time until the input changes, so the correct value is false with recovery carried in errorCategory and the suggestion that says how to fix the input.
What is correct. Return isRetryable false for validation, include expected format and example, and let the agent reformulate rather than resend as is.
The tempting answer. Retry a refund over the threshold because retry is the default reflex for any tool that returns an error.
Why it fails. The policy that blocks the first call blocks every retry of the same amount with the same principal, so looping wastes turns and never changes the outcome, while the correct action is to take an alternative path or escalate to a human reviewer.
What is correct. Branch business to alternative workflow or escalation and do not loop on the same payload, using the structured reason to explain why to the user.
The tempting answer. Treat isRetryable false as stop entirely because false reads as end of recovery.
Why it fails. False splits into three distinct branches, validation correctable by the agent alone, business and permission requiring a different workflow or principal, and only treating the three separately yields the right branching rather than premature abandonment.
What is correct. Read isRetryable false as cannot retry this call as written, then read errorCategory to decide whether to correct the identifier, restructure the call, or escalate.
The tempting answer. Return an empty success from a subagent when its tool failed, to keep the coordinator input clean.
Why it fails. That hides failure information from the coordinator, prevents intelligent recovery such as retry or partial synthesis, and leads to output that silently omits the failed source with no indication of degraded coverage.
What is correct. Propagate the structured failure upward with partial results and a record of what was tried so the coordinator can distinguish found nothing from could not search.
Tool Distribution and Choice
Keep four to five role scoped tools per agent, consolidate homogeneous variants into one enum tool, and apply tool_choice modes to enforce structure where needed.
What you need to know
How many tools an agent sees on a turn, and how relevant each tool is to that agent's role, determines the reliability of tool selection more directly than any prompt refinement can compensate for. Our curriculum's distribution guidance states the optimal range as four to five tools per agent, scoped to that agent's specific role, with reliability degrading as more options compete for attention. Each additional tool adds description overhead to the context budget, one more option to weigh before invoking anything, and one more chance to pick a plausible sounding tool over the correct one, so quantity and relevance must both be managed together rather than optimised separately.
Role scoping answers relevance. A synthesis agent should not have web search tools, a web search agent should not have document analysis tools, and more generally each agent should see only the tools it needs for its defined responsibilities. Giving an agent tools outside its specialisation invites misuse, such as a synthesis agent running its own searches instead of using results already handed to it, duplicating work and bloating context with unverified claims. The same discipline applies when a synthesis agent needs occasional verification of simple facts. Routing every such fact check back through the coordinator adds two to three round trips per verification and in our material inflates latency by roughly forty percent for workloads where eighty five percent of verifications are simple single site lookups. The tested fix is a scoped cross role tool such as verify_fact given directly to the synthesis agent for the common simple case, while complex verifications that need multiple sources, cross referencing, or judgement continue to route through the coordinator, combining local speed for the frequent path with full pipeline quality where needed.
Consolidation answers overload when the overload itself is homogeneous. A data platform server with twenty two tools where three query tools serve distinct sources but nineteen transformation tools such as pivot, percentile normalisation, and currency conversion all share the shape data in, operation, data out, is not well served by splitting by role, because a transformation agent handed nineteen tools still carries almost the whole problem moved one level down and still cannot choose reliably. For such groups, the correct move is to collapse the nineteen into a single parameterised tool such as transform_data with dataset, transform_type as an enum of the operations, and an options object, reducing twenty two tools to four with no capability lost, because every operation remains reachable as a value the model chooses inside one call rather than a tool it must locate among near identical descriptions. The diagnostic for this distinction is to count the tools before reaching for a remedy. Two similar tools in a set of five is a description problem addressed in Task 2.1, while twenty plus tools where many are variations on one job signals a toolkit shape problem that no description rewrite resolves.
Least privilege is the companion constraint on scope. Replacing a generic fetch_url that can fetch any URL with a constrained load_document that validates document URLs only prevents misuse by capability removal, clarifies the tool's purpose through its narrowed description, and reduces the risk of unintended side effects such as fetching non document resources that should never have been in the agent's repertoire. The interface becomes self documenting because the name itself conveys the allowed scope without extra prose.
A framing that consistently traps candidates is where tool boundaries sit physically. Server boundaries are invisible to the model. A client hands the model every tool from every connected MCP server as one flat list, so a twenty two tool problem split across two servers remains a twenty two tool problem, and consolidation and scoping must be evaluated on the merged prompt not per server. That single fact explains why moving tools between servers is not a distribution fix and why per agent allowlists and consolidation are the levers that actually reduce choice complexity.
Role scoping and the four to five ideal
Scoping by role means each agent's allowlist contains only the tools that implement its responsibilities, with synthesis holding compile_report, format_citation, assess_coverage, and a scoped verify_fact for simple lookups, while web search holds search_web, fetch_page, extract_links, and save_snippet, and document analysis holds extract_metadata, extract_data_points, summarize_content, and verify_claim. Each agent now sees four domain focused tools, and cross role misuse disappears because synthesis no longer carries search tools at all where it might otherwise run its own searches and duplicate coordinator handed results with unverified claims.
The four to five ideal is not a stylistic preference but a measured planning limit. Beyond that per turn choice degrades irrespective of description quality, which is why adding descriptions to an eighteen tool agent is still the incorrect action compared with distributing those tools across role scoped agents. The exam probes this by offering improved wording as a distractor when the toolkit size itself is the cause, and expects distribution rather than narration.
Consolidation for homogeneous overloads
Homogeneous overloads are groups of tools that share an input output shape and differ primarily by operation name, such as nineteen transform tools that all take data in and return data out with operation as the varying part. Splitting by role does not help here because the transformation agent still holds nineteen near identical contracts after the split, leaving selection almost as hard as before. Consolidation replaces the group with one parameterised entry point that exposes transform_type as an enum of the operations and an options envelope for per operation parameters, so the model's decision collapses from which tool among nineteen to which enum value inside one call while preserving every capability.
Counting before acting diagnoses the regime. Two similar tools where the set size is five signals a description problem addressed in Task 2.1. Twenty plus tools where many are variations on one job signals a toolkit shape problem where no description rewrite rescues selection and consolidation is the correct first step. Batch parameter design as a single endpoint that accepts an array of parameter sets for server side fan out is a companion cost reduction that follows the same shape thinking.
tool_choice modes and least privilege
The Messages API tool_choice parameter controls how the model interacts with the available set on this turn. The default auto lets the model decide freely whether to call a tool or return text and is used for general conversation where some turns legitimately need no tool. Any forces the model to call a tool, letting it choose which one, and is used to guarantee structured output against one of several schemas when the document type is unknown, ensuring the model always produces a tool call rather than a conversational response which is valuable for pipelines where unstructured text would otherwise require reparsing. Forced selection with type tool and a name property requires the model to call exactly the named tool and is used to enforce mandatory first steps such as metadata extraction before enrichment, after which subsequent turns typically revert to auto for the remaining steps.
Least privilege sharpens both selection and safety. A constrained load_document that validates document URL shape and host allowlist or extension is preferable to a generic fetch_url that can reach any URL, because the narrowed tool's description is sharply indicative of when to use it and its capability removal prevents misuse by construction. Applying required at the load_document level to URLs while leaving generic fetch capabilities off the allowlist creates a workflow shape that is enforced outside the model's discretion rather than narrated into it.
Why server splits do not reduce the flat list
MCP project level dot mcp dot json carries the shared set and supports ENV_VAR expansion so all tools from every configured server become the single list the model ultimately reasons over. The Agent SDK adds options dot agents and options dot allowedTools with Task or Agent as the delegation meta tool, letting the coordinator assign four to five tools per specialist while keeping the coordinator itself lean without domain specific tools. Moving tools between servers without merging does not change that flat size, so overload persists after the move and only per agent allowlist trimming and enum consolidation actually reduce selection pressure.
That invisibility also interacts with cost heuristics. Where tools are few enough to handle yet two read alike, sharpen descriptions as in Task 2.1. Where tools belong to different jobs such as query, transform, and export, split by role at four to five tools each. Where many tools share a shape such as data in plus operation plus data out, consolidate into one parameterised tool. Producing those three before reaching for a server boundary change is the operational check the exam rewards.
Mechanism and API surface
- Per agent allowlists and coordinator leanness
- Use options dot agents and options dot allowedTools with Task or Agent as the meta tool, assigning four to five domain tools per specialist such as web search, document analysis, and synthesis, while the coordinator stays without domain tools and owns routing.
- Scoping versus packing
- Scoping keeps each turn's choice among four or five role specific tools and eliminates cross role misuse. Packing eighteen tools onto one agent raises decision complexity and misrouting irrespective of description quality.
- Homogeneous consolidation pattern
- Collapse nineteen near duplicate tools sharing data in, operation, data out into one parameterised tool such as transform_data with dataset, transform_type as an enum of operations, and an options object, preserving capability at lower choice complexity.
- Scoped cross role verify_fact
- Give synthesis a verify_fact limited to simple single source lookups for the eighty five percent frequent path, with description stating complex multi source verification should be escalated through the coordinator for full pipeline quality.
- tool_choice auto, any, and forced named
- auto lets the model decide freely whether to call a tool, any forces at least one tool call while letting the model choose which, forced named with type tool and a name enforces a mandatory first step such as extract_metadata on this turn.
- Flat list invisibility of server boundaries
- Every tool from every connected MCP server is merged into one prompt visible list. Splitting across servers without consolidation leaves the model's option count unchanged and is not a distribution fix.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| Scoping by role at four to five tools per agent | Packing all tools onto one agent | Scoping keeps each turn's choice among four or five role specific tools. Packing eighteen tools onto one agent raises decision complexity and misrouting irrespective of description quality. |
| Consolidating near duplicate tool variants into one parameterised tool | Splitting by role across agents | The consolidated form replaces nineteen tools that share a shape with one enum driven tool. Splitting separates tools that do different jobs, such as query versus transform, into different agents. |
| Scoped cross role verify_fact | Routing every verification through the coordinator | The scoped tool handles the eighty five percent simple cases locally in one hop. Coordinator routing for every verification adds two to three round trips regardless of complexity. |
| tool_choice auto | tool_choice any | auto leaves the model free to return text without a tool call, suitable for general turns. any forces at least one tool call, suitable where structured output from one of several schemas must be produced. |
| tool_choice any | Forced tool type tool with name | any forces some tool but lets the model choose which. Forced named selection enforces that a specific mandatory first step such as extract_metadata executes on this turn. |
Traps
The tempting answer. Centralise all fact verification at the coordinator because centralisation feels cleaner and more observable.
Why it fails. Centralisation for the simple single site share adds two to three hops per task and inflates latency by roughly forty percent for that share, where measurement shows eighty five percent of checks are simple lookups that a scoped verify_fact handles locally while complex checks remain centralised.
What is correct. Add a scoped verify_fact to synthesis for the simple path with explicit escalation language for multi source verification, keeping complex verification on the coordinator pipeline.
The tempting answer. Leave the turn on auto because auto feels safe and non coercive.
Why it fails. auto lets the model decide not to call a tool even when a structured extraction step is mandatory, so pipelines that require structured output must enforce it via any where the tool identity can vary or via forced named selection where the step is fixed.
What is correct. Use any to guarantee some structured call or forced type tool with name for a mandatory first step, reserving auto for conversational turns where a tool call is legitimately optional.
The tempting answer. Improve wording alone for a single agent with eighteen tools because the descriptions read well individually and tool count is only a number.
Why it fails. Decision complexity scales with tool count in the prompt, and the four to five per agent guidance is the tested anchor, so description work on a bloated agent leaves misrouting in place where distribution is the correct lever.
What is correct. Distribute via role scoping or enum consolidation before investing further in prose, measuring selection on the merged flat list rather than per server.
The tempting answer. Keep the generic fetch_url because flexibility feels valuable and avoids maintaining a narrower variant.
Why it fails. That flexibility enables misuse, muddies the tool's purpose for selection, and creates unintended side effects such as fetching non document resources, while the constrained load_document variant implements least privilege and makes description sharply indicative.
What is correct. Replace generic fetch with load_document that validates document URL shape and allowlist, keeping the narrower contract on the allowlist.
The tempting answer. Create a second MCP server and place half the tools there, assuming the model will see two smaller lists.
Why it fails. Server boundaries are not visible to the model, which receives one flat list from every connected server, so the overload persists after the move and only per agent allowlist trimming and consolidation actually reduce selection pressure.
What is correct. Evaluate overload on the merged prompt and apply scoping or enum consolidation rather than a server topology change.
MCP Server Integration
Scope MCP servers correctly, keep the shared file credential free with expansion, prefer community servers for standard systems, and make MCP tools selectable via enriched descriptions and Resources.
What you need to know
The Model Context Protocol, or MCP, is the standard open protocol that defines how hosts such as Claude Desktop and Claude Code connect to external systems such as databases, search APIs, version control hosts, and internal services through Servers that expose Resources, Tools, and Prompts over a JSON-RPC 2.0 transport. Because MCP decouples the language provider's request response so that tool discovery, invocation, and management happen over persistent connections with initialization, negotiated capabilities, and ordered shutdown, configuration scoping determines whether a team shares a consistent toolset or drifts into configuration chaos.
The scoping hierarchy is two tiered, and the line between the tiers is where most setup problems start. Project level configuration lives in dot mcp dot json at the repository root, is version controlled, and is shared automatically with every teammate who clones or pulls the repository. It is used for servers the entire team needs, such as Jira for issue tracking, GitHub for source control, or internal connectors that represent the shared workflow. A representative entry declares an mcpServers object mapping names to server specs with command, args, and env, for example command npx, args with dash y and at modelcontextprotocol slash server-github, and an env block that references a token variable rather than containing it. User level configuration lives in tilde slash dot claude dot json in the home directory, is personal, is not version controlled, and is not shared with teammates, and is used for experimental servers or personal integrations that are being evaluated before being proposed to the team. At connection time every tool from every configured server on either tier is discovered and available simultaneously with no manual activation step, and if a server is configured and reachable its tools appear in the agent's toolkit.
Credential handling follows from that bridge. The shareable file must not contain secrets, and MCP's variable expansion for dollar brace VARIABLE_NAME in dot mcp dot json keeps the shared file safe to commit because each developer sets their own values locally in a shell profile, dot env file, or secret manager, and rotation then requires no file change and leaves no secret in the repository history. A common vendor of this failure is a dot mcp dot json committed with the literal GITHUB_TOKEN value, which exposes a secret to every clone and every continuous integration log, whereas the correct file contains the reference syntax and every member authenticates with their own locally scoped token. The protocol itself does not define credential management beyond transport, so host level expansion plus local secret storage is the convention that makes shared configuration safe.
MCP Resources are the catalogue complement to Tools, and choosing to expose them well reduces wasted exploratory calls. Where a Tool lets the model take an action that may mutate the world, a Resource exposes read only, host controlled data such as issue summaries with titles and statuses, documentation hierarchies, or database schemas with table names, column types, and relationships, which the host can fetch and inject as context before the model is asked to choose Tools. Without that catalogue the model must discover what exists by calling a sequence like list_tables then describe_table for every table, burning turns purely to get bearings, while with an exposed schema Resource it knows immediately and can call the substantive Tools correctly. The read only distinction matters for the exam. Resources are not write capable, so examining a catalogue cannot create the side effects a misuse of a Tool call would.
Build versus use is the integration choice the exam tests most directly in this task, and it is consistently framed as a pragmatic first step before bespoke work. For standard integrations such as Jira, GitHub, Slack, Linear, or Notion, mature community MCP Servers already exist, are maintained and tested by the community, receive updates without local build cost, and handle the common use cases correctly. The correct response when a team needs one of those systems is therefore to evaluate existing community Servers first and only build custom when there are team specific workflows, custom business logic at the tool layer, or proprietary internal systems with no community equivalent. Building a custom Jira integration as the first response when a standard server would have sufficed trades maintained code for a one off fork that will diverge over time, which the exam consistently marks as the wrong priority.
Tool selection quality inside MCP also depends on the model being able to read what an MCP Tool is capable of at selection time, not merely that it exists. When two tools share a name stem or an overlapping capability, the agent may prefer a built in tool such as Grep over a semantically richer MCP search, because the built in tool's description is detailed while the MCP entry is sparse, even when the MCP implementation is more capable for the task at hand. The correction is to enhance MCP Tool descriptions to the same five part standard that governs API tools generally, stating the capability, the expected inputs, the returned shape and example outputs, edge cases, and explicit when to use this tool versus built in or sibling MCP tools, for instance calling out that AST aware indexing is more accurate than text search for intent, so the model's choice under tool_choice auto is guided by substance rather than by length of description. This pattern illustrates how MCP design feeds back into tool design, where the same description standard that removed ambiguity for get_customer versus lookup_order also determines whether an MCP catalogue is actually chosen in practice.
Scoping hierarchy and discovery without manual activation
Project dot mcp dot json at the repository root is version controlled and shared, carrying the mcpServers map for team wide dependencies. User tilde slash dot claude dot json is personal and never shared, carrying experimental or personal servers that are evaluated before being proposed to the team. The runtime property that bridges both tiers is that at connection time every tool from every configured server on either tier is discovered simultaneously with no manual activation step. If a server is configured and reachable its tools appear, which is why placing shared servers in the personal file or requiring a local activation command is the incorrect model of how discovery works.
That simultaneity interacts with distribution. Because the client merges all tools from all servers into one flat list, a team that fixes overload by moving tools to a second server has not reduced the model's option count. The correct fix remains per agent allowlist trimming and enum consolidation, while scoping decisions remain about sharing versus personal experimentation rather than about choice complexity.
Credential hygiene via expansion
The shareable dot mcp dot json must not contain literal secrets. MCP's variable expansion for dollar brace VARIABLE_NAME in that file keeps the shared file credential free because each developer sets values locally in a shell profile, dot env file, or secret manager, and rotation then requires no file change and leaves no secret in repository history. A common failure is a dot mcp dot json committed with the literal GITHUB_TOKEN value, which exposes a secret to every clone and every build log. The correct file contains the reference syntax and every member authenticates with their own locally scoped token, with continuous integration injecting the same variable from the secret manager.
This hygiene is convention rather than protocol, because MCP itself does not define credential management beyond transport. Host level expansion plus local storage is the boundary that makes shared configuration safe and reviewable, and it is the detail the exam probes when a file diff shows a literal token versus a reference.
Resources as catalogues that eliminate exploratory chains
Resources expose read only, host controlled data such as issue summaries, documentation hierarchies, or database schemas with table names, column types, and relationships, which the host can fetch and inject as context before the model chooses Tools. Without that catalogue the model must discover what exists by calling a sequence like list_tables then describe_table for every table, burning turns purely to get bearings, while with an exposed schema Resource it knows immediately and can call the substantive Tools correctly.
The read only distinction matters for exam reasoning. Resources are not write capable, so examining a catalogue cannot create the side effects a misuse of a Tool call would. Rendering schema or issue lists as Resources under a db colon slash slash style URI with declared mimeType therefore moves orientation cost from turns to one context injection, preserving budget for the substantive workflow.
Build versus evaluate and description enrichment for selection
For standard external systems such as Jira, GitHub, Slack, Linear, or Notion, mature community MCP Servers already exist, are maintained and tested, receive updates without local build cost, and handle the common use cases correctly. The correct response when a team needs one of those systems is to evaluate existing community Servers first and only build custom when there are team specific workflows, custom business logic at the tool layer, or proprietary internal systems with no community equivalent. Proposing custom Jira work as the opening move trades maintained code for a one off fork that will diverge, which the exam marks as the wrong priority.
Even after a server is chosen, selection quality depends on how selectable its tools appear. When a sparse MCP entry named search_codebase carries the description Searches code, the agent prefers Grep over that server even where semantic search would be better, because the built in tool has richer text. The correction is to enhance MCP Tool descriptions to the same five part standard, stating the capability as AST aware semantic search, the returned shape as functions and methods with file path and line number, and the scope advice such as Use this instead of Grep when searching by intent rather than by exact string, after which selection stabilises.
Mechanism and API surface
- Two tier configuration map
- Project dot mcp dot json at the repository root with mcpServers holding command, args, and env, version controlled and shared, versus user tilde slash dot claude dot json in the home directory, personal and never shared, with every tool from every server on either tier discovered simultaneously with no manual step.
- ENV_VAR expansion for secrets
- Write dollar brace VARIABLE_NAME in dot mcp dot json env blocks such as GITHUB_TOKEN, JIRA_URL, and JIRA_TOKEN, with each developer and CI injecting values locally via shell profile or secret manager, keeping the shared file credential free and rotation requires no JSON edit.
- JSON-RPC 2.0 lifecycle and pagination
- Every connection sequences initialize to exchange supported primitives and version, initialized to confirm readiness, then discovery calls tools slash list and resources slash list, and invocation via tools slash call with pagination via nextCursor as an opaque string the client must not parse.
- Transport choice without behaviour change
- Stdio wraps a child process with stdin and stdout for local development and fast iteration, Streamable HTTP exposes MCP over HTTP as stateless endpoints scaling across users, with legacy SSE superseded, selection does not change tool semantics but determines sharing scope.
- Resources versus Tools versus Prompts
- Resources are read only host controlled catalogues with uri, name, description, and mimeType, Tools are model controlled actions with name, description, and inputSchema that may mutate the world, Prompts are user task templates consumed as slash commands or skills, with transport security and sampling governed at the host.
- Enriched MCP description for selection
- Expand sparse entries such as Searches code into multi sentence statements that name AST aware capability, returned shape with file path and line number, and when to prefer this tool over Grep or sibling servers, so model choice under auto is guided by substance rather than description length.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| Project dot mcp dot json | User tilde slash dot claude dot json | Project file is at the repository root, version controlled, and shared by the team. User file is in the home directory, personal, and never shared. |
| Dollar brace ENV_VAR expansion in dot mcp dot json | Literal secret in dot mcp dot json | Expansion keeps the shared file credential free and lets each developer authenticate locally. A literal secret leaks to every clone and build history. |
| Community MCP Server for Jira or GitHub | Custom Server as first step | Community options are already maintained, tested, and updated for standard systems and should be evaluated first. Custom is reserved for team specific logic or proprietary systems with no community equivalent. |
| Resources as content catalogues | Tools as actions | Resources expose read only data such as schema or issue lists that the host fetches for context before tool choice. Tools mutate or query with side effects and are model controlled. |
| Enhanced MCP description that states output shape | Sparse MCP description such as Searches code | The enhanced description supplies capability, input format, output shape, and when to prefer this tool over Grep or sibling servers. The sparse one leaves the model to prefer the better described built in tool. |
Traps
The tempting answer. Propose a custom integration for Jira or GitHub immediately because custom sounds more tailored to the team's sprint model.
Why it fails. Evaluated community Servers for Jira, GitHub, Slack, Linear, and Notion already cover standard workflows, receive updates, and remove build and maintenance cost, so the exam consistently prefers evaluation before build and marks custom first as the wrong priority.
What is correct. Evaluate the community Server against the required fields and operations first, adopt it when it covers the scope, and reserve custom for team specific workflow fields or proprietary systems with no community equivalent.
The tempting answer. Store team wide servers in tilde slash dot claude dot json so each developer maintains their own connection.
Why it fails. That file is personal and not version controlled, so teammates do not inherit the same toolset on clone and the team drifts, while the repository root file is the correct shared model with automatic inheritance on pull.
What is correct. Place shared servers in dot mcp dot json at the repository root and keep only experimental personal servers in the home file.
The tempting answer. Paste the GITHUB_TOKEN value directly into dot mcp dot json because pasting feels simpler than managing environment injection.
Why it fails. That value then lives in every cloned history and every CI log, exposing the credential, whereas expansion keeps the JSON credential free while each environment injects its own secret and rotation requires no JSON edit.
What is correct. Write dollar brace GITHUB_TOKEN in the JSON and inject the actual token via shell profile or secret manager at the developer and CI layer.
The tempting answer. Leave the MCP entry as Searches code because the name feels self documenting and longer prose feels redundant.
Why it fails. Model selection at tool_choice auto is driven by available text, so a sparse MCP entry loses to a well described built in such as Grep even when the MCP index is semantically richer, and selection only stabilises after an enriched description.
What is correct. Rewrite the description to state AST aware semantic capability, returned shape with file path and line numbers, and when to prefer this tool over Grep or sibling servers.
The tempting answer. Discover an unfamiliar backend by iterating list_tables then describe_table for every table because iterative calls feel explicit.
Why it fails. That burns multiple turns purely to get bearings, while a Resource catalogue exposes schema with table names, column types, and relationships in one read only fetch that the host injects before tool choice, preserving budget for substantive calls.
What is correct. Expose the catalogue as a db colon slash slash style Resource with declared mimeType and consume it as context before invoking domain tools.
Built-in Tools
Use Grep for inside, Glob for names, and Edit with a unique anchor before Read plus Write, all through incremental discovery rather than bulk reading.
What you need to know
Claude Code includes six built in tools for codebase work, Read, Write, Edit, Bash, Grep, and Glob, and using the right one for the task is a source of measured productivity rather than style. The workflow the exam probes is codebase comprehension and modification at scale, where choosing between Grep and Glob, or between Edit and Read plus Write, determines whether context is spent well or burned, and whether results are complete or truncated. The distinctions are small in name but large in consequence, because each tool emits a different shape of evidence and costs a different amount of the context budget.
The sharpest distinction the task tests is between Grep and Glob. Grep searches file contents for patterns such as function names, import strings, error messages, and variable assignments. It answers what is inside files. Glob matches file paths by naming patterns such as extension or directory shape, answering which files exist by name without opening them. The sentence that anchors the difference is simple. Grep finds what is inside files, Glob finds files by their names. The exam deliberately presents scenarios where the wrong choice produces a null result or an expensive misfire. Using Glob to find callers of a deprecated function fails entirely, because Glob can only match star star slash star processLegacyOrder star as a file name pattern, not the string processLegacyOrder inside OrderProcessor dot ts. Using Grep to find test files by naming suffix works technically by searching content for the literal test substring, but is the wrong tool because Glob is purpose built for star star slash star dot test dot tsx style path matching and the exam expects that choice.
The file write family has an analogous preferred order. Edit performs a targeted modification via unique text matching, where you supply the precise old_string and its replacement and the tool touches only that occurrence. When old_string appears multiple times in the file, Edit fails by design rather than guessing which occurrence was meant, which is a safety property rather than a bug. When that happens the documented recovery is to widen the anchor with more surrounding context until the match becomes unique, or to set replace_all true if every occurrence should be changed within the same turn. Both options keep you on Edit, which costs almost no additional context, while falling back to Read plus Write, loading the entire file and rewriting it, is a context heavy fallback that the exam treats as a last resort rather than the immediate next step, because it spends a file's worth of tokens to change a line. The ordering therefore matters. Attempt Edit with the shortest plausible unique anchor, widen until it is unique on a non unique match or set replace_all where a global change was intended, and only escalate to Read plus Write when neither narrowing path can disambiguate the target.
The discovery shape around these tools matters as much as the tools themselves. The correct orientation is incremental discovery rather than eager bulk reading. Grep first for entry points such as the function name, class name, or error string that anchors the investigation to learn which files are relevant, then Read those specific files to follow imports and trace flows, then Grep again for any wrapper or re export names to find indirect consumers, and only Read what each prior discovery step justified. The opposite shape, loading every file into context before knowing what is relevant, is the highest cost discovery failure in this domain, because a two hundred file codebase read upfront can exhaust the window largely on files with nothing to do with the task at hand.
Tracing through wrapper modules is a specific instance of that shape. When a function is defined in one module, re exported through a wrapper or a barrel file such as index dot ts, a naive Grep for the original name misses every consumer who imports the wrapper name, so the correct trace is to Grep for the definition to find the defining file, Read the defining file to identify exported and re exported names, Grep for each of those names across the codebase, and if a barrel file is involved, Grep for the barrel module's import path to include consumers that never mention the function directly. That layered trace is what the exam contrasts with Glob first or Bash for both steps when built ins would suffice, and with bulk Read before any selection.
The deprecation scenario brings these choices together into the canonical examined sequence. To find every file that calls a deprecated function and every test that covers those files, the correct order is Grep for the function name to locate callers, including tests that import it directly because call content search surfaces them, then Glob for the sibling test of each caller file, such as star star slash OrderProcessor dot test dot star for OrderProcessor dot ts, to capture tests that cover the function transitively through the source module without mentioning it by name, then a further Grep for any wrapper or alias name under which the caller exposes the function, catching remaining tests that covered it through that alias. That Grep then Glob then Grep again pattern, content for direct references, path for adjacent tests, content for indirect coverage, is a structured trace that the exam contrasts with Glob first, Bash for both steps when built ins would suffice, and bulk Read before any selection.
Grep versus Glob by evidence shape
Grep accepts a pattern and an optional path scope and emits matches with file path, line number, and line content, which is why search plus Read is sufficient to reconstruct the call sites for the deprecation scenario without an intermediate Bash pipeline. Glob accepts a pattern such as star star slash star dot test dot tsx or star star slash config dot star scoped to the working tree and emits matching paths without reading contents. The evidence shapes are therefore complementary. Grep tells you where a string occurs inside files, Glob tells you which files exist by naming convention, and the question wording itself tells you which you need, inside versus named.
That complementarity explains the two most common misfires. Using Glob to find callers searches path names for a function name that lives inside files and returns nothing, because the name is not in the path. Using Grep to find files by extension searches content for the literal substring test rather than matching the path pattern star star slash star dot test dot tsx, which is technically possible yet imprecise and slower compared with Glob which matches the extension directly without opening contents.
Edit with widened anchor versus Read plus Write
Edit accepts old_string and new_string plus an optional replace_all and returns success or a structured non unique match error naming the count of locations, which the caller handles by supplying a longer surrounding anchor that includes unique neighbouring lines, comments, or function signatures, and Write accepts a full path and content payload and replaces or creates the file, which is why its token cost scales with file size on every path. Read accepts a file path and an optional range and returns the requested window with no evaluation of contents beyond text delivery, requiring the caller to supply surrounding context when Edit needs a wider anchor.
The cost ordering is therefore attempt Edit with the shortest plausible unique anchor, on non unique failure widen until unique or set replace_all true where a global change was intended, and only then escalate to Read plus Write. The widened anchor returned success in the worked trace with only a few extra lines, while the fallback would have consumed the entire file length in tokens for what remained a single line edit. That ordering is what the exam marks as the expected workflow rather than Read plus Write as the default.
Incremental discovery as budget control
The correct discovery shape is Grep first for entry points such as the function name, class name, or error string that anchors the investigation to learn which files are relevant, then Read those specific files to follow imports and trace flows, then Grep again for any wrapper or re export names to find indirect consumers, and only Read what each prior discovery step justified. Each Read is therefore justified by a prior hit, and context spend is proportional to relevance rather than to repository size.
The opposite shape, loading every file into context before knowing what is relevant, is the highest cost discovery failure in this domain. A two hundred file upfront read is primarily a budget burn that the exam explicitly warns against, because most files have nothing to do with the task at hand and their inclusion crowds out the synthesis and validation budget needed later for correct integration.
Wrapper tracing and the canonical deprecation sequence
When a function is defined in one module and re exported through a wrapper or a barrel file such as index dot ts, a naive Grep for the original name misses every consumer who imports the wrapper name. The correct trace is to Grep for the definition to find the defining file, Read the defining file to identify exported and re exported names including the wrapper, Grep for each of those names across the codebase, and if a barrel is involved Grep for the barrel module's import path to include consumers that never mention the function directly. That reconstruction is necessary because no single Grep sees the full consumer set without the intermediate Read that reveals what was wrapped.
The deprecation scenario assembles the whole chain into the canonical order the exam expects. Grep for the deprecated function name to locate callers, including tests that imported it directly. Glob for the sibling test of each caller file such as star star slash OrderProcessor dot test dot star for OrderProcessor dot ts to capture transitive coverage. Grep for any wrapper or alias name to catch remaining tests that covered the function through that alias. That Grep then Glob then Grep again content for direct, path for adjacent, content for indirect, is the structured trace that beats Glob first, Bash for both steps when built ins would suffice, and bulk Read before any selection.
Mechanism and API surface
- Grep for content
- Searches inside files for patterns such as function names or error strings, emitting file path, line number, and line content without path matching, and is the only choice for finding callers by name.
- Glob for path
- Matches file names by pattern such as star star slash star dot test dot tsx or star star slash config dot star, emitting matching paths without reading contents, and is the purpose built choice for test suffix or extension discovery.
- Edit via unique anchor
- Accepts old_string and new_string with exact text matching and optional replace_all. On non unique match the tool fails by design with count rather than guessing, and the correct recovery is to widen the anchor with surrounding signature or comments until unique.
- Read plus Write as fallback
- Read returns a requested window, Write replaces or creates the file with full content. Cost scales with file size on every use, so this path is the documented last resort after widened anchor and replace_all have been tried.
- Incremental discovery ledger
- Grep for entry points, Read only the hit files to follow imports, Grep for wrapper and alias names, Read only what that step justifies. No bulk upfront reads, with each Read justified by a prior hit.
- Wrapper and barrel tracing
- After finding the defining file via Grep and reading it for exported and re exported names, Grep for each name plus the barrel import path, catching consumers that never mention the deprecated function directly.
Distinctions that decide answers
| This | Not this | How to tell them apart |
|---|---|---|
| Grep | Glob | Grep searches what is inside files. Glob matches file names by path pattern. The question wording tells you which you need, inside versus named star star slash star dot test dot tsx. |
| Edit with unique anchor | Read plus Write for a one line fix | Edit touches exactly the identified string with minimal context. Read plus Write replaces the whole file and is a last resort because it costs far more tokens. |
| Widen anchor with surrounding context or replace_all true | Jump straight to Read plus Write on non unique match | The widened anchor or replace_all keeps the fix on Edit at nearly zero extra cost. Jumping to Read plus Write escalates immediately to the heavyweight fallback. |
| Incremental Grep then Read then Grep | Read all files upfront | Incremental traversal spends tokens only on files that prior Grep hits justified. Bulk reading upfront is the most expensive discovery failure in the built ins. |
| Bash find plus xargs grep as combined step | Grep for callers then Glob for sibling tests | Bash can surface a similar result set, but the exam's expected trace separates concerns by tool purpose and preserves the Grep then Glob then Grep shape for wrapper tracing. |
Traps
The tempting answer. Run Glob with a pattern such as star star slash star processLegacyOrder star because Glob accepts patterns that look like names.
Why it fails. Glob can only match star star slash star processLegacyOrder star against path names, not inside OrderProcessor dot ts where the call lives. Content search is always Grep and Glob returns nothing for this task.
What is correct. Use Grep with the literal function name and an appropriate path scope to retrieve call sites with file and line information.
The tempting answer. Search file contents for the literal test substring to find star star slash star dot test dot tsx files because Grep will technically surface the substring.
Why it fails. Grep surfaces test as a content hit not a path match, which is slower and less precise, while Glob is purpose built for extension and directory matching and matches the suffix directly without opening contents.
What is correct. Use Glob with star star slash star dot test dot tsx or star star slash config dot star for path based discovery by suffix.
The tempting answer. Load every file into context before any search because reading feels like diligence and avoids missing a hidden consumer.
Why it fails. A two hundred file upfront read is primarily a context budget burn that crowds out synthesis and validation, where incremental Grep for entry points then targeted Read for imports is the tested pattern that spends proportionally to relevance.
What is correct. Grep first for entry points, Read only the hit files, then Grep for wrapper names and Read only what that step justifies.
The tempting answer. Use Read plus Write for every file change because that path never raises a non unique anchor error.
Why it fails. It spends a file's worth of tokens on every line change. The exam marks the Edit first, widen then Write last ordering as the expected workflow, with Write as a last resort.
What is correct. Attempt Edit with the shortest plausible unique anchor, widen until unique or set replace_all where a global change was intended, and only then escalate to Read plus Write.
The tempting answer. Treat the non unique match error as proof that Edit cannot handle the file and escalate immediately to the heavyweight fallback.
Why it fails. The widened anchor path succeeded in the worked trace with only a few extra lines of surrounding signature and comment, while the fallback costs the entire file on every use, so escalation skips the cheaper correct recovery.
What is correct. Supply surrounding context that makes the anchor unique or select replace_all true for the global case, keeping the fix on Edit before considering Read plus Write.