在前面的文章中,我们已经深入了解了 Claude Code 的查询引擎(Query Engine)、状态管理(AppState)和权限系统(Permission System)。现在,让我们把视线投向整个系统中最具"可扩展性"的模块——工具系统(Tool System)。
Claude Code 的底层是一个强大的 Agent 运行时,而它之所以能与外部世界交互——读取文件、执行命令、搜索网络、调用 MCP 服务——全靠一套精心设计的工具协议。截至当前版本,这套系统已经承载了超过 40 个内置工具,并且支持通过 MCP(Model Context Protocol) 动态注入外部工具。
本文将从 Tool.ts 的抽象基类协议出发,遍历 tools.ts 的注册表组装逻辑,拆解 Schema 系统的设计哲学,最终呈现一幅完整的工具系统全景图。
一、Tool.ts:一切工具的元协议
src/Tool.ts 是整个工具系统的"宪法"。它并不直接实现任何具体功能,而是定义了所有工具必须遵守的契约——从输入 Schema 到执行语义,从权限声明到结果渲染。
1.1 Tool 类型的核心结构
在 Tool.ts 中,Tool 被定义为一个 TypeScript 泛型接口(第 340-540 行附近)。它不是一个传统的面向对象抽象类,而是一个结构类型(Structural Typing)的接口,这意味着任何对象只要满足其形状,就可以被视为一个 Tool。
// src/Tool.ts (第 340 行起,精简版)
export type Tool<
Input extends AnyObject = AnyObject,
Output = unknown,
P extends ToolProgressData = ToolProgressData,
> = {
name: string
aliases?: string[]
searchHint?: string
readonly inputSchema: Input
readonly inputJSONSchema?: ToolInputJSONSchema
outputSchema?: z.ZodType<unknown>
call(args: z.infer<Input>, context: ToolUseContext, ...): Promise<ToolResult<Output>>
description(input, options): Promise<string>
prompt(options): Promise<string>
checkPermissions(input, context): Promise<PermissionResult>
isConcurrencySafe(input): boolean
isReadOnly(input): boolean
isDestructive?(input): boolean
isEnabled(): boolean
userFacingName(input): string
mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
renderToolResultMessage?(content, ...): React.ReactNode
// ... 约 30+ 个字段与方法
}这个设计有几个关键决策值得注意:
第一,泛型三元组 <Input, Output, P>。Input 是 Zod Schema 的类型,Output 是工具返回值类型,P 是进度(Progress)数据类型。这种设计让工具的类型安全贯穿定义 → 调用 → 结果渲染的全链路。
第二,call 是唯一的执行入口。不像某些框架把"执行"分散到多个生命周期钩子中,Claude Code 坚持单入口原则——所有副作用都发生在 call() 方法内部。这使得工具的语义非常清晰,也便于权限系统在调用前做统一拦截。
第三,渲染与执行分离。call() 返回的是纯数据(ToolResult<Output>),而 renderToolResultMessage() 负责把数据渲染成 React 节点。这种数据层与表现层分离的架构,让同一个工具可以在 CLI(纯文本)、Web UI(React)和 SDK(JSON)三种场景下复用同一套执行逻辑。
1.2 buildTool:从定义到实例的桥梁
并非所有工具都需要实现全部 30+ 个字段。Claude Code 提供了一个名为 buildTool 的工厂函数(第 783 行),它接受一个部分定义(ToolDef),自动填充安全默认值:
// src/Tool.ts (第 721-793 行,精简版)
export type ToolDef<Input, Output, P> =
Omit<Tool<Input, Output, P>, DefaultableToolKeys> &
Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: (_input?: unknown) => false, // 默认不安全
isReadOnly: (_input?: unknown) => false, // 默认可写
isDestructive: (_input?: unknown) => false, // 默认非破坏性
checkPermissions: (input, _ctx) =>
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_DEFAULTS 中的安全默认策略(Fail-Closed):
isConcurrencySafe默认为false——新工具不会意外地被并发调用。isReadOnly默认为false——新工具默认被视为会修改系统状态,从而触发权限检查。checkPermissions默认放行,但将权限决策推迟到上层的通用权限系统。
这种设计极大地降低了新增工具的边际成本。开发者只需关注 call()、inputSchema 和 prompt() 这三个核心要素,其余字段由框架补齐。
1.3 权限声明的三层模型
每个工具通过三个布尔方法向系统声明自己的"安全属性":
isReadOnly(input): boolean // 是否只读?决定是否需要权限弹窗
isDestructive?(input): boolean // 是否破坏性?用于风险提示(如覆盖文件)
isConcurrencySafe(input): boolean // 是否线程安全?决定能否与其他工具并发以 BashTool 为例(src/tools/BashTool/BashTool.tsx,第 440-460 行):
isConcurrencySafe(input) {
return this.isReadOnly?.(input) ?? false
},
isReadOnly(input) {
const compoundCommandHasCd = commandHasAnyCd(input.command)
const result = checkReadOnlyConstraints(input, compoundCommandHasCd)
return result.behavior === 'allow'
},BashTool 的 isReadOnly 并不是硬编码的,而是动态分析命令字符串——如果检测到 git push、rm 等写操作,则返回 false,触发权限检查;如果是 ls、cat、grep 等读操作,则返回 true,允许静默执行。
二、tools.ts:工具池的组装与过滤
如果说 Tool.ts 是"宪法",那么 src/tools.ts 就是"内阁名单"——它负责把所有工具聚合起来,根据运行时环境过滤,最终形成一个可用的工具池(Tool Pool)。
2.1 getAllBaseTools:40+ 工具的大集合
getAllBaseTools()(src/tools.ts,第 155-230 行)是整个系统的工具来源单一真相源(Single Source of Truth)。它返回一个数组,包含所有可能可用的内置工具:
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
ExitPlanModeV2Tool,
FileReadTool,
FileEditTool,
FileWriteTool,
NotebookEditTool,
WebFetchTool,
TodoWriteTool,
WebSearchTool,
TaskStopTool,
AskUserQuestionTool,
SkillTool,
EnterPlanModeTool,
...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []),
...(process.env.USER_TYPE === 'ant' ? [TungstenTool] : []),
...(isTodoV2Enabled()
? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool]
: []),
...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
getSendMessageTool(),
...(isAgentSwarmsEnabled()
? [getTeamCreateTool(), getTeamDeleteTool()]
: []),
...(feature('AGENT_TRIGGERS')
? [CronCreateTool, CronDeleteTool, CronListTool]
: []),
...(feature('MONITOR_TOOL') ? [MonitorTool] : []),
...(feature('WEB_BROWSER_TOOL') ? [WebBrowserTool] : []),
...(feature('HISTORY_SNIP') ? [SnipTool] : []),
...(feature('WORKFLOW_SCRIPTS') ? [WorkflowTool] : []),
// ... 更多条件编译的工具
ListMcpResourcesTool,
ReadMcpResourceTool,
...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
]
}这段代码揭示了几个重要的工程实践:
环境门控(Feature Flag)。大量工具被包裹在 feature('XXX') 或 process.env.XXX 的条件中。这使得 Anthropic 可以在同一份代码库中同时维护内部版(ant)和公开版,以及逐步灰度新功能。
延迟加载(Lazy Require)。部分工具使用 require() 而非 import 来打破循环依赖。例如 getSendMessageTool()、getTeamCreateTool() 等函数在被调用时才加载对应模块(第 70-85 行)。
动态省略。如果运行环境已经嵌入了 bfs/ugrep 等快速搜索工具(通过 hasEmbeddedSearchTools() 判断),则不再注册独立的 GlobTool 和 GrepTool,避免功能冗余。
2.2 权限过滤:从全量到可用
getAllBaseTools() 只是第一步。实际发送到 LLM 的工具池需要经过多层过滤,getTools() 函数(第 247-290 行)负责这项工作:
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
// Simple mode: 仅 Bash, Read, Edit
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], permissionContext)
}
const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
let allowedTools = filterToolsByDenyRules(tools, permissionContext)
// REPL mode: 隐藏原始工具,只暴露 REPL wrapper
if (isReplModeEnabled()) {
allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
}
const isEnabled = allowedTools.map(_ => _.isEnabled())
return allowedTools.filter((_, i) => isEnabled[i])
}过滤链路如下:
- Deny Rules 过滤:
filterToolsByDenyRules()根据用户配置的拒绝规则(如 "禁止所有 MCP 工具" 或 "禁止 Bash")剔除工具。这一步甚至发生在模型看到工具列表之前——被拒绝的工具对 LLM 完全不可见。 - REPL 模式过滤:如果启用了 REPL 模式,原始工具(Bash/Read/Edit 等)被隐藏,仅暴露
REPLTool。REPL 内部通过 VM 上下文仍可访问这些工具,但 LLM 只能通过 REPL 间接调用。 - isEnabled() 过滤:每个工具可以基于自身状态决定是否可用(如 LSP 工具仅在检测到语言服务器时启用)。
2.3 assembleToolPool:内置工具 + MCP 工具的合并
当用户配置了 MCP 服务器时,工具池还需要合并外部动态工具。assembleToolPool()(第 295-320 行)是这一过程的单一真相源:
export function assembleToolPool(
permissionContext: ToolPermissionContext,
mcpTools: Tools,
): Tools {
const builtInTools = getTools(permissionContext)
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
// 按名称排序,保证 Prompt Cache 的稳定性
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
}这里有一个精妙的细节:排序是为了 Prompt Cache 稳定。Anthropic API 支持系统提示(System Prompt)的缓存复用(cache_control)。如果工具列表的顺序在不同请求间发生变化,缓存键就会失效。因此 assembleToolPool 对内置工具和 MCP 工具分别排序后再合并,确保只要工具集合不变,顺序就恒定。
uniqBy 按 name 去重时,内置工具优先于 MCP 工具——因为内置工具在数组前面。这防止了恶意 MCP 服务器通过同名工具劫持内置功能。
2.4 子代理工具过滤
Claude Code 支持多 Agent 协作(Subagent),但并非所有工具都适合子代理使用。src/constants/tools.ts 定义了几套工具白名单:
// src/constants/tools.ts (第 40-100 行)
export const ALL_AGENT_DISALLOWED_TOOLS = new Set([
TASK_OUTPUT_TOOL_NAME, // 防止递归输出
EXIT_PLAN_MODE_V2_TOOL_NAME,// 计划模式是主线程抽象
ENTER_PLAN_MODE_TOOL_NAME,
ASK_USER_QUESTION_TOOL_NAME,// 子代理不能直接向用户提问
TASK_STOP_TOOL_NAME, // 需要主线程任务状态
])
export const ASYNC_AGENT_ALLOWED_TOOLS = new Set([
FILE_READ_TOOL_NAME, WEB_SEARCH_TOOL_NAME, TODO_WRITE_TOOL_NAME,
GREP_TOOL_NAME, WEB_FETCH_TOOL_NAME, GLOB_TOOL_NAME,
...SHELL_TOOL_NAMES, FILE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME,
NOTEBOOK_EDIT_TOOL_NAME, SKILL_TOOL_NAME,
])
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
AGENT_TOOL_NAME, TASK_STOP_TOOL_NAME,
SEND_MESSAGE_TOOL_NAME, SYNTHETIC_OUTPUT_TOOL_NAME,
])这种分层过滤确保了:
- 异步 Agent 只能使用文件/网络/Shell 等"安全"工具,不能操作计划模式或输出管道。
- Coordinator(协调器)模式 下,主协调器仅保留 Agent 管理和消息发送工具,实际工作由 Worker 代理完成。
- In-Process Teammate 可以额外使用任务管理工具(TaskCreate/Get/Update/List),实现团队级协作。
三、Schema 系统:Zod 与模型决策的桥梁
工具能做什么,是由 Schema 告诉模型的。Claude Code 的 Schema 系统基于 Zod v4,并在此基础上构建了一套与 Anthropic API 无缝对接的转换层。
3.1 Zod Schema 的定义范式
每个工具都通过 inputSchema 定义其输入参数。以 FileReadTool 为例(src/tools/FileReadTool/FileReadTool.ts,第 227-245 行):
const inputSchema = lazySchema(() =>
z.object({
file_path: z.string().describe('The path to the file to read'),
offset: z.number().optional().describe('Line number to start reading from'),
limit: z.number().optional().describe('Max number of lines to read'),
pages: z.string().optional().describe('Page range for PDF files'),
}),
)注意 .describe() 的使用——这些描述字符串直接流入模型看到的工具定义中,是模型决定是否调用该工具、以及如何填写参数的关键依据。
Claude Code 使用了 lazySchema() 工具函数来实现延迟初始化。某些 Schema 的定义依赖于运行时配置(如 isBackgroundTasksDisabled),延迟加载可以避免在模块初始化时就执行这些判断。
3.2 outputSchema 与类型安全
除了输入,FileReadTool 还定义了输出 Schema(第 300-340 行):
const outputSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('text'), file: z.object({ ... }) }),
z.object({ type: z.literal('image'), file: z.object({ ... }) }),
z.object({ type: z.literal('pdf'), file: z.object({ ... }) }),
z.object({ type: z.literal('notebook'), file: z.object({ ... }) }),
])这里使用了 z.discriminatedUnion 来实现标签联合类型(Tagged Union)。FileReadTool 的返回值根据文件类型不同而有完全不同的形状,但通过 type 字段的判别,TypeScript 可以在 switch (data.type) 中自动收窄类型。
3.3 JSON Schema 转换与 MCP 兼容
对于内置工具,Zod Schema 会被自动转换为 JSON Schema 后发送给 Anthropic API。但 MCP 工具有所不同——它们可能直接提供 inputJSONSchema(Tool.ts 第 52 行定义),绕过 Zod 转换:
export type ToolInputJSONSchema = {
[x: string]: unknown
type: 'object'
properties?: { [x: string]: unknown }
}这体现了 Claude Code 对 MCP 协议 的兼容:MCP 服务器使用 JSON Schema 定义工具,Claude Code 可以直接透传,无需再经过 Zod 这一层。
3.4 描述文本(Description)的工程意义
description() 方法返回的文本是模型可见的"工具说明书"。在 FileReadTool 中(第 350-365 行):
async prompt() {
const limits = getDefaultFileReadingLimits()
const maxSizeInstruction = limits.includeMaxSizeInPrompt
? `. Files larger than ${formatFileSize(limits.maxSizeBytes)} will return an error`
: ''
return renderPromptTemplate(
pickLineFormatInstruction(),
maxSizeInstruction,
OFFSET_INSTRUCTION_DEFAULT,
)
}prompt() 返回的内容会被注入到系统提示中,作为该工具的"详细说明书"。它与 description()(单行摘要)形成层级关系:
description():一行摘要,出现在工具列表中,帮助模型快速判断是否需要该工具。prompt():完整说明,包括参数含义、边界条件、使用建议,帮助模型正确填写参数。
这种分层设计的工程意义在于节省 Token:并非所有工具的完整说明都需要塞进每次请求的上下文。Claude Code 通过 ToolSearch 机制(后文详述)将不常用工具的 prompt() 延迟加载,只在模型主动搜索时才提供。
四、工具全景:40+ 工具的分类地图
graph TD
A[Claude Code Tool System
40+ Tools] --> B[文件操作类]
A --> C[Shell 执行类]
A --> D[Agent 协作类]
A --> E[网络与搜索类]
A --> F[MCP 与 LSP 类]
A --> G[交互与管理类]
A --> H[计划与工作区类]
A --> I[其他扩展类]
B --> B1[FileReadTool
读取文本/图片/PDF/Notebook]
B --> B2[FileEditTool
字符串替换编辑]
B --> B3[FileWriteTool
创建/覆盖文件]
B --> B4[GlobTool
文件 glob 匹配]
B --> B5[GrepTool
内容正则搜索]
B --> B6[NotebookEditTool
Jupyter Notebook 编辑]
C --> C1[BashTool
Unix Shell 执行]
C --> C2[PowerShellTool
Windows PowerShell]
C --> C3[REPLTool
交互式解释器]
D --> D1[AgentTool
子代理创建与管理]
D --> D2[SendMessageTool
Agent 间消息]
D --> D3[TaskOutputTool
任务结果输出]
D --> D4[TaskStopTool
停止任务]
D --> D5[TaskCreate/Get/Update/List
任务生命周期]
D --> D6[TeamCreate/DeleteTool
团队管理]
E --> E1[WebSearchTool
网络搜索]
E --> E2[WebFetchTool
URL 内容获取]
E --> E3[WebBrowserTool
浏览器自动化]
F --> F1[ListMcpResourcesTool
列举 MCP 资源]
F --> F2[ReadMcpResourceTool
读取 MCP 资源]
F --> F3[MCPTool 动态注入
MCP 服务器工具]
F --> F4[LSPTool
语言服务器协议]
G --> G1[AskUserQuestionTool
向用户提问]
G --> G2[TodoWriteTool
待办列表管理]
G --> G3[SkillTool
技能脚本调用]
G --> G4[BriefTool
摘要生成]
G --> G5[ConfigTool
配置管理]
H --> H1[EnterPlanModeTool
进入计划模式]
H --> H2[ExitPlanModeV2Tool
退出计划模式]
H --> H3[EnterWorktreeTool
进入工作树]
H --> H4[ExitWorktreeTool
退出工作树]
I --> I1[ToolSearchTool
工具搜索/延迟加载]
I --> I2[WorkflowTool
工作流脚本]
I --> I3[MonitorTool
进程监控]
I --> I4[CronCreate/Delete/List
定时任务]
I --> I5[SnipTool
历史消息裁剪]
I --> I6[TerminalCaptureTool
终端捕获]4.1 文件操作类(6 个)
这是最高频的工具集合,几乎每次对话都会用到:
FileReadTool:支持文本、图片、PDF(含分页读取)、Jupyter Notebook。内置缓存去重(readFileState),同文件未修改时返回file_unchanged避免重复传输。FileEditTool:基于字符串匹配(old_string→new_string)的精确编辑,支持多行块替换。FileWriteTool:创建新文件或完全覆盖已有文件。GlobTool/GrepTool:文件搜索工具。在 Ant 内部构建中,如果嵌入了bfs/ugrep,这两个工具会被省略,由 Shell 别名替代。NotebookEditTool:专门处理.ipynb文件的单元格级编辑。
4.2 Shell 执行类(3 个)
BashTool:最复杂的工具之一(1143 行)。支持前台/后台执行、超时控制、沙箱检测(shouldUseSandbox)、命令语义分析(区分 read/search/list/write)、进度渲染。PowerShellTool:Windows 环境的 Shell 执行,条件编译(仅在 Windows 或显式启用时出现)。REPLTool:交互式解释器包装器,将 Bash/Read/Edit 等工具封装在 VM 上下文中,实现类似 Jupyter 的 REPL 体验。
4.3 Agent 协作类(6+ 个)
AgentTool:创建子代理(Subagent),是整个多 Agent 架构的核心。SendMessageTool:Agent 间通信,支持 Coordinator-Worker 模式的指令传递。TaskOutputTool/TaskStopTool:任务结果输出和强制停止。TaskCreate/Get/Update/List:Todo V2 任务管理系统的 CRUD。TeamCreateTool/TeamDeleteTool:Agent Swarms 的团队生命周期管理。
4.4 网络类(3 个)
WebSearchTool:搜索引擎集成,支持查询和结果摘要。WebFetchTool:直接抓取 URL 内容,支持静态页面。WebBrowserTool:条件编译的浏览器自动化工具,可执行 JavaScript、截图等。
4.5 MCP 类(3+ 个)
ListMcpResourcesTool/ReadMcpResourceTool:枚举和读取 MCP 服务器的 Resource。MCPTool(动态):由 MCP 服务器在连接时动态注入,通过mcpInfo字段记录{serverName, toolName}。McpAuth相关:处理 MCP 服务器的 OAuth/Token 认证流程。
4.6 LSP 类(1 个)
LSPTool:语言服务器协议客户端,条件编译(ENABLE_LSP_TOOL)。支持跳转到定义、查找引用、诊断获取等 IDE 级功能。
4.7 交互与管理类(5 个)
AskUserQuestionTool:当模型需要澄清时,主动向用户提问并等待回答。TodoWriteTool:管理任务列表,在 UI 中渲染为独立的 Todo 面板。SkillTool:调用项目本地的 Skill 脚本(.claude/skills/目录下的自定义工具)。BriefTool:生成对话摘要,用于长会话的上下文压缩。ConfigTool:读取/修改 Claude Code 的配置项(Ant 内部专用)。
4.8 计划与工作区类(4 个)
EnterPlanModeTool/ExitPlanModeV2Tool:计划模式的进入和退出。计划模式下模型先输出完整的执行计划,经用户确认后再逐步执行。EnterWorktreeTool/ExitWorktreeTool:Git Worktree 的切换支持,条件编译。
4.9 其他扩展类(6+ 个)
ToolSearchTool:支持工具的延迟加载(Deferred Loading)。当工具数量超过阈值时,不常用工具的完整 Schema 不会直接发给模型,而是需要通过 ToolSearch 按需检索。WorkflowTool:执行预定义的工作流脚本(条件编译WORKFLOW_SCRIPTS)。MonitorTool:监控长时间运行的进程或日志流(条件编译MONITOR_TOOL)。CronCreate/Delete/List:定时任务调度(条件编译AGENT_TRIGGERS)。SnipTool:裁剪历史消息,控制上下文长度(条件编译HISTORY_SNIP)。TerminalCaptureTool:捕获终端面板输出(条件编译TERMINAL_PANEL)。
五、工具系统的扩展机制
5.1 如何添加一个新工具
基于 buildTool 工厂,新增一个工具的标准步骤如下:
第一步,创建目录和文件。每个工具有独立的目录,如 src/tools/MyTool/MyTool.ts:
import { z } from 'zod/v4'
import { buildTool, type ToolDef } from '../../Tool.js'
const inputSchema = z.object({
param: z.string().describe('Parameter description'),
})
const outputSchema = z.object({
result: z.string(),
})
export const MyTool = buildTool({
name: 'my_tool',
searchHint: 'do something useful',
maxResultSizeChars: 10_000,
inputSchema,
outputSchema,
async description() {
return 'Does something useful'
},
async prompt() {
return 'Detailed prompt for the model...'
},
isReadOnly() {
return true // 或 false
},
isConcurrencySafe() {
return true
},
async call(args, context, canUseTool, parentMessage, onProgress) {
// 执行逻辑
return { data: { result: 'done' } }
},
mapToolResultToToolResultBlockParam(data, toolUseID) {
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: data.result,
}
},
} satisfies ToolDef<typeof inputSchema, z.infer<typeof outputSchema>>)第二步,注册到工具池。在 src/tools.ts 的 getAllBaseTools() 数组中添加新工具。如果需要条件编译,使用 feature() 或环境变量包裹。
第三步,更新权限常量(如果需要)。如果该工具不应该被子代理使用,在 src/constants/tools.ts 的 ALL_AGENT_DISALLOWED_TOOLS 或 ASYNC_AGENT_ALLOWED_TOOLS 中添加工具名。
5.2 工具测试框架
Claude Code 没有独立的"工具测试框架",但每个工具的测试遵循以下模式(从源码中的 __tests__ 目录推断):
- Schema 验证测试:确保 Zod Schema 能正确解析合法输入、拒绝非法输入。
- 权限测试:模拟不同的
ToolPermissionContext,验证checkPermissions()的返回值。 - Mock 调用测试:使用 Jest/Vitest 的
spyOn来模拟ToolUseContext和文件系统操作,测试call()的逻辑。 - Render 保真测试:
transcriptSearch.renderFidelity.test.tsx会检查renderToolResultMessage渲染的文本与extractSearchText()返回的文本是否一致,防止搜索索引与实际显示内容出现偏差。
5.3 工具 Schema 缓存与延迟加载
当工具数量膨胀时(尤其是接入多个 MCP 服务器后),系统提示中的工具定义会消耗大量 Token。Claude Code 通过两种机制缓解这个问题:
系统提示缓存(Prompt Cache)。Anthropic API 支持对系统提示进行缓存复用。assembleToolPool() 中的排序策略正是为了保证缓存键的稳定性——只要工具列表和顺序不变,缓存就命中。
延迟加载(Deferred Loading)。当工具数量超过阈值时,isToolSearchEnabledOptimistic() 返回 true,ToolSearchTool 被注册到工具池中。此时,标记了 shouldDefer: true 的工具不会发送完整的 Schema 给模型,而是只发送名称和摘要。模型如果需要调用该工具,必须先通过 ToolSearchTool 查询获取完整 Schema。这在 MCP 场景下尤为重要——防止 50+ MCP 工具一次性挤爆上下文。
sequenceDiagram
participant LLM as Claude API
participant CC as Claude Code
participant TP as Tool Pool
participant MCP as MCP Server
Note over CC,MCP: 初始化阶段
MCP->>CC: 注册工具列表 (name + description)
CC->>TP: assembleToolPool(builtIn + mcp)
TP->>LLM: 发送高频工具完整 Schema
+ 低频工具延迟标记
Note over LLM,CC: 运行阶段
LLM->>CC: ToolSearch(keyword="database")
CC->>TP: 检索匹配工具
TP->>LLM: 返回完整 Schema
LLM->>CC: 调用目标工具六、总结
Claude Code 的工具系统是协议驱动、分层过滤、类型安全、动态扩展的典范设计。其核心架构可以概括为:
- 一个基类协议(
Tool.ts):通过 TypeScript 结构类型定义工具契约,buildTool工厂提供安全默认值,降低扩展成本。 - 一个注册中心(
tools.ts):getAllBaseTools()聚合 40+ 工具,getTools()多层过滤,assembleToolPool()合并 MCP 动态工具。 - 一套 Schema 系统(Zod):输入输出全链路类型安全,
.describe()直接影响模型决策,延迟加载和缓存机制优化 Token 开销。 - 一层权限网关:工具通过
isReadOnly/isDestructive/checkPermissions声明安全属性,Permission Context 在调用前统一拦截。
这种架构让 Claude Code 能够在同一份代码库中同时支持:极简的 --simple 模式(3 个工具)、标准的交互模式(30+ 工具)、复杂的 Coordinator-Worker 多 Agent 模式、以及外部 MCP 生态的无限扩展。对于任何正在构建 AI Agent 平台的开发者来说,这套设计都是一份值得深入研究的参考实现。