Architecture
此内容尚不支持你的语言。
Claude Code’s tool system is a unified abstraction layer that allows 40+ built-in tools, MCP tools, and custom tools to coexist under a single dispatch mechanism. This chapter covers the Tool interface, registration, and runtime filtering.
The Tool Interface
Section titled “The Tool Interface”Every tool in Claude Code implements the Tool type defined in src/Tool.ts. It is one of the largest type definitions in the codebase — over 50 methods and properties — but can be understood in layers:
Core Methods (Required)
Section titled “Core Methods (Required)”export type Tool< Input extends AnyObject = AnyObject, Output = unknown, P extends ToolProgressData = ToolProgressData,> = { readonly name: string; readonly inputSchema: Input; // Zod schema for validation readonly inputJSONSchema?: ToolInputJSONSchema; // Optional JSON Schema override
call( args: z.infer<Input>, context: ToolUseContext, canUseTool: CanUseToolFn, parentMessage: AssistantMessage, onProgress?: ToolCallProgress<P>, ): Promise<ToolResult<Output>>;
description( input: z.infer<Input>, options: { isNonInteractiveSession: boolean; toolPermissionContext: ToolPermissionContext; tools: Tools; }, ): Promise<string>;
prompt(options: { getToolPermissionContext: () => Promise<ToolPermissionContext>; tools: Tools; agents: AgentDefinition[]; }): Promise<string>;};The three core methods:
call: Executes the tool. Receives validated input, the current context, and a progress callback.description: Returns the tool description sent to the API. Can be dynamic based on input and context.prompt: Returns instructions for the model about how to use this tool.
Behavioral Methods
Section titled “Behavioral Methods”{ isConcurrencySafe(input: z.infer<Input>): boolean; // Can run in parallel? isEnabled(): boolean; // Available in current config? isReadOnly(input: z.infer<Input>): boolean; // Side-effect free? isDestructive?(input: z.infer<Input>): boolean; // Irreversible operation? interruptBehavior?(): 'cancel' | 'block'; // How to handle user interrupts requiresUserInteraction?(): boolean; // Needs human input?}These methods control how the tool participates in concurrent execution, permission checks, and interrupt handling.
Permission Methods
Section titled “Permission Methods”{ checkPermissions( input: z.infer<Input>, context: ToolUseContext, ): Promise<PermissionResult>;
validateInput?( input: z.infer<Input>, context: ToolUseContext, ): Promise<ValidationResult>;
preparePermissionMatcher?( input: z.infer<Input>, ): Promise<(pattern: string) => boolean>;}Permission flow: validateInput → checkPermissions → canUseTool (user prompt if needed).
Rendering Methods
Section titled “Rendering Methods”{ renderToolUseMessage(input: Partial<z.infer<Input>>, options: {...}): React.ReactNode; renderToolResultMessage?(content: Output, progressMessages: [...], options: {...}): React.ReactNode; renderToolUseProgressMessage?(progressMessages: [...], options: {...}): React.ReactNode; renderToolUseRejectedMessage?(input: z.infer<Input>, options: {...}): React.ReactNode; renderToolUseErrorMessage?(result: ToolResultBlockParam['content'], options: {...}): React.ReactNode; renderGroupedToolUse?(toolUses: Array<{...}>, options: {...}): React.ReactNode | null; renderToolUseTag?(input: Partial<z.infer<Input>>): React.ReactNode;}These methods control how the tool appears in the TUI. Each stage of the tool lifecycle has a dedicated renderer.
The buildTool Helper
Section titled “The buildTool Helper”Rather than requiring every tool to implement all 50+ methods, Claude Code provides buildTool() which fills in safe defaults:
const TOOL_DEFAULTS = { isEnabled: () => true, isConcurrencySafe: (_input?: unknown) => false, // assume not safe isReadOnly: (_input?: unknown) => false, // assume writes isDestructive: (_input?: unknown) => false, checkPermissions: (input) => Promise.resolve({ behavior: 'allow', updatedInput: input }), toAutoClassifierInput: (_input?: unknown) => '', userFacingName: (_input?: unknown) => '',};
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> { return { ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def, } as BuiltTool<D>;}Tool Registration
Section titled “Tool Registration”Tools are registered in the app startup flow, assembled into a Tools array (which is readonly Tool[]):
graph TD A[App Startup] --> B[Build built-in tools] A --> C[Connect MCP servers] C --> D[Fetch MCP tool lists] D --> E[Create MCPTool wrappers] B --> F[Merge: built-in + MCP tools] E --> F F --> G[tools: Tools] G --> H[ToolUseContext.options.tools] H --> I[queryLoop]Built-in tools are imported directly from their module files:
// Conceptual assembly (spread across multiple files)import { BashTool } from './tools/BashTool/BashTool.js';import { FileReadTool } from './tools/FileReadTool/FileReadTool.js';import { FileEditTool } from './tools/FileEditTool/FileEditTool.js';// ... 40+ more
const builtinTools: Tools = [ BashTool, FileReadTool, FileEditTool, FileWriteTool, GlobTool, GrepTool, AgentTool, TodoWriteTool, WebSearchTool, WebFetchTool, NotebookEditTool, // ...];MCP tools are dynamically created at runtime when MCP servers connect (see MCP Integration).
Input Validation with Zod
Section titled “Input Validation with Zod”Every tool defines an inputSchema using Zod v4. The schema serves dual purposes:
- Runtime validation: Input from the API is validated before execution
- API schema generation: Converted to JSON Schema for the tool definition sent to the API
// Example: FileReadTool input schemaconst inputSchema = z.object({ file_path: z.string().describe('The absolute path to the file to read'), offset: z.number().optional().describe('Line number to start reading from'), limit: z.number().optional().describe('Number of lines to read'),});Validation happens in runToolUse():
const parsedInput = tool.inputSchema.safeParse(block.input);if (!parsedInput.success) { return createUserMessage({ content: [{ type: 'tool_result', content: `<tool_use_error>Error: Invalid input - ${parsedInput.error}</tool_use_error>`, is_error: true, tool_use_id: block.id, }], });}For MCP tools, the inputJSONSchema field provides the JSON Schema directly (from the MCP server’s tool listing), bypassing Zod conversion.
Tool Lookup
Section titled “Tool Lookup”Tools are looked up by name (or alias) using findToolByName():
export function toolMatchesName( tool: { name: string; aliases?: string[] }, name: string,): boolean { return tool.name === name || (tool.aliases?.includes(name) ?? false);}
export function findToolByName(tools: Tools, name: string): Tool | undefined { return tools.find(t => toolMatchesName(t, name));}Aliases support tool renaming without breaking existing conversations — the old name is kept as an alias for backward compatibility.
Tool Filtering
Section titled “Tool Filtering”Not all tools are available in every context. Filtering happens at several levels:
1. isEnabled() Check
Section titled “1. isEnabled() Check”Each tool can self-disable based on runtime conditions:
// Example: LSPTool is only enabled when LSP servers are connectedisEnabled(): boolean { return getLspClients().length > 0;}2. Context-Based Filtering
Section titled “2. Context-Based Filtering”The tools array is filtered before being passed to the API:
// Tools are filtered based on the query contextconst availableTools = tools.filter(tool => { if (!tool.isEnabled()) return false; // Additional context-specific filters return true;});3. Tool Deferral (ToolSearch)
Section titled “3. Tool Deferral (ToolSearch)”When ToolSearch is enabled, tools with shouldDefer: true are sent to the API with defer_loading: true. The model must use the ToolSearch tool to discover them before they can be called:
// Tool definition{ name: 'NotebookEdit', shouldDefer: true, // deferred by default searchHint: 'jupyter', // keyword for ToolSearch matching}
// Tools with alwaysLoad override deferral{ name: 'mcp__server__critical_tool', alwaysLoad: true, // always sent in full}4. Plan Mode Filtering
Section titled “4. Plan Mode Filtering”In plan mode, write tools are filtered out, leaving only read-only tools available.
The ToolResult Type
Section titled “The ToolResult Type”Tool execution returns a ToolResult:
export type ToolResult<T> = { data: T; // The tool's output newMessages?: Message[]; // Additional messages to inject contextModifier?: (context: ToolUseContext) => ToolUseContext; // Modify context for next turn mcpMeta?: { // MCP protocol metadata _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown>; };};The contextModifier is powerful — it allows a tool to modify the execution context for subsequent tools. For example, the EnterPlanModeTool uses it to switch permission modes.
Result Size Management
Section titled “Result Size Management”Each tool specifies maxResultSizeChars to control when results are persisted to disk:
{ name: 'Bash', maxResultSizeChars: 30_000, // persist results > 30K chars}
{ name: 'Read', maxResultSizeChars: Infinity, // never persist (self-bounded)}When exceeded, the result is saved to a temp file and the model receives a preview with:
[Full output saved to /tmp/.../result.txt]<preview>first 1000 chars...</preview>ToolUseContext: The Execution Environment
Section titled “ToolUseContext: The Execution Environment”Every tool receives a ToolUseContext — a rich object containing the entire execution environment:
export type ToolUseContext = { options: { commands: Command[]; debug: boolean; mainLoopModel: string; tools: Tools; verbose: boolean; thinkingConfig: ThinkingConfig; mcpClients: MCPServerConnection[]; mcpResources: Record<string, ServerResource[]>; isNonInteractiveSession: boolean; agentDefinitions: AgentDefinitionsResult; }; abortController: AbortController; readFileState: FileStateCache; getAppState(): AppState; setAppState(f: (prev: AppState) => AppState): void; messages: Message[]; // ... 30+ more fields};Key fields:
abortController: For cancellation propagationreadFileState: LRU cache of recently read filesgetAppState/setAppState: Access to the global application statemessages: The current conversation historycontentReplacementState: Tracks which large results have been persisted