15 KiB
3. Tool System
How 42 built-in tools are defined, validated, orchestrated, and rendered.
Overview
Every capability Claude Code has — reading files, running bash, editing code, searching the web — is a Tool. Tools are the bridge between the model's intentions and the real world.
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'primaryBorderColor': '#28a745', 'lineColor': '#28a745', 'secondaryColor': '#16213e', 'tertiaryColor': '#0f3460'}}}%%
graph TB
subgraph Interface["Tool Interface — Tool.ts"]
direction LR
IS["inputSchema<br/>Zod validation"]
CP["checkPermissions"]
CALL["call — execute"]
PROMPT["prompt — model instructions"]
RENDER["render — terminal UI"]
end
IS --> CP --> CALL --> PROMPT --> RENDER
subgraph FileOps["File Operations"]
FR["FileRead"]
FW["FileWrite"]
FE["FileEdit"]
GL["Glob"]
GR["Grep"]
NE["NotebookEdit"]
end
subgraph Exec["Execution"]
BA["Bash"]
PS["PowerShell"]
end
subgraph Web["Web"]
WF["WebFetch"]
WS["WebSearch"]
end
subgraph AgentTools["Agent and Task"]
AG["Agent — spawn sub-agent"]
TC["TaskCreate"]
TG["TaskGet"]
TU["TaskUpdate"]
TL["TaskList"]
TS["TaskStop"]
SM["SendMessage"]
end
subgraph Meta["Meta Tools"]
AQ["AskUserQuestion"]
SK["SkillTool"]
TW["TodoWrite"]
EP["EnterPlanMode"]
XP["ExitPlanMode"]
TSR["ToolSearch"]
end
subgraph Dynamic["Dynamic — Runtime Loaded"]
MCP_T["MCP Tools<br/>from external servers"]
LSP_T["LSP Tool<br/>language server queries"]
end
subgraph Orchestration["Orchestration Layer"]
RUN["toolOrchestration.ts<br/>runTools — parallel dispatch"]
STE["StreamingToolExecutor<br/>execute as blocks stream in"]
TEX["toolExecution.ts — 60KB<br/>single tool lifecycle"]
THK["toolHooks.ts<br/>Pre/Post hook dispatch"]
end
Interface --> FileOps
Interface --> Exec
Interface --> Web
Interface --> AgentTools
Interface --> Meta
Interface --> Dynamic
FileOps --> Orchestration
Exec --> Orchestration
Web --> Orchestration
AgentTools --> Orchestration
Meta --> Orchestration
Dynamic --> Orchestration
RUN --> STE
RUN --> TEX
TEX --> THK
The Tool Interface — Tool.ts (793 lines)
Every tool implements the Tool<Input, Output, Progress> type. Here are the key methods:
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
flowchart LR
subgraph Definition["Tool Definition"]
NAME["name: string"]
SCHEMA["inputSchema: Zod"]
ALIASES["aliases?: string array"]
HINT["searchHint?: string"]
end
subgraph Lifecycle["Lifecycle Methods"]
VAL["validateInput<br/>pre-execution check"]
PERM["checkPermissions<br/>allow / deny / prompt"]
CALL["call<br/>execute the tool"]
DESC["description<br/>model-facing summary"]
end
subgraph Rendering["Rendering Methods"]
RUM["renderToolUseMessage<br/>show input in terminal"]
RRM["renderToolResultMessage<br/>show output in terminal"]
RPM["renderToolUseProgressMessage<br/>spinner / progress bar"]
GRP["renderGroupedToolUse<br/>parallel display"]
end
subgraph Metadata["Metadata Methods"]
RO["isReadOnly<br/>does it write?"]
CS["isConcurrencySafe<br/>parallel safe?"]
EN["isEnabled<br/>available now?"]
DS["isDestructive<br/>irreversible?"]
AC["toAutoClassifierInput<br/>safety classifier text"]
end
Definition --> Lifecycle --> Rendering
Definition --> Metadata
The buildTool Factory
All tools go through buildTool() which provides safe defaults:
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: () => false, // Assume not safe
isReadOnly: () => false, // Assume writes
isDestructive: () => false,
checkPermissions: (input) => // Defer to general system
Promise.resolve({ behavior: 'allow', updatedInput: input }),
toAutoClassifierInput: () => '', // Skip classifier
userFacingName: () => '',
}
export function buildTool(def) {
return { ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def }
}
This "fail-closed" design means a tool that forgets to implement isConcurrencySafe defaults to false (not safe for parallel execution).
The 42 Built-in Tools
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
graph TB
subgraph FileOps["File Operations — Read + Write + Search"]
FR["FileRead<br/>Read files, images,<br/>PDFs, notebooks"]
FW["FileWrite<br/>Create or overwrite<br/>entire files"]
FE["FileEdit<br/>Partial string<br/>replacement edits"]
GL["Glob<br/>File pattern<br/>matching search"]
GR["Grep<br/>ripgrep content<br/>search"]
NE["NotebookEdit<br/>Jupyter notebook<br/>cell editing"]
end
subgraph Exec["Execution — Run Commands"]
BA["Bash<br/>Shell command<br/>execution"]
PS["PowerShell<br/>Windows shell<br/>execution"]
REPL["REPL<br/>Persistent JS/TS<br/>runtime context"]
end
subgraph Web["Web — Fetch and Search"]
WF["WebFetch<br/>HTTP GET to URLs<br/>HTML to markdown"]
WS["WebSearch<br/>Web search via<br/>Brave or similar"]
end
subgraph AgentTask["Agent and Task Management"]
AG["Agent<br/>Spawn sub-agent<br/>with forked context"]
TC["TaskCreate<br/>Background task"]
TG["TaskGet<br/>Check task status"]
TU["TaskUpdate<br/>Update task state"]
TL["TaskList<br/>List all tasks"]
TS["TaskStop<br/>Terminate task"]
SM["SendMessage<br/>Inter-agent<br/>messaging"]
TmC["TeamCreate<br/>Create agent team"]
TmD["TeamDelete<br/>Remove agent team"]
end
subgraph Meta["Meta Tools — Control Claude's Behavior"]
AQ["AskUserQuestion<br/>Interactive prompt"]
SK["SkillTool<br/>Execute skills"]
TW["TodoWrite<br/>Manage task lists"]
EP["EnterPlanMode<br/>Switch to read-only"]
XP["ExitPlanMode<br/>Resume full access"]
TSR["ToolSearch<br/>Find deferred tools"]
BF["Brief<br/>Toggle concise mode"]
SL["Sleep<br/>Idle wait for<br/>proactive mode"]
SO["SyntheticOutput<br/>Structured JSON<br/>output"]
end
subgraph Dynamic["Dynamic — Loaded at Runtime"]
MCP["MCP Tools<br/>From external<br/>MCP servers"]
LSP["LSP Tool<br/>Language server<br/>queries"]
end
subgraph Special["Special Purpose"]
EW["EnterWorktree<br/>Git worktree<br/>isolation"]
XW["ExitWorktree<br/>Leave worktree"]
RT["RemoteTrigger<br/>Remote execution"]
SC["ScheduleCron<br/>Timed triggers"]
CF["Config<br/>Settings management"]
end
Tool Orchestration — Parallel Execution
When the model returns multiple tool_use blocks, Claude Code can execute them in parallel:
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
sequenceDiagram
participant Q as query.ts
participant O as toolOrchestration.ts
participant STE as StreamingToolExecutor
participant T1 as Tool 1 — FileRead
participant T2 as Tool 2 — Grep
participant T3 as Tool 3 — Bash
participant P as Permission System
Q->>O: runTools(3 tool_use blocks)
activate O
Note over O: Check concurrency safety
O->>STE: FileRead — isConcurrencySafe = true
O->>STE: Grep — isConcurrencySafe = true
O->>STE: Bash — isConcurrencySafe = false
par Parallel Execution
STE->>P: checkPermissions(FileRead)
P-->>STE: allow
STE->>T1: call(input)
T1-->>STE: result
STE->>P: checkPermissions(Grep)
P-->>STE: allow
STE->>T2: call(input)
T2-->>STE: result
end
Note over STE: Wait for parallel tools
STE->>P: checkPermissions(Bash)
P-->>STE: prompt user
STE->>T3: call(input)
T3-->>STE: result
O-->>Q: yield all tool_result messages
deactivate O
Key files in the orchestration layer:
toolOrchestration.ts—runTools(): dispatches tools, handles parallel vs. sequentialStreamingToolExecutor— Starts permission checks while model is still streamingtoolExecution.ts(60KB) — Single tool lifecycle: validate → permissions → execute → hookstoolHooks.ts— Dispatches PreToolUse and PostToolUse hooks
Single Tool Lifecycle
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
flowchart TD
BLOCK["tool_use block arrives<br/>from model stream"]:::start
PARSE["Parse + validate input<br/>via Zod inputSchema"]:::step
VAL{"validateInput?"}:::check
DENY_VAL["Return error to model<br/>with validation message"]:::deny
PRE_HOOK["Run PreToolUse hooks<br/>user-defined scripts"]:::hook
HOOK_R{"Hook result?"}:::check
PERM["Check permissions<br/>deny → allow → tool → hooks → classifier → dialog"]:::step
PERM_R{"Permission?"}:::check
EXEC["tool.call(input, context)<br/>execute the operation"]:::step
RESULT["Map output to tool_result<br/>via mapToolResultToToolResultBlockParam"]:::step
SIZE{"Result exceeds<br/>maxResultSizeChars?"}:::check
PERSIST["Persist to disk<br/>return file path + preview"]:::step
POST_HOOK["Run PostToolUse hooks"]:::hook
RENDER["Render in terminal<br/>renderToolResultMessage"]:::step
YIELD["Yield tool_result<br/>to query loop"]:::done
DENY_PERM["Return permission_denied<br/>error to model"]:::deny
BLOCK --> PARSE --> VAL
VAL -->|"pass"| PRE_HOOK
VAL -->|"fail"| DENY_VAL
PRE_HOOK --> HOOK_R
HOOK_R -->|"approve"| PERM
HOOK_R -->|"deny"| DENY_PERM
HOOK_R -->|"modify input"| PERM
PERM --> PERM_R
PERM_R -->|"allow"| EXEC
PERM_R -->|"deny"| DENY_PERM
EXEC --> RESULT --> SIZE
SIZE -->|"within limit"| POST_HOOK
SIZE -->|"exceeds limit"| PERSIST --> POST_HOOK
POST_HOOK --> RENDER --> YIELD
classDef start fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
classDef step fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
classDef hook fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
classDef deny fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
classDef done fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
ToolSearch — Deferred Tool Loading
With 42+ tools, sending all schemas to the model wastes tokens. ToolSearch defers tools that aren't immediately needed:
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#ffc107', 'primaryBorderColor': '#ffc107'}}}%%
flowchart LR
ALL["42+ Tools"]:::input
SPLIT{"shouldDefer?"}:::check
EAGER["~15 Eager Tools<br/>Always in prompt<br/>FileRead, Bash, Grep..."]:::eager
DEFER["~27 Deferred Tools<br/>Schema not sent initially<br/>TaskCreate, WebSearch..."]:::defer
ALWAYS["alwaysLoad Tools<br/>Forced eager by MCP meta"]:::eager
SEARCH["ToolSearch Tool<br/>Model searches by keyword<br/>using searchHint"]:::tool
FOUND["Tool schema injected<br/>into next request"]:::result
ALL --> SPLIT
SPLIT -->|"no"| EAGER
SPLIT -->|"yes"| DEFER
SPLIT -->|"alwaysLoad"| ALWAYS
DEFER --> SEARCH
SEARCH --> FOUND
classDef input fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
classDef eager fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
classDef defer fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
classDef tool fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
classDef result fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
Dynamic Tools — MCP and LSP
Beyond built-in tools, Claude Code loads tools dynamically at runtime:
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
flowchart TD
subgraph MCP["MCP Tools — Model Context Protocol"]
SRV["External MCP Servers<br/>configured in settings"]
CONN["MCPConnectionManager<br/>stdio / SSE transport"]
DISC["Discover tools<br/>via tools/list"]
WRAP["Wrap as Tool objects<br/>name: mcp__server__tool"]
end
subgraph LSP["LSP Tool — Language Server Protocol"]
LS["Language Server<br/>runtime type info"]
QUERY_LSP["Query definitions,<br/>references, diagnostics"]
end
SRV --> CONN --> DISC --> WRAP
LS --> QUERY_LSP
MERGE["Merged into tool pool<br/>via useMergedTools hook"]:::merge
WRAP --> MERGE
QUERY_LSP --> MERGE
classDef merge fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
MCP tools are prefixed with mcp__<server>__<tool> unless running in SDK no-prefix mode. They go through the same permission system as built-in tools.
Key Design Decisions
1. Self-Contained Modules
Each tool directory (src/tools/<ToolName>/) contains everything:
index.ts— Tool definition viabuildTool()prompt.ts— Model-facing instructions*.test.ts— Tests- Additional helpers as needed
2. Fail-Closed Defaults
buildTool() defaults are conservative:
isConcurrencySafe = false— Won't run in parallel unless explicitly safeisReadOnly = false— Assumed to write unless stated otherwisecheckPermissionsdefaults toallow— But the general permission system still applies
3. Result Size Budgets
Each tool has maxResultSizeChars. Oversized results are persisted to disk and the model gets a truncated preview + file path. This prevents single tool results from consuming the entire context window.
4. Observable Input Backfilling
backfillObservableInput() adds derived fields to tool inputs for SDK consumers and transcripts, without mutating the API-bound input (which would break prompt caching):
// The API sees: { file_path: "src/foo.ts" }
// SDK/transcript sees: { file_path: "src/foo.ts", resolved_path: "/abs/path/src/foo.ts" }
Previous: ← The Agentic Loop · Next: Permission System →