工具系统:42 个工具和一条治理流水线
不是模型说调就调。从 Tool 接口设计到 14 步执行 pipeline,看 Claude Code 怎么管理工具调用。
工具调用不是”模型说调就调”。打开 Tool.ts,792 行,光接口定义就超过 20 个方法。这不是一个简单的函数注册表——它是一套有明确治理规则的执行框架。
Tool 接口:不只是函数签名
最简单的工具抽象是:名字 + 输入格式 + 执行函数。Claude Code 的 Tool 接口走得更远。
执行层面:call() 是实际运行,inputSchema() 返回 Zod schema 用于 JSON schema 生成和运行时校验,validateInput() 在 schema 之外处理业务规则,checkPermissions() 做工具级权限检查。preparePermissionMatcher() 在执行前预先构造匹配器,避免每次调用都重建。
isReadOnly()、isDestructive()、isConcurrencySafe() 这三个方法声明工具的”性格”——调度器凭这三个值决定能不能并发、要不要额外警告、是否需要确认。
交互式终端、非交互式管道、IDE 集成、桌面通知,同一个工具结果在不同场景下格式要求各异。接口里超过 6 个 render 变体,就是为了覆盖这些场景。
prompt() 动态生成工具说明文本,内容随当前可用工具集和权限模式变化——不可用的功能不出现在说明里,模型不会去调用不该用的路径。另外两个方法服务于可观测性:backfillObservableInput() 把权限决策后被修改的输入版本回填到 observable 记录,确保日志和实际执行一致;toAutoClassifierInput() 给后面会讲的 speculative classifier 提供结构化输入。
buildTool() 的 fail-closed 默认值
工具用 buildTool() 工厂函数创建,不直接实现接口。工厂函数为没有显式声明的属性设置默认值,这些默认值的选择是一个设计决策:
isConcurrencySafe 和 isReadOnly 默认都是 false,checkPermissions 默认 allow(放行)。
前两个是保守的,checkPermissions 是宽松的——这不是矛盾,而是两个维度的不同考量。并发安全和只读性影响调度策略,宁可多等不能出错,所以默认严格。权限检查则是功能性的,新工具没有特殊限制就不应该默认拦截,所以 allow。
对开发者来说,这个设计的实际效果是”忘了就严格”:两个并发/只读属性漏写,工具被当成最保守的情况处理,不会出错只是慢一点或多一步确认。犯错成本低。
42 个工具的分类
tools/ 目录下 184 个文件,实现了 42 个工具,按职责分七类:
文件操作(FileRead / Edit / Write / Glob / Grep / NotebookEdit)、Shell(Bash / PowerShell)、Agent 调度(AgentTool + 6 个任务管理工具)、MCP 集成(MCPTool / ListMcpResources / ReadMcpResource / McpAuth)、Web(WebSearch / WebFetch)、用户交互(AskUserQuestion / SendMessage)、模式切换(EnterPlanMode / ExitPlanMode / EnterWorktree / ExitWorktree)、其他(SkillTool / TodoWrite / Config / ToolSearch / ScheduleCron 等)。
几个值得单独说的设计选择:NotebookEdit 独立于普通文件操作,因为 Jupyter 格式需要解析 cell 结构,不能当文本直接改。Agent 调度类有 7 个工具,不只是启动子 Agent,还覆盖了完整的任务生命周期——创建、查询、暂停、更新、读取输出。MCPTool 是动态生成的,连接 MCP server 后,server 提供的每个工具都会在运行时实例化出一个对应的 MCPTool,工具数量会随着连接的 server 增加。
14 步执行 Pipeline
toolExecution.ts 1745 行,是工具执行的完整实现。模型发出工具调用请求到工具真正跑完,中间经过 14 步——其中真正执行工具的只有第 10 步,其余 13 步全是围绕它的治理。
前四步是校验:按名字或别名找到工具,解析 MCP 元数据,Zod schema 校验输入格式,validateInput() 处理业务规则。格式不对第三步就打回去,不往后走。
步骤 5 是整个 pipeline 里设计最精妙的一处。BashTool 有专属的 speculative classifier,在正式权限决策之前异步启动,预判命令风险等级(读/写/网络/危险)。它和紧接着的 PreToolUse hooks 并行跑——Hook 在执行外部脚本时,classifier 同时在分析命令。等到步骤 8 真正需要做权限决策时,分类结果往往已经就绪,不产生额外等待。Hook 完成后返回值(allow/deny/modify)被解析,决定是否继续。
权限决策汇合三路输入:classifier 的风险评级、Hook 的明确指令、settings.json 里的 allow/deny list。只有三者都没有结论时,才弹出用户确认窗口。tool.call() 最终拿到的始终是决策通过、可能已被修改过的那份输入——决策结果和执行输入之间没有缝隙。
前九步铺垫完,第十步真正开始干活。tool.call() 返回后写入 OpenTelemetry 链路追踪,记录耗时和输入输出大小。执行结果打包成 tool_result block 写回对话上下文,供模型下一轮参考——PostToolUse hooks 在这里触发,外部脚本可以检查结果。出错则由 PostToolUseFailure hooks 接手,处理错误报告和告警。
14 步里只有第 10 步是真正在执行,其余 13 步全是围绕它的治理。这条流水线是 Claude Code 能在不受控代码库里安全运行的基础。
参考来源: 本文内容参考 Xiao Tan(@tvytlx)的《Claude Code 源码架构深度解析 V2.0》,基于原报告的分析框架和研究成果整理。