5. The Tool System
5.1 The Tool contract — Tool.ts:362
Every capability the model has is a Tool<Input, Output, Progress>. Minimally:
type Tool<Input extends AnyObject, Output> = {
readonly name: string
aliases?: string[]
readonly inputSchema: Input // zod
readonly inputJSONSchema?: ToolInputJSONSchema // for MCP tools
call(
args: z.infer<Input>,
context: ToolUseContext,
canUseTool: CanUseToolFn,
parentMessage: AssistantMessage,
onProgress?: ToolCallProgress,
): Promise<ToolResult<Output>>
description(input, { isNonInteractiveSession, toolPermissionContext, tools }): Promise<string>
prompt(getCtx, tools, agents): Promise<string>
isEnabled(): boolean
isReadOnly(input): boolean
isConcurrencySafe(input): boolean
isDestructive?(input): boolean
interruptBehavior?(): 'cancel' | 'block'
isSearchOrReadCommand?(input): { isSearch; isRead; isList }
shouldDefer?: boolean // requires ToolSearch to load
alwaysLoad?: boolean // opt out of deferral
searchHint?: string // 3-10 words for ToolSearch keyword match
maxResultSizeChars: number // output > this → persisted to disk, preview returned
strict?: boolean // API-level strict schema enforcement
checkPermissions?(input, context): Promise<PermissionResult>
validateInput?(input, context): Promise<ValidationResult>
getPath?(input): string // for wildcard rules like "FileEdit(/src/*)"
preparePermissionMatcher?(ctx): (pattern: string) => boolean
backfillObservableInput?(input: Record<string, unknown>): void
renderToolUseMessage?, renderToolResultMessage?, renderToolUseProgressMessage?,
renderToolUseRejectedMessage?, renderToolUseErrorMessage?,
renderGroupedToolUse?,
...
mapToolResultToToolResultBlockParam?(…)
extractSearchText?(input, output): string // for transcript search index
inputsEquivalent?(a, b): boolean // dedupe identical consecutive calls
mcpInfo?: { serverName; toolName }
}buildTool() at Tool.ts:783 is the factory: it supplies safe defaults (isEnabled: () => true, isConcurrencySafe: () => false, isReadOnly: () => false, checkPermissions: allow-passthrough, etc.).
Two tiny helpers toolMatchesName(tool, name) (Tool.ts:348) and findToolByName(tools, name) (Tool.ts:358) respect aliases.
The ToolUseContext
Threaded through every call(). Includes: options (model, tools, mcpClients, agentDefinitions, thinkingConfig, theme), AppState getter/setter, abortController, readFileState, permission context, file-history / attribution updaters, discoveredSkillNames, loadedNestedMemoryPaths, dynamicSkillDirTriggers, nestedMemoryAttachmentTriggers, setInProgressToolUseIDs, setResponseLength, setSDKStatus. The bag is big because tool execution touches many subsystems.
5.2 Registry and gating — tools.ts
getAllBaseTools() (tools.ts:193) returns the superset:
AgentTool, TaskOutputTool,
BashTool, FileReadTool, FileEditTool, FileWriteTool,
NotebookEditTool, WebFetchTool, TodoWriteTool, WebSearchTool,
ExitPlanModeV2Tool, EnterPlanModeTool,
SkillTool, BriefTool,
GlobTool, GrepTool (omitted when embedded search tools present),
Task{Create,Get,Update,List}Tool (when TODO_V2),
REPLTool, PowerShellTool (ant/platform gated),
SleepTool, CronTools, MonitorTool (PROACTIVE/KAIROS/AGENT_TRIGGERS),
ListMcpResourcesTool, ReadMcpResourceTool,
ToolSearchTool (when deferred tools are enabled)
getTools(permissionContext) (tools.ts:271) applies deny-rules via filterToolsByDenyRules() (tools.ts:262-269) which match tool names or MCP server prefixes against blanket-deny rules.
5.3 Tour of the notable tools
BashTool (tools/BashTool/)
The most complex tool. Its prompt (tools/BashTool/prompt.ts) is huge — see doc 07 for excerpts of the commit / PR / sandbox / background-task guidance it injects.
- Collapsible display classification (
BashTool.tsx:95-159): parses pipelines and compound commands (&&,;,|) and classifies each part as search / read / list. Semantic-neutral commands (echo,printf,true,false,:) don't change the classification, sols dir && echo --- && ls dir2is still a read. - Command sets:
BASH_SEARCH_COMMANDS= find, grep, rg, ag, ack, locate, which, whereis.BASH_READ_COMMANDS= cat, head, tail, less, more, wc, stat, file, strings, jq, awk, cut, sort, uniq, tr.BASH_LIST_COMMANDS= ls, tree, du.BASH_SILENT_COMMANDS= mv, cp, rm, mkdir, rmdir, chmod, chown, chgrp, touch, ln, cd, export, unset, wait.
- Sandboxing:
SandboxManager(utils/sandbox/sandbox-adapter) decides whether to run under macOS sandbox-exec / Linux bubblewrap / a declared read-write allowlist.shouldUseSandbox()(tools/BashTool/shouldUseSandbox.ts) gates by platform, command semantics, and permission mode. - Security parsing (
utils/bash/ast.ts): parses the command to a bash AST and detects redirects, pipes,$(…), backticks, variable expansion. Drives wildcard-pattern matching for rules likeBash(git *). - Read-only validation (
tools/BashTool/readOnlyValidation.ts): if the permission context requires read-only, the tool refuses any command whose AST indicates mutation. sededit detection (tools/BashTool/sedEditParser.ts): becausesed -i …is a disguised file edit, BashTool refuses it and instructs the model to useFileEditinstead.- Git tracking (
tools/shared/gitOperationTracking.ts): if the command is agit commit, BashTool records attribution intoattributionState. - Auto-background: in assistant mode, a blocking bash exceeding
ASSISTANT_BLOCKING_BUDGET_MS = 15_000moves to a background task (BashTool.tsx:57).
FileReadTool (tools/FileReadTool/)
- Blocks device files that would hang indefinitely (
/dev/zero,/dev/stdin, etc.). - Inline PDF extraction with page limits.
- Nested memory discovery: when reading a file inside a directory that contains a
CLAUDE.md, the tool adds a trigger so thatCLAUDE.mdis attached as a user message next turn. This lets the agent automatically pick up project-local rules. - Skill directory discovery: reading within a directory that has a
.claude/skills/dir triggers adynamicSkillDirTriggersattachment. maxResultSizeChars: Infinity(Tool.ts:466rationale): persisting Read output would create Read→file→Read loops, and Read already self-bounds via line/byte limits.
FileEditTool (tools/FileEditTool/)
- Strict-mode schema (
strict: true). - 1 GiB file-size ceiling.
- Read-before-write invariant:
validateInput()requiresold_stringto be present in the file's current content orreplace_all=true. If the agent edits a file it did not just read, the cached content is trusted only if it matches what's on disk now; otherwise the tool errors out and forces a Read. - Team memory secret check: if the edit touches a file that looks like it could contain secrets and team memory sync is on, the tool warns.
- Clears LSP diagnostics for the edited file; records to fileHistory for
/rewind. - Computes a git diff via
fetchSingleFileGitDiff()for rendering.
FileWriteTool (tools/FileWriteTool/)
Creates files. Refuses to write inside ~/.claude unless an explicit override. Same sandboxing and permission flows as Edit.
GlobTool / GrepTool (tools/GlobTool/, tools/GrepTool/)
GlobTool: glob matching viamicromatch, capped at 100 results.GrepTool: wraps ripgrep with output modes (content,files_with_matches,count), context lines,-i, type filtering,head_limit,offset, multiline. Both excluded whenhasEmbeddedSearchTools()is true (then the agent is told to usefind/grepvia Bash).- Both marked
isSearchOrReadCommandso the UI can collapse them.
WebFetchTool (tools/WebFetchTool/)
- Fetches a URL, runs the user's prompt over the content with a cheap model — returns extracted info rather than raw HTML.
- Preapproved host list (
preapproved.ts) skips permission prompts for common dev hosts (GitHub, Anthropic docs). - Deferred tool (
shouldDefer: true), concurrency-safe, read-only.
WebSearchTool (tools/WebSearchTool/)
Uses the API's native web_search_20250305 tool. allowed_domains / blocked_domains filtering. Max 8 searches per invocation.
AgentTool (tools/AgentTool/)
Described in detail in doc 06. Schema includes description (3-5 words), prompt, subagent_type (optional), model (sonnet/opus/haiku), run_in_background (feature-gated), and under multi-agent mode: name, team_name, mode, isolation ('worktree' | 'remote'), cwd.
SkillTool (tools/SkillTool/)
Invokes skills. Two execution contexts: 'inline' (expand prompt into current context) or 'fork' (run in a forked subagent with its own budget). Discovers bundled skills, plugin skills, user/project skills, and MCP prompts as one merged set.
Todo/Task tools
TodoWriteTool is the v1 surface (enabled when !isTodoV2Enabled()) — takes a whole list of todos. The newer v2 suite (TaskCreate, TaskGet, TaskUpdate, TaskList) allows granular CRUD with hooks. Deferred. TodoWriteTool does a structural nudge when 3+ tasks have been completed with no verification step (TodoWriteTool.ts:76-86).
MCPTool + ListMcpResourcesTool + ReadMcpResourceTool (tools/MCPTool/, tools/List…, tools/Read…)
Generic wrappers for MCP (Model Context Protocol) capabilities. The MCPTool has all of its real methods overridden per-instance in services/mcp/client.ts:29-51. Passthrough permission model — delegates to MCP server rules.
ToolSearchTool (tools/ToolSearchTool/)
Input schema:
query: "select:Read,Edit,Grep" — exact match
query: "notebook jupyter" — keyword search, rank by relevance
query: "+slack send" — require 'slack' substring, rank by remaining terms
max_results: number (default 5)
Output: { matches: string[], total_deferred_tools: number, pending_mcp_servers?: string[] }. The matched tool schemas are returned inside a <functions> block matching the encoding of the system-prompt tool list. Memoized per tool-set via cache-invalidation on change.
EnterPlanMode / ExitPlanModeV2 (tools/EnterPlanModeTool/, tools/ExitPlanModeTool/)
Flip toolPermissionContext.mode = 'plan' and remember prePlanMode for restoration. Plan mode disables writes and bash side effects. Deferred, so the model sees them only after a ToolSearch call or when a plan-related context hints at them.
AskUserQuestionTool (tools/AskUserQuestionTool/)
Interactive tool: opens a UI prompt to the user. Used sparingly — the "Doing tasks" prompt explicitly tells the model to use it only when you're genuinely stuck after investigation, not as a first response to friction (constants/prompts.ts:233).
TodoWriteTool vs TaskCreateTool
Different generations, same purpose. v1 was a full-list replace; v2 is CRUD + hooks (TaskCreated, TaskCompleted).
SendMessageTool (tools/SendMessageTool/)
Inter-agent messaging for teammate mode: "continue a previously spawned agent." Referenced from AgentTool's prompt (tools/AgentTool/prompt.ts:267).
BriefTool (tools/BriefTool/)
Kairos / assistant-daemon specific: sends a brief (summary/status) out through a messaging channel.
REPLTool (tools/REPLTool/)
Interactive Python/JS REPL. Gated to ant / REPL-mode only. Hides the conventional filesystem tools — its own prompt tells the model to call them from scripts.
PowerShellTool (tools/PowerShellTool/)
Windows-platform variant of BashTool. Gated on Windows.
SleepTool, cron tools, MonitorTool (tools/SleepTool/, tools/ScheduleCron*, tools/RemoteTriggerTool)
Scheduling and long-watch tools for the proactive / Kairos / agent-triggers configuration. Let an agent wake later, or monitor a running process.
NotebookEditTool (tools/NotebookEditTool/)
Edits .ipynb cells with proper JSON preservation.
SyntheticOutputTool (tools/SyntheticOutputTool/)
Used only when jsonSchema is requested by an SDK caller — the model emits its structured output by calling this tool. QueryEngine.ts:328-333 registers a registerStructuredOutputEnforcement hook so retries are limited.
5.4 Permissions
Contexts and rules (Tool.ts:123-148)
ToolPermissionContext is DeepImmutable:
mode: 'default' | 'plan' | 'auto' | 'ask' | 'deny'
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
alwaysAllowRules | alwaysDenyRules | alwaysAskRules: ToolPermissionRulesBySource
isBypassPermissionsModeAvailable, isAutoModeAvailable
shouldAvoidPermissionPrompts, awaitAutomatedChecksBeforeDialog
prePlanMode
Rules come from 4 sources (utils/permissions/permissions.ts:109-114): settings (.claude/settings.json), cliArg (--allow-bash), command (session-scoped), session (API-set). Flattened via getAllowRules().
The canUseTool flow (hooks/useCanUseTool.tsx)
canUseTool(tool, input, context, message, toolUseID, forceDecision?):
→ hasPermissionsToUseTool(tool, input, context)
→ step 1a: blanket-deny (tool name or MCP server prefix)
→ step 1b: allow/ask/deny pattern match via preparePermissionMatcher
→ step 2: if mode='bypass' → allow
→ step 3: tool.checkPermissions()
→ step 4: general classification / denial-tracking / ask path
On 'allow' → log + return
On 'deny' → log, record denial, notify
On 'ask' → coordinator pending check? swarm worker?
for Bash with classifier: race the classifier result
fallback: interactive permission dialog (UI) or remote permission bridge
Denial tracking (permissions.ts:95-101) counts repeated denials; past DENIAL_LIMITS, auto-mode escalates to prompts to avoid getting stuck fighting the user.
Bash classifier (tools/BashTool/bashPermissions.ts)
When feature('BASH_CLASSIFIER') is on, a cheap model classifies commands. High-confidence "safe" commands auto-allow in auto-mode; low-confidence or policy-mismatches fall back to prompting. The classifier result is raced against the UI prompt (useCanUseTool.tsx:127-141) — whichever arrives first wins.
5.5 Deferred tools and ToolSearch
Why defer?
With 40+ tools in production, the full tool-schema block balloons the system prompt to a point where keep-in-cache costs matter. Solution: send most tools with defer_loading: true and expose a single meta-tool ToolSearch that resolves schemas on demand.
What counts as deferred (tools/ToolSearchTool/prompt.ts:62-108)
alwaysLoad: true→ never deferred.- MCP tools → deferred by default (but can set
_meta['anthropic/alwaysLoad']to opt out). ToolSearchToolitself → never deferred (that would be circular).AgentTool→ not deferred when fork-subagent is on.BriefTool,SendUserFileTool→ never deferred (communication channels).- Tools with
shouldDefer: true→ deferred.
The model-visible contract
From the ToolSearchTool description (tools/ToolSearchTool/ToolSearchTool.ts:21-44):
Deferred tools appear by name in <system-reminder> messages. Until fetched,
only the name is known — there is no parameter schema, so the tool cannot be
invoked. This tool takes a query, matches it against the deferred tool list,
and returns the matched tools' complete JSONSchema definitions inside a
<functions> block. Once a tool's schema appears in that result, it is callable
exactly like any tool defined at the top of the prompt.
Query forms:
- "select:Read,Edit,Grep" — fetch these exact tools by name
- "notebook jupyter" — keyword search, up to max_results best matches
- "+slack send" — require "slack" in the name, rank by remaining terms
5.6 Rendering and UI integration
A tool contributes several render hooks (all optional):
renderToolUseMessage(input, options)— rendered as the input streams (partial).renderToolUseProgressMessage(progress)— intermediate progress.renderToolResultMessage(result)— the success rendering.renderToolUseRejectedMessage/renderToolUseErrorMessage— failure paths.renderGroupedToolUse(instances)— coalesces parallel calls (e.g. three Reads become one collapsed group).
The UI uses isSearchOrReadCommand to decide whether to collapse a row by default; the user can expand.
5.7 Tool categories at a glance
| Category | Tools | Deferred | Key invariant |
|---|---|---|---|
| Core I/O | Bash, Read, Edit, Write, Glob, Grep | No | Edit requires Read-before-write |
| Filesystem-adjacent | NotebookEdit | Yes | JSON structural preservation |
| Web | WebFetch, WebSearch | Yes | Preapproved host list; domain filtering |
| Tasks | TodoWrite / TaskCreate/Get/Update/List | Yes | v1 vs v2 picked by isTodoV2Enabled |
| Agents | Agent, Skill | No | Skill via SkillTool, Agent via AgentTool |
| MCP | MCPTool, ListMcpResources, ReadMcpResource | Yes (usually) | Passthrough permissions |
| Planning | EnterPlanMode, ExitPlanModeV2 | Yes | Sets toolPermissionContext.mode |
| Meta | ToolSearch | No | Loads other deferred tools |
| Scheduling | Sleep, Cron*, Monitor, RemoteTrigger | Yes | Feature-gated |
| Communication | Brief, SendMessage, SendUserFile | Never deferred | Channel integrations |
| Interactive | AskUserQuestion | Yes | Requires user interaction |