CCA-F STUDY
← Domains·Domain 2· 18% of exam

Tool Design & MCP Integration

Tool Design & MCP Integration covers how an architect shapes the tool surface an agent reasons over: writing descriptions and boundaries that drive reliable selection, returning structured errors so the agent can recover, distributing scoped tools across specialized agents with the right tool_choice, wiring MCP servers into Claude Code and agent workflows, and choosing the right built-in tool (Read/Write/Edit/Bash/Grep/Glob) for the job. The unifying principle is agent-centric design: tools are a context-engineering surface, and small, deliberate refinements to names, descriptions, schemas, error shapes, and scope produce outsized gains in agent reliability. This domain is 18% of the CCA-F exam and is heavily scenario-based, testing whether you can diagnose misrouting, error-handling, and tool-bloat anti-patterns and apply the correct remediation.

What the exam expects
  • 01Treat the tool description as the model's only window into a tool. When a scenario shows two tools getting confused, the fix is almost always rename + rewrite descriptions to differentiate purpose, inputs, outputs, and when-to-use — not more system-prompt instructions.
  • 02Memorize the four tool_choice values and their exact JSON: {"type":"auto"} (default), {"type":"any"}, {"type":"tool","name":"..."}, {"type":"none"}. Know that any and tool prefill the assistant turn and suppress any natural-language preamble before the tool_use block.
  • 03For MCP errors, distinguish protocol errors (JSON-RPC error object, e.g. unknown tool / invalid arguments — discarded by the client) from tool execution errors (a successful result with isError: true — injected back into the model so it can recover). Business/validation failures belong in isError results with structured metadata, not protocol errors.
  • 04Know the three Claude Code MCP scopes and where they live: local (default, ~/.claude.json, private to one project), project (.mcp.json at repo root, shared via version control), user (~/.claude.json, all your projects). Environment-variable expansion ${VAR} and ${VAR:-default} works in command, args, env, url, and headers.
  • 05An empty result set is a SUCCESS, not an error. Returning isError:true (or a generic 'operation failed') for a valid query with zero matches is a classic trap — it triggers pointless retries.
  • 06Too many tools degrades selection. If a human can't say which of two tools to use, the agent can't either — scope each subagent to ~4-5 role-relevant tools and replace generic tools (fetch_url) with constrained ones (load_document).
  • 07For built-in tools: Grep = search file CONTENTS (ripgrep regex), Glob = match file PATHS/names (**/*.test.tsx), Read/Write = whole files, Edit = unique-anchor string replacement. When Edit can't find a unique match, fall back to Read then Write.
Task 2.1

Design effective tool interfaces with clear descriptions and boundaries

Official objective — Knowledge & Skills
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

Tool descriptions are the primary signal Claude uses to decide which tool to call, so they function as a context-engineering surface, not just documentation. Anthropic's guidance is to write each description the way you'd brief a new hire: make implicit context explicit, and cover what the tool does, when to use it (and when not to), what each parameter means, what it returns, and how it differs from similar tools. The API-side best practice is at least 3-4 sentences per description, with unambiguous parameter names (user_id, not user). For format-sensitive or nested inputs, the Messages API also supports an input_examples array of schema-valid examples that show concrete well-formed calls.

The dominant failure mode is overlapping or vague descriptions. If analyze_content and analyze_document have near-identical descriptions, Claude misroutes between them; if a human engineer can't definitively pick the right tool, the agent can't either. Two remedies: (1) rename and rewrite to eliminate overlap — e.g., rename analyze_content to extract_web_results with a web-specific description and clear input/output contract; (2) split an overloaded generic tool into purpose-specific tools with defined contracts — e.g., split a catch-all analyze_document into extract_data_points, summarize_content, and verify_claim_against_source. Meaningful namespacing (service- and resource-prefixed names like asana_search vs jira_search, or asana_projects_search vs asana_users_search) further disambiguates selection as the library grows.

System-prompt wording interacts with tool selection. Keyword-sensitive instructions can create unintended tool associations that override otherwise well-written descriptions — for example, a system prompt that keeps saying 'search the documents' can bias Claude toward a tool whose name contains 'search' even when a better tool exists. So reviewing the system prompt for keyword bias is part of tool-interface design. Anthropic notes that precise refinements to tool descriptions alone produced state-of-the-art SWE-bench results by sharply reducing error rates — evidence that description quality, not just code, drives reliability.

jsonCorrect patternStrong, differentiated description: purpose, valid input, output, and boundary
{
  "name": "get_stock_price",
  "description": "Retrieves the current stock price for a given ticker symbol. The ticker must be a valid symbol for a publicly traded company on a major US exchange (NYSE or NASDAQ). Returns the latest trade price in USD. Use when the user asks for the current or most recent price of a specific stock. It will NOT return any other company information.",
  "input_schema": {
    "type": "object",
    "properties": { "ticker": { "type": "string", "description": "Ticker symbol, e.g. AAPL for Apple Inc." } },
    "required": ["ticker"]
  }
}
jsonAnti-patternAnti-pattern: vague description leaves selection and inputs ambiguous
{
  "name": "get_stock_price",
  "description": "Gets the stock price for a ticker.",
  "input_schema": { "type": "object", "properties": { "ticker": { "type": "string" } }, "required": ["ticker"] }
}
jsonCorrect patternSplitting a generic tool into purpose-specific tools with clear contracts
// Instead of one analyze_document, expose:
// extract_data_points(document_id)        -> structured fields
// summarize_content(document_id, length)  -> prose summary
// verify_claim_against_source(claim, document_id) -> supported/unsupported + evidence
Anti-patterns & traps
TrapWhy it failsCorrect pattern
Two tools with near-identical descriptions (e.g., analyze_content vs analyze_document).Claude selects on description text; overlapping descriptions give no basis to distinguish them, so it misroutes between them.Rename one to its specific job and rewrite descriptions so each states a distinct purpose, inputs, outputs, and when-to-use (e.g., analyze_content -> extract_web_results).
Fixing misrouting by adding more imperative instructions in the system prompt.Keyword-sensitive system-prompt phrasing can create unintended associations that bias selection toward a wrongly-named tool, fighting the descriptions instead of fixing them.Fix the tool descriptions/names first, then audit the system prompt for keyword bias that pulls Claude toward a particular tool.
One generic do-everything tool (analyze_document) handling many distinct jobs.An overloaded contract makes inputs/outputs ambiguous and forces Claude to guess intent, lowering reliability.Split into purpose-specific tools (extract_data_points, summarize_content, verify_claim_against_source) each with a defined input/output contract.
Must-know
  • The description is the main mechanism for tool selection; minimal/vague descriptions cause unreliable choice among similar tools.
  • Cover purpose, when-to-use vs alternatives, input format, parameter meanings, outputs, edge cases, and boundaries; aim for 3-4+ sentences.
  • Eliminate overlap by renaming + rewriting (analyze_content -> extract_web_results) and by splitting generic tools into purpose-specific ones.
  • Use unambiguous parameter names (user_id) and meaningful namespacing (asana_search vs jira_search).
  • Review the system prompt for keyword-sensitive phrasing that can override good descriptions and create unintended tool associations.
  • input_examples lets you attach schema-valid example calls for complex/format-sensitive tools.
Practice — 3 questions for this taskDrill Task 2.1
Task 2.2

Implement structured error responses for MCP tools

Official objective — Knowledge & Skills
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)

MCP tools report failures in two distinct channels, and choosing the right one determines whether the agent can recover. Protocol errors are standard JSON-RPC error objects (code/message) used for issues like an unknown tool name or invalid arguments; the MCP client typically captures and discards these, so the model never sees them and cannot react. Tool execution errors are returned as a successful JSON-RPC result whose payload sets isError: true alongside content describing the failure. Because an isError result is injected back into the model's context just like a normal result, the model can read it and decide what to do next. The rule of thumb: anything you want the agent to recover from or explain to the user (API failures, invalid input data, business-logic violations) belongs in an isError result, not a protocol error.

Uniform errors are the core anti-pattern. A generic 'Operation failed' gives the agent no basis to pick a recovery path, so it may retry a non-retryable failure, give up on a transient one, or surface a confusing message. The fix is structured error metadata: classify the failure (errorCategory: transient | validation | business | permission), include an isRetryable / retriable boolean, and provide a human-readable, customer-friendly explanation. Transient errors (timeouts, service unavailable) are retryable; validation, permission, and business-rule violations generally are not, and marking them retriable: false prevents wasted retry loops. For business-rule violations, a clear customer-facing message lets the agent communicate appropriately instead of looping.

In multi-agent designs, recover locally where possible: a subagent should handle transient failures itself (e.g., retry with backoff) and only propagate to the coordinator the errors it cannot resolve locally — and when it does, it should pass along partial results and what it already attempted, so the coordinator doesn't redo work. Finally, distinguish access failures from valid empty results: a query that legitimately matches nothing is a SUCCESS that returns an empty set, not an error. Returning isError: true for zero matches mislabels a successful query and triggers pointless retries.

jsonCorrect patternStructured, recoverable tool execution error (isError result)
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [{ "type": "text", "text": "Refund exceeds the $500 policy limit for this account." }],
    "structuredContent": {
      "errorCategory": "business",
      "isRetryable": false,
      "message": "Refund amount $750 exceeds the per-transaction limit of $500. Ask the customer to split the request or escalate."
    },
    "isError": true
  }
}
jsonAnti-patternAnti-pattern: uniform opaque error gives the agent nothing to act on
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [{ "type": "text", "text": "Operation failed." }],
    "isError": true
  }
}
jsonExampleProtocol error (JSON-RPC) for invalid arguments / unknown tool
{
  "jsonrpc": "2.0",
  "id": 3,
  "error": { "code": -32602, "message": "Unknown tool: invalid_tool_name" }
}
Anti-patterns & traps
TrapWhy it failsCorrect pattern
Returning a generic 'Operation failed' for every failure.Uniform errors carry no category or retryability signal, so the agent can't choose a recovery path and may retry non-retryable failures or abandon transient ones.Return structured metadata (errorCategory, isRetryable, human-readable message) in an isError result so the agent recovers or explains appropriately.
Flagging a valid query with zero matches as isError: true.An empty result is a successful query; labeling it an error triggers needless retries and obscures that the operation actually worked.Return a successful result with an empty set; reserve isError for genuine access/validation/business failures.
Surfacing business-rule and validation failures as JSON-RPC protocol errors.Protocol errors are captured and discarded by the client, so the model never sees them and can't recover or relay a customer-friendly explanation.Report execution failures as isError results with retriable:false and a clear message; reserve protocol errors for unknown tools / invalid arguments.
Must-know
  • Two channels: JSON-RPC protocol errors (unknown tool/invalid args, discarded by client) vs tool execution errors (result with isError: true, fed back to the model).
  • Put recoverable failures (API/validation/business/permission) in isError results so the agent can read and act on them.
  • Return structured metadata: errorCategory (transient/validation/business/permission), isRetryable/retriable boolean, and a human-readable message.
  • Transient = retryable; validation/permission/business = not retryable. Mark retriable:false to stop wasted retries.
  • Subagents recover transient failures locally and propagate only unresolved errors with partial results and what was attempted.
  • A valid empty result set is a success, not an error — never flag zero matches as isError.
Practice — 3 questions for this taskDrill Task 2.2
Task 2.3

Distribute tools appropriately across agents and configure tool choice

Official objective — Knowledge & Skills
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

How tools are distributed across agents is as important as how they're described. Giving one agent too many tools (e.g., 18 instead of a focused 4-5) increases decision complexity and degrades selection reliability, because each extra tool is another option Claude must weigh on every turn. Agents also tend to misuse tools outside their specialization — a synthesis agent handed web-search tools will attempt searches it shouldn't. The remedy is scoped tool access: give each subagent only the tools its role needs, plus a small number of constrained cross-role tools for specific high-frequency needs, and route complex cases back through the coordinator.

In the Claude Agent SDK and Claude Code, you scope tools per subagent with the AgentDefinition tools field (an allowlist) and optionally disallowedTools (a denylist; when both are set, disallowedTools wins). Omitting tools makes the subagent inherit all parent tools — convenient but the opposite of scoping. Common role bundles: read-only analysis = [Read, Grep, Glob]; test execution = [Bash, Read, Grep]; code modification = [Read, Edit, Write, Grep, Glob]. Prefer constrained alternatives over generic capabilities: replace a broad fetch_url with a load_document tool that validates document URLs, so the agent can't wander the open web. For a synthesis agent that occasionally needs to check a fact, give it a single scoped verify_fact tool rather than a general search tool, and send anything more complex through the coordinator.

tool_choice (Messages API) controls whether and which tool Claude calls on a turn. The four options: {"type":"auto"} (default — Claude decides whether to call a tool), {"type":"any"} (must call some tool, model picks which), {"type":"tool","name":"..."} (force a specific tool), and {"type":"none"} (no tools). Use forced selection to guarantee an ordering — e.g., force extract_metadata first, then let enrichment tools run on follow-up turns. Use any when you need a guaranteed tool call rather than conversational text. Note: with any or tool, the API prefills the assistant turn, so Claude emits no natural-language preamble before the tool_use block; and any/tool are incompatible with extended thinking.

pythonCorrect patternScoped, specialized subagents via AgentDefinition tools allowlist
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep", "Glob", "Agent"],
    agents={
        "code-reviewer": AgentDefinition(
            description="Read-only security/quality reviewer.",
            prompt="Review code; never modify files.",
            tools=["Read", "Grep", "Glob"],          # no Edit/Write/Bash
        ),
        "test-runner": AgentDefinition(
            description="Runs and analyzes test suites.",
            prompt="Run tests and report results.",
            tools=["Bash", "Read", "Grep"],           # scoped to its role
        ),
    },
)
jsonCorrect patternForcing a specific tool first, then any vs none
// Force extract_metadata to run before enrichment tools:
{ "tool_choice": { "type": "tool", "name": "extract_metadata" } }

// Guarantee the model calls *some* tool rather than chatting:
{ "tool_choice": { "type": "any" } }

// Default: let Claude decide:
{ "tool_choice": { "type": "auto" } }
pythonAnti-patternAnti-pattern: one agent inherits everything (no scoping)
# Omitting `tools` makes the subagent inherit ALL parent tools (18+),
# inflating decision complexity and inviting out-of-role misuse.
AgentDefinition(
    description="Synthesis agent.",
    prompt="Synthesize findings.",
    # tools omitted -> inherits web search, bash, write, etc.
)
Anti-patterns & traps
TrapWhy it failsCorrect pattern
Giving every agent the full toolset (e.g., 18 tools) so it 'can do anything'.More tools means more options to weigh each turn, degrading selection reliability and letting agents misuse tools outside their specialization.Scope each subagent's tools field to ~4-5 role-relevant tools; add only narrow cross-role tools for specific high-frequency needs.
Handing a synthesis agent a generic fetch_url or web-search tool.Agents misuse out-of-specialization tools (a synthesis agent attempting web searches), producing off-task behavior.Replace generic tools with constrained ones (load_document that validates document URLs) and route complex needs through the coordinator; give only a scoped verify_fact tool if it truly needs one.
Using tool_choice:'any' or a forced tool together with extended thinking, or expecting a chatty preamble before a forced call.any and tool prefill the assistant turn (suppressing preamble) and are unsupported with extended thinking, causing 400 errors or missing explanations.Use tool_choice:auto with an explicit instruction when you need natural-language context, and reserve any/tool for guaranteed/forced calls without extended thinking.
Must-know
  • Too many tools per agent (18 vs 4-5) raises decision complexity and degrades selection; scope each agent to role-relevant tools.
  • Agents misuse out-of-specialization tools (a synthesis agent doing web searches); restrict the tool set to prevent it.
  • Scope tools via AgentDefinition tools (allowlist) / disallowedTools (denylist); both set means disallowedTools wins; omitting tools inherits all.
  • Replace generic tools with constrained ones (fetch_url -> load_document that validates document URLs); give scoped cross-role tools (verify_fact) for high-frequency needs and route complex cases through the coordinator.
  • tool_choice: auto (default), any (must call a tool), tool+name (force one), none (no tools).
  • Force a tool to guarantee ordering (extract_metadata first); use any to guarantee a tool call. any/tool prefill the turn (no preamble) and are not supported with extended thinking.
Practice — 3 questions for this taskDrill Task 2.3
Task 2.4

Integrate MCP servers into Claude Code and agent workflows

Official objective — Knowledge & Skills
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

Claude Code connects to external tools through MCP servers, and where you configure a server determines who can use it. There are three scopes: local (the default) is stored under your project's entry in ~/.claude.json and stays private to you in that one project; project is stored in a .mcp.json file at the repository root and is meant to be committed so the whole team gets the same tooling; user is stored in ~/.claude.json and loads across all your projects but stays private to you. Use project scope for shared team integrations and user/local scope for personal or experimental servers. Project-scoped servers from .mcp.json require your approval before first use, for security.

.mcp.json supports environment-variable expansion so secrets never get committed: ${VAR} expands to the variable's value and ${VAR:-default} supplies a fallback. Expansion works in the command, args, env, url, and headers fields — so you can write "Authorization": "Bearer ${GITHUB_TOKEN}" and keep the token in your shell environment. If a referenced variable is unset and has no default, Claude Code fails to parse the config.

At connection time Claude Code discovers the tools (and prompts and resources) from every configured server, and they're all available simultaneously; servers also support list_changed notifications to update their offerings live. Because Claude can fall back to built-in tools (like Grep) when an MCP tool's purpose is unclear, enhance each MCP tool's description to spell out its capabilities and outputs so Claude prefers the more capable MCP tool. For standard integrations (Jira, GitHub, Sentry), prefer an existing community/Directory MCP server over a custom build, and reserve custom servers for team-specific workflows. Finally, MCP resources expose content catalogs — issue summaries, documentation hierarchies, database schemas — that the agent can reference (via @server:protocol://path mentions in Claude Code) to gain visibility into available data without burning turns on exploratory tool calls.

jsonCorrect patternProject-scoped .mcp.json with env-var expansion (committed, no secrets)
{
  "mcpServers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/",
      "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" }
    },
    "api-server": {
      "type": "http",
      "url": "${API_BASE_URL:-https://api.example.com}/mcp",
      "headers": { "Authorization": "Bearer ${API_KEY}" }
    }
  }
}
bashCorrect patternAdding servers at each scope from the CLI
# Project scope -> writes .mcp.json (shared with team)
claude mcp add --transport http jira --scope project https://mcp.example.com/mcp

# User scope -> ~/.claude.json (all your projects)
claude mcp add --transport http hubspot --scope user https://mcp.hubspot.com/anthropic

# Local scope (default) -> private to you in this project
claude mcp add --transport http stripe https://mcp.stripe.com
jsonAnti-patternAnti-pattern: hardcoded secret committed in project .mcp.json
{
  "mcpServers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/",
      "headers": { "Authorization": "Bearer ghp_REAL_TOKEN_HARDCODED" }
    }
  }
}
Anti-patterns & traps
TrapWhy it failsCorrect pattern
Hardcoding API tokens directly in a committed project .mcp.json.The .mcp.json is checked into version control, so the secret leaks to everyone with repo access.Use ${VAR} / ${VAR:-default} expansion in headers/env/url and keep the actual value in the shell environment or a local .env.
Building a custom MCP server for a standard integration like Jira or GitHub.It duplicates maintained community/Directory servers and adds maintenance burden for no differentiation.Adopt an existing community/Directory MCP server for standard integrations; build custom servers only for team-specific workflows.
Leaving MCP tool descriptions thin and assuming Claude will use them.When an MCP tool's purpose is unclear, Claude falls back to built-in tools (like Grep) instead of the more capable MCP tool.Enhance MCP tool descriptions to spell out capabilities and outputs, and expose content catalogs as resources so Claude prefers and discovers them.
Must-know
  • Three scopes: local (default, ~/.claude.json, private/one project), project (.mcp.json at repo root, shared via version control), user (~/.claude.json, all your projects, private).
  • Use ${VAR} and ${VAR:-default} expansion in command, args, env, url, headers to keep credentials out of source control; missing required vars fail config parsing.
  • Tools/prompts/resources from all configured servers are discovered at connection time and available simultaneously; list_changed updates them live.
  • Write detailed MCP tool descriptions so Claude prefers them over built-in tools (e.g., a capable MCP search vs built-in Grep).
  • Prefer existing community/Directory MCP servers for standard integrations (Jira); reserve custom servers for team-specific workflows.
  • Expose content catalogs as MCP resources so agents see available data without exploratory tool calls.
Practice — 3 questions for this taskDrill Task 2.4
Task 2.5

Select and apply built-in tools (Read, Write, Edit, Bash, Grep, Glob) effectively

Official objective — Knowledge & Skills
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

Claude Code's built-in tools each have a precise job, and choosing the right one is a core architecture skill. Grep searches file contents for patterns — function names, error-message strings, import statements — and is built on ripgrep, so it uses ripgrep regex (escape metacharacters; interface{} becomes interface{}), respects .gitignore by default, and supports output modes (files_with_matches default, content, count) plus glob/type scoping. Glob, by contrast, matches file paths by name pattern (/*.test.tsx, src//*.ts), returns up to 100 files sorted by modification time, and does not respect .gitignore by default. The mnemonic: Grep finds files by what's inside; Glob finds files by their name.

Read loads full file contents (with line numbers; always pass absolute paths) and also handles images, PDFs, and notebooks. Write creates a new file or completely overwrites an existing one (it does not append or merge), and overwriting an already-existing file requires having Read it first in the session. Edit performs targeted, exact string replacement: it takes old_string and new_string, uses no regex or fuzzy matching, and requires that the file was Read first, that old_string matches exactly (whitespace included), and that old_string is unique in the file. When old_string isn't unique, Edit fails — the fixes are to supply a longer string with enough surrounding context to pin one occurrence, set replace_all: true to change them all, or fall back to Read + Write to rewrite the whole file reliably.

The skill the exam tests most is incremental codebase understanding: don't read every file upfront. Start with Grep to find entry points (a route registration, a CLI main, an error string), then Read those files and follow their imports to trace flows on demand. To trace how a function is used across wrapper modules, first identify all the exported names (the wrappers may re-export under different names), then Grep for each exported name across the codebase to find every caller. This Grep-then-Read-then-follow-imports loop keeps context lean and is the correct pattern versus bulk-reading.

textCorrect patternRight tool for each job
Grep  -> find all callers of computeTotal(  | locate "connection refused" error string
Glob  -> **/*.test.tsx                       | src/**/*.ts
Read  -> open and inspect a known file (absolute path)
Edit  -> change one unique line in a file you've Read
Write -> create a new file, or fully replace one you've Read
textCorrect patternIncremental tracing loop (correct exploration pattern)
1) Grep "app.get(" or the route name        -> find entry points
2) Read the matched handler file
3) Follow its imports: Read each imported module on demand
4) Grep each exported symbol name to find every caller across the repo
textAnti-patternAnti-pattern: Edit with a non-unique anchor, and bulk reading
// Edit fails: old_string "return result" appears 6 times -> not unique
Edit(old_string="return result", new_string="return cachedResult")
// Fix: longer context to pin one match, or replace_all:true, or Read+Write the file

// Also avoid: Read every file in the repo before doing anything
// -> bloats context; Grep first, then Read what matters.
Anti-patterns & traps
TrapWhy it failsCorrect pattern
Using Glob to find code by content, or Grep to find files by name.Glob matches paths/names, not file contents; Grep matches contents, not filenames — using the wrong one returns the wrong results.Grep for content (function callers, error strings, imports); Glob for path/name patterns (**/*.test.tsx).
Calling Edit with an old_string that appears more than once in the file.Edit requires a unique match (no regex/fuzzy); a non-unique anchor makes the edit fail.Supply a longer string with surrounding context, use replace_all:true, or fall back to Read + Write to rewrite the file.
Reading every file in the codebase upfront to 'understand' it.It floods the context window and wastes turns; most files are irrelevant to the task.Grep for entry points, Read the relevant files, follow imports on demand, and Grep each exported name to trace usage across wrappers.
Must-know
  • Grep = search file CONTENTS (ripgrep regex, respects .gitignore, modes: files_with_matches/content/count); Glob = match file PATHS/names (**/*.test.tsx, up to 100 results, ignores .gitignore by default).
  • Read = load full file (absolute paths, line numbers, also images/PDFs/notebooks); Write = create or fully overwrite (no append/merge; must Read an existing file first).
  • Edit = exact, unique-anchor string replacement; no regex/fuzzy; requires read-before-edit, exact match, and uniqueness.
  • When Edit's old_string isn't unique: add surrounding context, use replace_all:true, or fall back to Read + Write.
  • Build understanding incrementally: Grep for entry points -> Read -> follow imports, instead of reading everything upfront.
  • Trace usage across wrappers: enumerate all exported names first, then Grep each name across the codebase to find callers.
Practice — 2 questions for this taskDrill Task 2.5
Ready to test it?

Drill Tool Design & MCP Integration

16 scenario-based questions, timed, with full explanations.

Start the Tool drill →