架构
Claude Code 的 tool 系统是一个统一的抽象层,使 40 余个 builtin tool、MCP tool 以及自定义 tool 能够共存于同一套分发机制之下。本章介绍 Tool interface、注册流程以及运行时过滤。
Tool Interface
Section titled “Tool Interface”Claude Code 中每个 tool 都实现了定义在 src/Tool.ts 中的 Tool 类型。这是代码库中体量最大的类型定义之一——超过 50 个方法和属性——但可以分层理解:
核心方法(必选)
Section titled “核心方法(必选)”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>;};三个核心方法:
call:执行 tool。接收经过验证的输入、当前 context 以及进度回调。description:返回发送给 API 的 tool 描述。可根据输入和 context 动态生成。prompt:返回指导模型如何使用该 tool 的说明。
{ 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?}这些方法控制 tool 如何参与并发执行、permission 检查以及中断处理。
Permission 方法
Section titled “Permission 方法”{ 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 流程:validateInput → checkPermissions → canUseTool(如需则弹出用户提示)。
{ 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;}这些方法控制 tool 在 TUI 中的呈现方式。tool 生命周期的每个阶段都有专属渲染器。
buildTool 辅助函数
Section titled “buildTool 辅助函数”为避免每个 tool 都需实现全部 50 余个方法,Claude Code 提供了 buildTool(),它会填充安全的默认值:
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 注册
Section titled “Tool 注册”Tool 在应用启动流程中注册,组装为 Tools 数组(即 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]
Builtin tool 直接从各自的模块文件导入:
// 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 tool 在 MCP 服务器连接时于运行时动态创建(参见 MCP Integration)。
使用 Zod 进行输入验证
Section titled “使用 Zod 进行输入验证”每个 tool 都使用 Zod v4 定义 inputSchema。该 schema 具有双重用途:
- 运行时验证:来自 API 的输入在执行前进行验证
- API schema 生成:转换为 JSON Schema,作为 tool 定义发送给 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'),});验证发生在 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, }], });}对于 MCP tool,inputJSONSchema 字段直接提供 JSON Schema(来自 MCP 服务器的 tool 列表),绕过 Zod 转换。
Tool 查找
Section titled “Tool 查找”Tool 通过名称(或别名)使用 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));}别名支持 tool 重命名而不破坏已有对话——旧名称作为别名保留,以保证向后兼容。
Tool 过滤
Section titled “Tool 过滤”并非所有 tool 在每种 context 下都可用。过滤在多个层面进行:
1. isEnabled() 检查
Section titled “1. isEnabled() 检查”每个 tool 可根据运行时条件自行禁用:
// Example: LSPTool is only enabled when LSP servers are connectedisEnabled(): boolean { return getLspClients().length > 0;}2. 基于 Context 的过滤
Section titled “2. 基于 Context 的过滤”tools 数组在传递给 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 延迟加载(ToolSearch)
Section titled “3. Tool 延迟加载(ToolSearch)”当 ToolSearch 启用时,带有 shouldDefer: true 的 tool 会以 defer_loading: true 发送给 API。模型必须先使用 ToolSearch tool 发现它们,才能调用:
// 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. 计划模式过滤
Section titled “4. 计划模式过滤”在计划模式下,写入类 tool 会被过滤掉,仅保留只读 tool 可用。
ToolResult 类型
Section titled “ToolResult 类型”Tool 执行返回 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>; };};contextModifier 功能强大——它允许 tool 修改后续 tool 的执行 context。例如,EnterPlanModeTool 利用它来切换 permission 模式。
结果大小管理
Section titled “结果大小管理”每个 tool 通过 maxResultSizeChars 指定何时将结果持久化到磁盘:
{ name: 'Bash', maxResultSizeChars: 30_000, // persist results > 30K chars}
{ name: 'Read', maxResultSizeChars: Infinity, // never persist (self-bounded)}超出限制时,结果保存到临时文件,模型收到包含以下内容的预览:
[Full output saved to /tmp/.../result.txt]<preview>first 1000 chars...</preview>ToolUseContext:执行环境
Section titled “ToolUseContext:执行环境”每个 tool 都接收一个 ToolUseContext——包含整个执行环境的富对象:
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};关键字段:
abortController:用于取消传播readFileState:最近读取文件的 LRU cachegetAppState/setAppState:访问全局应用状态messages:当前对话历史contentReplacementState:追踪哪些大型结果已被持久化