Skip to main content
Every capability Claude can invoke is a tool. Tools live in src/tools/<ToolName>/ as self-contained modules and are registered in src/tools.ts. The Query Engine discovers them during LLM tool-call loops and executes them when the model requests them.

Tool definition pattern

All tools are created through the buildTool() factory (src/Tool.ts). This function fills in safe defaults for optional methods so every Tool object always has a complete interface.
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}
Defaults applied by buildTool():
MethodDefaultRationale
isEnabled() => trueTools are enabled by default
isConcurrencySafe() => falseFail-closed — assume not safe to run in parallel
isReadOnly() => falseFail-closed — assume the tool writes
isDestructive() => falseNon-destructive by default
checkPermissionsallowDefer to the general permission system
toAutoClassifierInput''Skip ML classifier unless overridden
userFacingNamenameFalls back to the tool’s registered name

Full tool definition shape

export const GlobTool = buildTool({
  name: 'Glob',
  searchHint: 'find files by name pattern or wildcard',
  inputSchema: z.strictObject({
    pattern: z.string().describe('The glob pattern to match files against'),
    path: z.string().optional().describe(
      'The directory to search in. Defaults to the current working directory.'
    ),
  }),

  async call(args, context, canUseTool, parentMessage, onProgress) {
    // Execute and return { data: result }
  },

  async checkPermissions(input, context) {
    return checkReadPermissionForTool(input.path ?? getCwd(), context)
  },

  isConcurrencySafe(_input) { return true },  // read-only: safe to parallelize
  isReadOnly(_input) { return true },

  prompt(options) {
    // Returns a string injected into the system prompt
    return DESCRIPTION
  },

  renderToolUseMessage(input, options) {
    return <GlobToolUseMessage input={input} />
  },

  renderToolResultMessage(content, progressMessages, options) {
    return <GlobToolResultMessage content={content} />
  },
})

Directory structure per tool

Each tool is a self-contained module directory:
src/tools/GlobTool/
├── GlobTool.ts
├── UI.tsx
├── prompt.ts
└── utils.ts
FilePurpose
GlobTool.tsMain implementation — schema, call(), permission checks, concurrency flags
UI.tsxTerminal rendering for invocation display and result display
prompt.tsSystem prompt contribution — the description Claude sees in its context
utils.tsTool-specific helper functions

Tool properties

Every tool declares its parameters with a Zod schema (using the zod/v4 API from zod@^3.24.0). The schema is used for:
  • Runtime validation of LLM-provided arguments
  • JSON Schema generation for the Anthropic API tool spec
  • TypeScript inference for the call() function’s args parameter
inputSchema: z.strictObject({
  pattern: z.string().describe('The glob pattern to match files against'),
  path: z.string().optional(),
})
z.strictObject() rejects unknown keys, preventing the LLM from injecting unexpected parameters.
Every tool invocation passes through the permission system (src/hooks/toolPermission/). Tools implement checkPermissions() which returns a PermissionResult:
async checkPermissions(input, context): Promise<PermissionResult> {
  return checkReadPermissionForTool(input.path ?? getCwd(), context)
}
Permission modes:
ModeBehavior
defaultPrompt the user for each potentially destructive operation
planShow the full plan, ask once before execution
bypassPermissionsAuto-approve everything (use with caution)
autoML-based classifier decides
Permission rules use wildcard patterns:
Bash(git *)        # Allow all git commands
FileEdit(/src/*)   # Allow edits to anything under src/
FileRead(*)        # Allow reading any file
The call() function is the tool’s implementation. It receives validated args, a ToolUseContext (with access to AppState, abort signal, file cache, etc.), the permission-check function, the parent message, and an onProgress callback for streaming progress events.
async call(
  args: z.infer<typeof inputSchema>,
  context: ToolUseContext,
  canUseTool: CanUseToolFn,
  parentMessage: AssistantMessage,
  onProgress: (data: ToolProgressData) => void,
): Promise<{ data: Output; newMessages?: Message[] }>
Each tool provides React components for terminal rendering. These are called by the REPL to display tool invocations and results in the message stream:
  • renderToolUseMessage(input, options) — rendered as soon as the tool call starts streaming (input may be partial)
  • renderToolResultMessage(content, progressMessages, options) — rendered when execution completes
  • renderToolUseProgressMessage(...) — optional live progress UI while the tool runs
  • renderGroupedToolUse(...) — optional grouped rendering when multiple instances run in parallel
Tools declare whether they can safely run in parallel with other tools:
isConcurrencySafe(_input) { return true }   // read-only: safe
isConcurrencySafe(_input) { return false }  // writes: serialize
The Query Engine uses this flag to decide whether to await tool results sequentially or fire them concurrently during multi-tool LLM responses.

Tool catalog

ToolDescriptionRead-only
FileReadToolRead file contents — text, images, PDFs, notebooks. Supports line rangesYes
FileWriteToolCreate or overwrite filesNo
FileEditToolPartial file modification via string replacementNo
GlobToolFind files matching glob patterns (e.g. **/*.ts)Yes
GrepToolContent search using ripgrep (regex-capable)Yes
NotebookEditToolEdit Jupyter notebook cellsNo
TodoWriteToolWrite to a structured todo/task fileNo

Registration and presets

Tools are registered in src/tools.ts and grouped into presets for different contexts:
  • Full toolset — all tools available during normal development sessions
  • Read-only preset — only read-only tools, used for code review contexts
  • MCP-filtered — dynamic subset based on connected MCP server capabilities
The Query Engine receives a Tools array at construction time and resolves tool calls against it using toolMatchesName().

See also

Build docs developers (and LLMs) love