Tool descriptions are THE selection mechanism — not metadata. Misrouting fix order: expand descriptions first, never few-shot examples, a routing classifier, or consolidation as step one.
Five elements of a production description: purpose · inputs w/formats · example queries · edge cases/limits · boundaries vs similar tools.
Messages API tool name regex: ^[a-zA-Z0-9_-]{1,64}$. Required fields: name, description, input_schema.
Build workflow tools, not API wrappers: schedule_event not list_events+create_event. Namespace by service (asana_search) or resource (asana_projects_search).
Claude Code truncates tool descriptions/server instructions at 2KB each. Tool search (default on) defers MCP tool defs until needed.
2.2 — Structured error responses
Category
Retryable?
Recovery
Transient (timeout, rate limit)
Yes
Retry after delay
Validation (bad input)
Yes
Fix input, retry
Business (policy violation)
No
Escalate / alt workflow
Permission (access denied)
No
Escalate / different credentials
MCP's two error mechanisms: protocol errors (JSON-RPC: unknown tool, bad args) vs tool execution errors (isError: true inside a successful result).
Messages API equivalent: tool_result.is_error; MCP connector: mcp_tool_result.is_error.
structuredContent carries structured JSON output; if outputSchema is declared, server MUST conform, client SHOULD validate.
Access failure (isError:true, couldn't reach data) ≠ valid empty result (isError:false, resultCount:0, reached data + found nothing). Confusing these = wasted retries. This is the #1 tested distinction in this task.
Claude auto-retries a malformed tool call 2-3× with corrections — different from your tool's isRetryable flag, which governs agent-level retry after execution failure.
Multi-agent propagation: local recovery for transient failures first; propagate only unresolvable errors + partial results + what was attempted. Never silently suppress (empty-as-success) or kill the whole workflow on one failure.
2.3 — Tool distribution & tool_choice
4-5 tools per agent, scoped to its role. Bloated/overlapping tool sets are a named Anthropic failure mode (ambiguous decision points + wasted context).
Scoped cross-role tool: give a high-frequency simple capability (e.g. verify_fact) directly to the agent; route complex cases through the coordinator.
Replace generic tools with constrained ones: fetch_url → load_document (validates document URLs only) = least privilege.
tool_choice.type
Behaviour
Default when
auto
Model decides tool vs text
tools provided
any
Must call some tool
—
tool
Must call the named tool: {"type":"tool","name":"…"}
—
none
May not call any tool
no tools provided
Forced (any/tool) prefills the assistant message — no text commentary before tool_use, even if asked.
Manual extended thinking works only with auto/none; any/toolerror out.
disable_parallel_tool_use lives insidetool_choice, not top-level. With auto: at most one call. With any/tool: exactly one call.
Use community servers first (Jira, GitHub, Slack, Notion); build custom only for team-specific workflows or proprietary systems.
Sparse MCP descriptions lose to detailed built-in descriptions (agent prefers Grep over an under-described MCP search tool) — enhance the description, don't rename to mimic a built-in.
2.5 — Built-in tools
Tool
Searches / does
Grep
File contents (patterns, calls, imports)
Glob
File paths by naming pattern
Read
Full file load
Write
Full file output
Edit
Targeted change via unique old_string match
Bash
Shell commands
WebFetch
External URL content (domain: permission rules)
Agent (was Task)
Spawns subagents — renamed in Claude Code v2.1.63
Edit fails on a non-unique match (safety, not a bug). Recovery order: widen old_string with context → replace_all: true → Read + Write is the last resort, not the default.
Incremental exploration: Grep → Read → Grep again (wrappers/barrels) → Read only what's justified. Never read all files upfront.
Deprecation-tracing pattern: Grep (find callers) → Glob (find sibling test files) → Grep again (catch wrapper/indirect consumers). Not Glob first.
SDK tools option scopes built-in availability: tools:["Read","Grep"] keeps only those; tools:[] removes all built-ins (MCP tools unaffected).
--allowedTools skips prompts; --tools restricts availability. --disallowedTools: bare name removes the tool entirely; scoped rule (Bash(rm *)) leaves it available, denies only matches.
Bash prefix matching: Bash(git diff *) — the trailing space matters (without it, Bash(git diff*) also matches git diff-index).
WebFetch(domain:*.example.com) matches subdomains, not the apex domain.