主题
15. Claude Code 工具系统
← 返回目录 | 上一章:Claude Code Agent 与编排系统 | 下一章:Claude Code 状态管理与 UI →
上一章我们看到了 Agent 如何编排查询循环和任务调度。本章深入工具层——那些 Agent 实际调用的"手脚"。Claude Code 拥有 50+ 内置工具和无限扩展的 MCP 工具,通过统一的
buildTool工厂函数构建,配合多层权限模型和流式并行执行器,形成了当前开源 AI 编码工具中最复杂的工具系统之一。
15.1 Tool 类型系统
Claude Code 的工具定义在 src/Tool.ts(792 行)中,以一个三参数泛型接口为核心:
typescript
type Tool<
Input extends AnyObject = AnyObject, // Zod schema
Output = unknown, // 返回值类型
P extends ToolProgressData = ToolProgressData // 进度事件
> = {
name: string
aliases?: string[] // 重命名后的向后兼容
searchHint?: string // ToolSearch 关键词匹配用
inputSchema: Input // Zod v4 schema
outputSchema?: z.ZodType // 可选的输出 schema
maxResultSizeChars: number // 超限后持久化到磁盘
strict?: boolean // 严格模式(API 侧更严格遵循 schema)
// 核心方法
call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
description(input, options): Promise<string>
prompt(options): Promise<string>
// 行为声明(用于编排决策)
isConcurrencySafe(input): boolean // 能否并行
isReadOnly(input): boolean // 是否只读
isDestructive?(input): boolean // 是否不可逆
isEnabled(): boolean // 当前环境是否可用
interruptBehavior?(): 'cancel' | 'block' // 用户中断时的行为
// 权限
checkPermissions(input, context): Promise<PermissionResult>
validateInput?(input, context): Promise<ValidationResult>
preparePermissionMatcher?(input): Promise<(pattern: string) => boolean>
// UI 渲染(React/Ink 组件)
renderToolUseMessage(input, options): React.ReactNode
renderToolResultMessage?(content, progress, options): React.ReactNode
renderToolUseProgressMessage?(progress, options): React.ReactNode
renderToolUseRejectedMessage?(input, options): React.ReactNode
renderToolUseErrorMessage?(result, options): React.ReactNode
renderGroupedToolUse?(toolUses, options): React.ReactNode | null
// 辅助
userFacingName(input): string
getActivityDescription?(input): string | null // Spinner 文案
getToolUseSummary?(input): string | null // 紧凑视图摘要
toAutoClassifierInput(input): unknown // 安全分类器输入
mapToolResultToToolResultBlockParam(content, id): ToolResultBlockParam
// ...更多
}这个接口的设计哲学值得注意:
- 行为元数据与实现分离:
isConcurrencySafe、isReadOnly、isDestructive等方法不影响工具自身逻辑,但驱动编排层的调度决策(分批、串行、权限升级) - 输入依赖的行为声明:这些方法接收
input参数——同一个 BashTool,执行ls时是 concurrency-safe + read-only,执行rm -rf时则是 destructive + serial - 渲染与逻辑共存:每个工具自带完整的 UI 渲染能力(7 个 render 方法),工具既是功能单元也是 UI 组件
对比 Opencode:Opencode 的工具定义(tool/tool.go)相对精简——Name、Description、Schema、Execute 四大字段,没有行为元数据也没有渲染逻辑(UI 由 TUI 层统一处理)。这反映了两者的架构取向:Opencode 追求简洁解耦,Claude Code 追求工具层的自描述完备性。
15.2 buildTool 工厂模式
直接实现完整的 Tool 接口需要填充 30+ 个字段,大多数工具的许多字段值是相同的。buildTool 工厂函数解决了这个问题:
typescript
// 7 个可默认化的方法
type DefaultableToolKeys =
| 'isEnabled' // 默认 () => true
| 'isConcurrencySafe' // 默认 () => false(保守:假设不安全)
| 'isReadOnly' // 默认 () => false(保守:假设有写操作)
| 'isDestructive' // 默认 () => false
| 'checkPermissions' // 默认 allow(交给通用权限系统)
| 'toAutoClassifierInput' // 默认 ''(跳过分类器)
| 'userFacingName' // 默认使用 tool.name
function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return { ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def }
}默认值的设计遵循**失败安全(fail-closed)**原则:
- 不确定是否并发安全?默认串行执行
- 不确定是否只读?默认当作有写操作
- 没有特殊权限逻辑?默认交给通用权限系统
实际工具定义因此非常简洁,以 FileEditTool 为例:
typescript
export const FileEditTool = buildTool({
name: FILE_EDIT_TOOL_NAME,
searchHint: 'modify file contents in place',
maxResultSizeChars: 100_000,
strict: true,
// 核心逻辑
async call(input, context, canUseTool, parentMessage) { /* 625 行实现 */ },
async description() { return 'A tool for editing files' },
async prompt() { return getEditToolDescription() },
// 行为声明(覆盖默认值)
// isConcurrencySafe: 未覆盖 → false(文件编辑不能并行)
// isReadOnly: 未覆盖 → false(确实有写操作)
// 权限(覆盖默认的 allow)
async checkPermissions(input, context) {
return checkWritePermissionForTool(FileEditTool, input, ...)
},
// UI 渲染
renderToolUseMessage,
renderToolResultMessage,
renderToolUseRejectedMessage,
renderToolUseErrorMessage,
// 辅助
toAutoClassifierInput: (input) => `${input.file_path}: ${input.new_string}`,
getPath: (input) => input.file_path,
userFacingName,
getToolUseSummary,
// ...
})对比:Opencode 的工具注册也是声明式的(tool.Define + 结构体),但没有工厂函数和默认值机制——每个工具独立声明所有字段。OmO 的 Skill 系统则更高层:它定义的是"Sisyphus 应该如何使用工具组合",而非工具本身。三者形成了一个抽象层级:Claude Code(工厂+默认值)→ Opencode(直接声明)→ OmO(行为编排指令)。
15.3 工具全景:分类与清单
tools.ts 中的 getAllBaseTools() 是所有内置工具的唯一注册入口。根据功能可分为以下几大类:
文件系统工具
| 工具 | 功能 | 并发安全 | 特殊说明 |
|---|---|---|---|
| FileReadTool | 读取文件内容 | ✅ | maxResultSizeChars: Infinity(永不持久化,避免循环读取) |
| FileEditTool | 原地编辑文件 | ❌ | 625 行实现,含 diff 预览、行尾保持、引号风格保持 |
| FileWriteTool | 创建/覆盖文件 | ❌ | 自动创建中间目录 |
| NotebookEditTool | Jupyter Notebook 编辑 | ❌ | 专门处理 .ipynb 的 cell 级操作 |
| GlobTool | 文件名模式搜索 | ✅ | 当内嵌搜索工具可用时自动隐藏 |
| GrepTool | 文件内容搜索 | ✅ | 同上,Ant 内部构建嵌入了 ugrep |
Agent 与任务工具
| 工具 | 功能 | 说明 |
|---|---|---|
| AgentTool | 创建子 Agent | Coordinator 模式的核心,详见第 14 章 |
| TaskOutputTool | 获取后台任务输出 | 轮询子 Agent 结果 |
| TaskStopTool | 终止任务 | Worker 的 graceful shutdown |
| TaskCreate/Get/Update/ListTool | Todo v2 任务管理 | feature flag 控制,替代旧版 TodoWriteTool |
| SkillTool | 加载/执行 Skill | 从 .claude/skills/ 加载专业化指令 |
| SendMessageTool | 发送消息给 peer | 跨进程通信(UDS Inbox) |
| TeamCreate/DeleteTool | 创建/删除团队 | Agent Swarms 功能 |
搜索与网络工具
| 工具 | 功能 | 说明 |
|---|---|---|
| WebSearchTool | 网络搜索 | 返回结构化搜索结果 |
| WebFetchTool | 抓取网页内容 | URL → 文本 |
| WebBrowserTool | 浏览器自动化 | feature flag 控制 |
| ToolSearchTool | 搜索延迟加载的工具 | 详见 15.5 节 |
模式控制工具
| 工具 | 功能 | 说明 |
|---|---|---|
| EnterPlanModeTool | 进入计划模式 | 只规划不执行 |
| ExitPlanModeV2Tool | 退出计划模式 | 恢复执行权限 |
| EnterWorktreeTool | 进入 Git Worktree | 在独立工作树中操作 |
| ExitWorktreeTool | 退出 Git Worktree | 合并回主分支 |
| BriefTool | 切换简洁模式 | AI 主动调整输出详细度 |
| SnipTool | 手动裁剪上下文 | 配合 History Snip 压缩策略 |
Shell 与执行工具
| 工具 | 功能 | 说明 |
|---|---|---|
| BashTool | 执行 Shell 命令 | 18 个文件的子目录,含安全分析、sed 校验、沙箱判断 |
| REPLTool | 交互式 REPL | Ant 内部专用,在 VM 中包装其他原子工具 |
| PowerShellTool | Windows PowerShell | 条件加载 |
| LSPTool | LSP 交互 | 环境变量控制启用 |
特殊工具
| 工具 | 功能 | 说明 |
|---|---|---|
| AskUserQuestionTool | 向用户提问 | 交互式确认和澄清 |
| ConfigTool | 修改配置 | Ant 内部专用 |
| CronCreate/Delete/ListTool | 定时任务管理 | Agent Triggers 功能 |
| RemoteTriggerTool | 远程触发器 | 远程事件响应 |
| MonitorTool | 监控工具 | 后台监控任务 |
| SyntheticOutputTool | 合成输出 | 内部使用,不直接暴露给模型 |
条件加载机制
工具清单中大量使用 feature() 函数(来自 bun:bundle)和 process.env 进行条件加载:
typescript
// bun:bundle 编译时死代码消除
const REPLTool = process.env.USER_TYPE === 'ant'
? require('./tools/REPLTool/REPLTool.js').REPLTool : null
// feature flag(构建时确定)
const MonitorTool = feature('MONITOR_TOOL')
? require('./tools/MonitorTool/MonitorTool.js').MonitorTool : null
// 运行时条件
...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : [])这意味着外部用户看到的工具集与 Anthropic 内部(USER_TYPE=ant)的工具集有显著差异。REPLTool、ConfigTool、TungstenTool 等仅在内部构建中可用。
对比 Opencode:Opencode 的工具集相对精简(~15 个),全部始终可用,没有条件加载。这降低了复杂度,但也限制了针对不同场景的工具优化空间。
15.4 权限模型
Claude Code 的权限系统是三个项目中最复杂的,涉及多个决策层次和多种决策来源。
权限模式(PermissionMode)
系统定义了 7 种权限模式:
| 模式 | 行为 | 适用场景 |
|---|---|---|
default | 破坏性操作需确认 | 默认交互模式 |
acceptEdits | 自动接受文件编辑,其他仍需确认 | 信任编辑但谨慎执行 |
plan | 只读,禁止一切写操作 | 规划阶段 |
dontAsk | 不确认,不允许的直接拒绝 | 非交互式/CI 场景 |
bypassPermissions | 跳过所有权限检查 | 完全信任模式 |
auto | ML 分类器自动判断(内部) | 智能审批 |
bubble | 向上冒泡到父级决策 | 子 Agent 场景 |
权限决策流程
每次工具调用经过以下决策管线:
工具调用请求
↓
① validateInput() — 工具自身的输入校验
↓(通过)
② 全局 deny 规则匹配 — 立即拒绝匹配的工具
↓(未匹配)
③ tool.checkPermissions() — 工具特定的权限逻辑
↓(返回 allow / ask / deny / passthrough)
④ 通用权限系统 — 基于模式和规则的最终决策
↓(如果 ask)
⑤ 用户确认 / ML 分类器自动审批
↓
执行 tool.call()规则系统
权限规则来自多个层级,按优先级从高到低:
typescript
type PermissionRuleSource =
| 'policySettings' // 组织策略(最高优先级)
| 'cliArg' // 命令行参数
| 'localSettings' // 本地设置
| 'projectSettings' // 项目设置(.claude/settings.json)
| 'userSettings' // 用户全局设置
| 'session' // 会话级临时规则
| 'command' // 斜杠命令添加
| 'flagSettings' // feature flag 设置每条规则指定工具名和可选的内容匹配模式:
typescript
type PermissionRuleValue = {
toolName: string // 如 "Bash", "mcp__server"
ruleContent?: string // 如 "git *", "/tmp/**"
}这使得精细控制成为可能:允许 Bash(git *) 但拒绝 Bash(rm *),允许编辑 src/ 下的文件但拒绝编辑 .env。
auto 模式与 ML 分类器
当启用 auto 模式时,权限决策还会经过一个 ML 分类器(toAutoClassifierInput 方法就是为此设计的)。分类器评估工具调用的安全性,可以在用户看到确认对话框之前自动审批安全操作:
typescript
// BashTool 的分类器输入
toAutoClassifierInput: (input) => input.command // "git status"
// FileEditTool 的分类器输入
toAutoClassifierInput: (input) => `${input.file_path}: ${input.new_string}`权限决策的溯源
每个决策都携带 PermissionDecisionReason,记录决策来源:
rule— 哪条规则匹配了mode— 当前模式的默认行为hook— 哪个 Hook 做出了决策asyncAgent— 后台 Agent 的自动拒绝subcommandResults— BashTool 的子命令级权限聚合
这种溯源机制对调试和审计至关重要——当用户问"为什么这个操作被拒绝了"时,系统能给出精确答案。
对比 Opencode:Opencode 的权限模型简洁得多——dangerous 标记的工具需要确认,其余自动放行。没有多层规则、没有分类器、没有溯源。OmO 通过 Skill 的 MUST NOT DO 指令增加了一层软约束,但这是 prompt 层面的约束而非代码级强制。Claude Code 在这方面的投入,反映了其作为企业级产品对安全合规的严格要求。
15.5 ToolSearch 与延迟加载
当工具数量庞大时(50+ 内置 + MCP 工具),每次请求都发送所有工具定义会消耗大量 token。Claude Code 引入了 ToolSearch 机制解决这个问题:
初始请求时:
核心工具(~20 个) → 完整 schema 发送给模型
可延迟工具(~30 个)→ 仅发送 name + searchHint(defer_loading: true)
MCP 工具 → 根据 alwaysLoad 标记决定
模型需要某个延迟工具时:
调用 ToolSearchTool(query="select:NotebookEdit")
→ 返回完整 schema
→ 后续请求中该工具变为完整可用ToolSearch 支持两种查询模式:
- 直接选择:
select:<tool_name>— 精确匹配工具名 - 关键词搜索:自然语言描述 → 基于
searchHint和工具名的模糊匹配
工具的 shouldDefer 属性决定是否延迟加载,alwaysLoad 属性可强制始终加载(MCP 工具可通过 _meta['anthropic/alwaysLoad'] 设置)。
这个设计平衡了能力覆盖和 token 效率:模型知道所有工具的存在(通过名称),但只为实际需要的工具支付完整 schema 的 token 开销。
15.6 工具池装配与 MCP 集成
assembleToolPool:单一装配入口
内置工具和 MCP 工具通过 assembleToolPool 函数统一装配:
typescript
function assembleToolPool(permissionContext, mcpTools): Tools {
const builtInTools = getTools(permissionContext)
const allowedMcpTools = filterToolsByDenyRules(mcpTools)
// 按名称排序,内置工具作为前缀(prompt cache 稳定性)
// uniqBy name 去重(内置工具优先)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
}排序策略值得注意:内置工具始终排在 MCP 工具前面形成连续前缀,这确保了 Anthropic API 的 prompt cache 稳定性——当 MCP 工具增减时,不会打乱内置工具的位置导致缓存失效。
MCP 工具的桥接
MCP 工具通过 MCPTool 模板创建,在 mcpClient.ts 中动态覆盖其字段:
typescript
export const MCPTool = buildTool({
isMcp: true,
name: 'mcp', // 实际被覆盖为 mcp__serverName__toolName
async call() { ... }, // 实际被覆盖为 MCP 协议调用
async checkPermissions() {
return { behavior: 'passthrough' } // 交给通用权限系统
},
})MCP 工具名遵循 mcp__<serverName>__<toolName> 的命名约定,权限规则可以按服务器前缀批量控制(如拒绝整个不受信任的 MCP 服务器的所有工具)。
模式感知的工具过滤
getTools() 根据当前运行模式进行额外过滤:
- Simple 模式(
--bare):仅保留 Bash + FileRead + FileEdit - REPL 模式:隐藏被 REPL 包装的原子工具(由 REPL VM 内部使用)
- Coordinator 模式:Worker 看到受限工具集(详见 14.4 节)
- 所有模式都先经过
filterToolsByDenyRules全局过滤
对比 Opencode + OmO:Opencode 的 MCP 支持同样实现了工具注册和调用的统一接口,但没有 prompt cache 优化排序,也没有模式感知过滤。OmO 通过 skill_mcp 调用封装了 MCP 交互,增加了 Skill 级别的使用约束。三者在 MCP 集成深度上的差异,本质上反映了各自的规模——Claude Code 需要管理几十乃至上百个工具,排序和过滤是性能刚需;Opencode 的工具数量级还不需要这些优化。
15.7 BashTool:最复杂的单体工具
BashTool 是 Claude Code 中工程投入最大的单个工具,其子目录包含 18 个文件,覆盖安全分析、权限验证、沙箱隔离等多个维度:
tools/BashTool/
├── BashTool.tsx # 主入口与 call() 实现
├── bashPermissions.ts # Shell 命令的权限判断
├── bashSecurity.ts # 安全性分析(危险命令检测)
├── bashCommandHelpers.ts # 命令解析辅助
├── commandSemantics.ts # 命令语义分析(只读?破坏性?)
├── readOnlyValidation.ts # 只读命令白名单
├── sedValidation.ts # sed 命令的特殊校验
├── sedEditParser.ts # sed 表达式解析
├── destructiveCommandWarning.ts # 破坏性命令警告
├── modeValidation.ts # 模式相关校验
├── pathValidation.ts # 路径安全校验
├── shouldUseSandbox.ts # 沙箱决策
├── prompt.ts # 系统 prompt 生成
├── commentLabel.ts # 注释标签
├── toolName.ts # 工具名常量
├── utils.ts # 通用工具
├── UI.tsx # 渲染组件
└── BashToolResultMessage.tsx # 结果渲染输入依赖的行为判断
BashTool 的 isConcurrencySafe 和 isReadOnly 方法根据实际命令内容动态判断:
ls、cat、find、grep→ concurrency-safe + read-onlygit status、git diff→ concurrency-safe + read-onlynpm install、pip install→ serial + not read-onlyrm、chmod、mv→ serial + destructive
这种细粒度的语义分析使编排层能够安全地并行执行多个 ls 命令,同时确保 rm 命令串行执行。
Bash 错误的级联取消
在 StreamingToolExecutor 中,BashTool 享有特殊的错误处理逻辑:只有 Bash 错误会触发兄弟工具的级联取消。原因是 Shell 命令常有隐式依赖链(mkdir 失败后续命令无意义),而 FileRead、WebFetch 等工具相互独立,一个失败不应影响其他。
typescript
if (tool.block.name === BASH_TOOL_NAME) {
this.hasErrored = true
this.siblingAbortController.abort('sibling_error')
}对比 Opencode:Opencode 的 Bash 工具实现相对简单——执行命令、捕获输出、超时控制。没有命令语义分析、没有沙箱决策、没有级联取消。这不是缺陷而是取舍:Opencode 信任用户(需手动确认危险命令),Claude Code 则尝试在代码级别理解命令意图。
15.8 小结
Claude Code 的工具系统呈现出鲜明的工业级特征:
- 自描述完备:每个工具携带行为元数据(并发安全性、只读性、破坏性)、权限逻辑、UI 渲染能力,编排层无需了解工具内部实现
- 工厂 + 默认值:
buildTool以 fail-closed 原则提供安全默认值,60+ 个工具定义因此保持简洁 - 多层权限:7 种模式 × 8 种规则来源 × ML 分类器 × 决策溯源,满足企业级安全合规需求
- 延迟加载:ToolSearch 机制在不牺牲能力覆盖的前提下优化 token 开销
- 缓存友好:工具池装配时的排序策略确保 prompt cache 稳定性
- 单体深度:BashTool 的 18 文件子目录展示了对单个工具的极致工程投入
如果将三个系统的工具架构画成光谱:
简洁 ←————————————————————————→ 复杂
Opencode OmO 增强 Claude Code
(~15 工具, (Skill 编排层, (50+ 工具,
扁平注册, 行为约束, buildTool 工厂,
二元权限) 多级调度) 多层权限+分类器)下一章我们将转向 Claude Code 的状态管理与 UI 系统,看看 90+ 字段的全局状态对象和 144+ React 组件是如何协同工作的。
← 返回目录 | 上一章:Claude Code Agent 与编排系统 | 下一章:Claude Code 状态管理与 UI →