AI Assistant

Tools Overview

The tool system is the backbone of Claude Code's ability to interact with the filesystem, shell, web, and external services. Every action Claude takes (reading files, running commands, searching code) is mediated through a typed tool interface defined in src/Tool.ts, registered via src/tools.ts, and executed through the streaming tool executor.

Tool Base Type

Every tool implements the Tool type, a generic interface parameterized over input schema, output type, and progress data:

type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  name: string
  aliases?: string[]
  searchHint?: string
  inputSchema: Input
  outputSchema?: z.ZodType<unknown>
  call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
  description(input, options): Promise<string>
  prompt(options): Promise<string>
  checkPermissions(input, context): Promise<PermissionResult>
  validateInput?(input, context): Promise<ValidationResult>
  isReadOnly(input): boolean
  isConcurrencySafe(input): boolean
  isEnabled(): boolean
  isDestructive?(input): boolean
  maxResultSizeChars: number
  // ... rendering and UI methods
}

Key Properties

namestring

Unique identifier for the tool. Used in permission rules, analytics, and tool lookup.

aliasesstring[]

Optional alternative names for backward compatibility when a tool is renamed. The tool can be found by any alias.

searchHintstring

A 3-10 word capability phrase used by ToolSearch for keyword matching when the tool is deferred.

inputSchemaZodObject

Zod schema defining the tool's input parameters. Used for validation and API schema generation.

maxResultSizeCharsnumber

Maximum characters for a tool result before it gets persisted to disk. Set to Infinity for tools like Read whose output must never be persisted.

shouldDeferboolean

When true, the tool is sent with defer_loading: true and requires ToolSearch before it can be called.

strictboolean

Enables strict mode for the tool, causing the API to more strictly adhere to the parameter schema.

Core Methods

call()async

Executes the tool. Receives parsed input, the ToolUseContext, a canUseTool callback, the parent message, and an optional progress callback. Returns a ToolResult containing data, optional new messages, and an optional context modifier.

checkPermissions()async

Determines whether the user must be prompted for permission. Called after validateInput() passes. Returns a PermissionDecision with behaviors: allow, deny, ask, or passthrough.

validateInput()async

Pre-execution validation. Checks file existence, path validity, size limits, and security constraints. Returns { result: true } or { result: false, message, errorCode }.

isReadOnly()boolean

Whether the tool modifies state for the given input. Read-only tools may skip certain permission checks.

isConcurrencySafe()boolean

Whether this tool can safely run in parallel with other tools. Used by the streaming executor to decide parallel vs. sequential execution.

isEnabled()boolean

Whether this tool is currently available. Checked at registry assembly time. Tools can be disabled by environment variables, feature flags, or API provider constraints.

Tool Registry

The tool registry in src/tools.ts is the single source of truth for which tools are available.

getAllBaseTools()

Returns the complete exhaustive list of all tools that could be available in the current environment. This respects feature flags and environment variables:

function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    // Glob/Grep conditionally included based on embedded search tools
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    ExitPlanModeV2Tool,
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    // ... many more tools, conditionally included
  ]
}

getTools(permissionContext)

Filters getAllBaseTools() by:

  1. Removing tools blanket-denied by permission rules
  2. Hiding primitive tools when REPL mode is active (they are accessible inside the REPL VM)
  3. Filtering out disabled tools via isEnabled()
  4. Supporting "simple mode" (CLAUDE_CODE_SIMPLE) which restricts to Bash, Read, and Edit

assembleToolPool(permissionContext, mcpTools)

The primary entry point for building the final tool list. Combines built-in tools from getTools() with MCP tools, deduplicates by name (built-in tools take precedence), and sorts for prompt-cache stability:

function assembleToolPool(permissionContext, mcpTools): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
  // Sort each partition, built-ins first for cache stability
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}

Built-in tools are sorted as a contiguous prefix before MCP tools. This layout is critical for prompt-cache stability: interleaving MCP tools with built-ins would invalidate cache keys whenever an MCP tool is added or removed.

Permission Context

The ToolPermissionContext controls what tools can do:

type ToolPermissionContext = DeepImmutable<{
  mode: PermissionMode             // 'default' | 'plan' | 'auto' | 'bypassPermissions'
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
  alwaysAllowRules: ToolPermissionRulesBySource
  alwaysDenyRules: ToolPermissionRulesBySource
  alwaysAskRules: ToolPermissionRulesBySource
  isBypassPermissionsModeAvailable: boolean
  shouldAvoidPermissionPrompts?: boolean
  prePlanMode?: PermissionMode
}>

Permission rules are organized by source (user settings, project settings, session overrides) and matched against tool names and input content. A blanket deny rule (no ruleContent) removes the tool entirely from the model's view.

ToolUseContext

The ToolUseContext is passed to every tool invocation and provides:

  • options: Commands, debug flags, model info, MCP clients, agent definitions
  • abortController: For cancellation support
  • readFileState: File state cache for staleness detection
  • App state access: getAppState() / setAppState() for reading and updating global state
  • messages: The current conversation history
  • File reading limits: Token and size limits for file reads
  • Tool decisions: Map tracking accept/reject decisions for tool uses

Tool Execution Flow

The streaming tool executor processes tool calls through this pipeline:

  1. Input parsing: The tool's inputSchema validates and parses the raw input from the API
  2. Input validation: validateInput() checks tool-specific constraints (file exists, valid path, size limits)
  3. Permission check: checkPermissions() determines if user approval is needed, consulting permission rules, hooks, and the auto-mode classifier
  4. Execution: call() runs the tool with progress reporting via onProgress
  5. Result handling: Output is sized against maxResultSizeChars; oversized results are persisted to disk with a preview returned to the model
  6. Context modification: Non-concurrency-safe tools may return a contextModifier that updates the ToolUseContext for subsequent tools

Tools that are not concurrency-safe (isConcurrencySafe() === false) will block other tool executions and may modify the shared context via contextModifier. The executor serializes these tools.

Tool Deferred Loading

When many tools are available (especially with MCP servers), ToolSearch enables deferred loading. Tools marked with shouldDefer: true are sent to the API with defer_loading: true, so the model sees only their names, not full schemas. The model must call ToolSearch to fetch the full schema before using a deferred tool.

Tools can opt out with alwaysLoad: true to ensure their schema is always visible on turn 1 without a ToolSearch round-trip.

Key Source Files

  • src/Tool.ts: Tool type definitions, ToolUseContext, ToolPermissionContext
  • src/tools.ts: Tool registry: getAllBaseTools(), getTools(), assembleToolPool()
  • src/utils/permissions/permissions.ts: Permission matching and rule evaluation