Tool Design & MCP Integration
CCA Foundations · Exam Prep Guide · ImagineX Consulting
Design effective tool interfaces with clear descriptions and boundaries
In production logs, an agent keeps calling get_customer when users ask about orders — both tools carry minimal descriptions ("Retrieves customer information" / "Retrieves order details") and accept similar identifiers, so Claude can't tell them apart. The tool description is the primary mechanism Claude uses to pick a tool; thin or overlapping ones make selection unreliable among similar tools.
The fix is to expand each description with the input formats it accepts, example queries, edge cases, and explicit boundaries — when to use it versus a similar alternative. When two tools still overlap, rename one to its specific job (analyze_content → extract_web_results with a web-specific description) or split a generic tool into purpose-specific ones with defined input/output contracts (analyze_document → extract_data_points, summarize_content, verify_claim_against_source). Then review the system prompt: keyword-sensitive instructions can create unintended tool associations that override even a well-written description.
Key Behaviors
- Differentiate every description: state purpose, expected inputs, outputs, and when to use it versus a similar alternative
- Include input formats, example queries, edge cases, and boundary explanations — not just a one-line summary
- Eliminate overlap by renaming (
analyze_content→extract_web_results) so each tool's name and description map to one job - Split a generic tool into purpose-specific tools with defined input/output contracts (
extract_data_points,summarize_content,verify_claim_against_source) - Audit the system prompt for keyword-sensitive wording that builds unintended tool associations and overrides the descriptions
How It Works
Distractor Traps (common wrong answers)
- "Add few-shot examples" to the system prompt as the first-step fix — adds token overhead without fixing the minimal descriptions
- "Implement a routing layer" that parses input and pre-selects a tool by keywords — over-engineered, bypasses Claude's language understanding
- "Consolidate both tools into a single tool" (e.g.
lookup_entity) that internally branches — more effort than a first step warrants - Leaving "ambiguous or overlapping tool descriptions" or "minimal descriptions" in place
Recommended Material
Implement structured error responses for MCP tools
When an MCP tool fails, a generic "Operation failed" tells the agent nothing — it can't decide whether to retry, fix its input, or escalate, so uniform error responses block appropriate recovery. MCP signals a failure with the isError flag on the tool result; what makes that flag useful is the structured metadata you attach to it.
Return an errorCategory — transient (timeouts, service unavailability), validation (invalid input), business (policy violations), or permission — plus an isRetryable boolean and a human-readable description. A transient timeout is retryable; a business-rule violation gets retriable: false and a customer-friendly explanation so the agent communicates appropriately instead of looping. Inside a subagent, recover from transient failures locally and propagate to the coordinator only what you couldn't resolve — along with partial results and what was attempted.
Key Behaviors
isErroris the MCP flag that marks a tool result as a failure — the agent reads it to choose a recovery path- Categorize every error:
transient·validation·business·permission— each implies a different recovery decision isRetryable(orretriable: false) tells the agent whether retrying is worthwhile — structured metadata prevents wasted retry attempts- Business-rule violations carry
retriable: falseplus a customer-friendly explanation, not a raw error - Recover transient failures locally in the subagent; propagate to the coordinator only unresolved errors, with partial results and what was attempted
- Distinguish an access failure (needs a retry decision) from a valid empty result (a successful query with no matches) — empty is not an error
How It Works
Distractor Traps (common wrong answers)
- Returning a uniform generic "Operation failed" — prevents the agent from making appropriate recovery decisions
- "Generic search unavailable status" returned only after retries are exhausted — hides the failure type and any partial results
- "Return an empty result set marked as successful" when the query actually failed — suppresses the error and risks incomplete output
- "Terminate the entire workflow on a single failure" instead of recovering locally and propagating selectively
Recommended Material
is_error flag and instructive messagesDistribute tools appropriately across agents and configure tool choice
Give one agent 18 tools instead of the 4–5 it actually needs and its tool selection gets unreliable — every extra tool adds decision complexity. Worse, an agent handed tools outside its specialization tends to misuse them (a synthesis agent that can web-search will). The principle is scoped tool access: give each subagent only the tools its role needs, plus a few constrained cross-role tools for specific high-frequency needs.
When the synthesis agent constantly needs simple fact-checks, give it a narrow verify_fact tool and keep complex investigations routed through the coordinator — rather than the full web-search toolset. You also control how tools get called with tool_choice: "auto" lets Claude decide, "any" guarantees it calls some tool rather than returning conversational text, and {"type": "tool", "name": "..."} forces one specific tool first (e.g. extract_metadata before any enrichment tool), with later steps handled in follow-up turns.
Key Behaviors
- Keep each agent near 4–5 relevant tools; ~18 tools degrades selection reliability by increasing decision complexity
- Restrict each subagent's tool set to its role to prevent cross-specialization misuse (a synthesis agent attempting web searches)
- Replace a generic tool with a constrained alternative (
fetch_url→load_documentthat validates document URLs) - Provide scoped cross-role tools for high-frequency needs (
verify_factfor the synthesis agent); route complex cases through the coordinator tool_choice:"auto"= model decides ·"any"= must call a tool (no plain text) ·{"type":"tool","name":"..."}= forces one named tool first
How It Works
Distractor Traps (common wrong answers)
- "Give the synthesis agent access to all web search tools" so it can handle any verification directly — over-provisions and violates separation of concerns
- "Accumulate all verification needs and return them as a batch" at the end of a pass — creates blocking dependencies between steps
- "Proactively cache extra context" anticipating future needs — speculative caching can't reliably predict what will be verified
- Giving an agent tools outside its specialization, where it tends to misuse them
Recommended Material
tool_choice) · auto · any · tool · noneIntegrate MCP servers into Claude Code and agent workflows
An MCP server is an external process that hands Claude extra tools; where you configure it decides who gets it. Put shared team tooling in a project-level .mcp.json (committed to the repo) and personal or experimental servers in your user-level ~/.claude.json. To keep secrets out of git, reference credentials with environment-variable expansion — ${GITHUB_TOKEN} in .mcp.json rather than the literal token.
At connection time Claude discovers the tools from every configured server and makes them all available at once, so write detailed MCP tool descriptions — otherwise the agent quietly prefers a built-in tool like Grep over a more capable MCP tool. Prefer an existing community server for standard integrations (e.g. Jira) and reserve custom servers for team-specific workflows. Finally, expose content catalogs (issue summaries, documentation hierarchies, database schemas) as MCP resources so the agent sees what data exists without burning exploratory tool calls.
Key Behaviors
- Project-level
.mcp.json= shared team tooling (committed); user-level~/.claude.json= personal/experimental servers - Use environment-variable expansion (
${GITHUB_TOKEN}) in.mcp.jsonso auth tokens are never committed - Tools from all configured servers are discovered at connection time and available to the agent simultaneously
- Write detailed MCP tool descriptions, or the agent prefers built-in tools (like
Grep) over more capable MCP tools - Expose content catalogs as MCP resources (issue summaries, doc hierarchies, DB schemas) to cut exploratory tool calls
How It Works
Distractor Traps (common wrong answers)
- Leaving thin MCP descriptions so the agent keeps "preferring built-in tools (like Grep) over more capable MCP tools"
- Committing literal credentials instead of
${...}environment-variable expansion in.mcp.json - Building a custom MCP server for a standard integration (e.g. Jira) when a community server already exists
- Putting shared team servers in user-level
~/.claude.json(not shared) instead of project-level.mcp.json
Faded Example (shared MCP server — project-scoped .mcp.json)
// Shared team tooling — committed to the repo so everyone gets it.
// File: .mcp.json (project scope; ~/.claude.json would be user/personal scope)
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
// Reference the secret by name — never commit the literal token.
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}
Recommended Material
Select and apply built-in tools (Read, Write, Edit, Bash, Grep, Glob) effectively
Each built-in tool answers one question, and picking the right one keeps the agent fast and reliable. Grep searches inside files for content — function names, error messages, import statements; Glob matches file paths by name or extension pattern (**/*.test.tsx). Read and Write do whole-file operations, while Edit makes a targeted change by matching unique anchor text.
When that anchor text isn't unique, Edit fails — the reliable fallback is to Read the full file and Write it back. The expert move is to build understanding incrementally rather than reading everything upfront: start with Grep to find entry points, then Read to follow imports and trace flows. To trace a function used across wrapper modules, first identify all the exported names, then Grep each name across the codebase.
Key Behaviors
Grep= search file contents (callers of a function, error messages, import statements)Glob= match file paths by naming pattern (**/*.test.tsx)Read/Write= full-file operations;Edit= targeted change via unique text match- On a non-unique
Editmatch, fall back toRead+Writefor a reliable modification - Explore incrementally:
Grepfor entry points →Readto follow imports and trace flows; don't read all files upfront - Trace usage across wrapper modules by listing all exported names first, then
Grep-ing each name
How It Works
Distractor Traps (common wrong answers)
- Retrying
Editon non-unique anchor text instead of falling back toRead+Write - Using
Glob(path patterns) to search file contents, orGrep(content) to find files by name - Reading all files upfront instead of
Grep-to-entry-point, thenRead-to-trace incremental exploration
Recommended Material
Read, Write, Edit, Bash, Grep, Glob behaviorsFurther Reading — Claude Docs
Official Anthropic documentation for the concepts in this domain.
is_error)
Further Reading — Anthropic Academy on Skilljar
Optional self-paced courses.
Further Viewing — Peace Of Code (YouTube)
Watch if a topic is still unclear.
CCA Full Course — Peace Of Code