Skip to main content

工具系统:4个原语,59个工具

工具系统大概是 Claude Code 源码里规模最大的板块——约 50,000 行代码、59 个独立的工具目录。但把所有复杂度剥开,工具的本质可以归结为 4 种动作

四个能力原语

本节源码:src/tools/(按能力分类来自各工具的 Tool interface 推断)

原语做什么代表工具风险等级
Read读取信息FileRead、Grep、Glob、WebFetch、WebSearch低风险(不改任何东西)
Write创建或修改FileWrite、FileEdit、NotebookEdit中风险
Execute运行命令Bash、PowerShell、REPL中高风险(改系统状态)
Connect连接外部系统MCP 工具、Agent、SendMessage、TeamCreate视场景而定

四分类不是事后归纳,而是从源码权限设计就能看出来——权限系统直接按这四个层级设防。提醒我们:先想清楚能力边界,再写代码

工具全景

本节源码:src/tools/(目录结构就是工具清单本身)

src/tools/ 目录下 59 个工具按功能归类:

类别数量代表工具
文件操作5FileRead, FileWrite, FileEdit, Glob, Grep
命令执行3Bash, PowerShell, REPL
Web 访问3WebFetch, WebSearch, WebBrowser
Agent 协调9Agent, SendMessage, TeamCreate, TeamDelete, TaskCreate/Get/List/Update/Stop
计划与工作区4EnterPlanMode, ExitPlanMode, EnterWorktree, ExitWorktree
MCP 相关4MCPTool, McpAuth, ListMcpResources, ReadMcpResource
技能与配置4Skill, DiscoverSkills, Config, ToolSearch
内部专用7+Tungsten, Monitor, ReviewArtifact, SuggestBackgroundPR...
KAIROS 专用3PushNotification, SendUserFile, SubscribePR
其他若干Sleep, Brief, TodoWrite, NotebookEdit, RemoteTrigger...

Bash:59 个工具里最特殊的那一个

本节源码:src/tools/BashTool/BashTool.tsx(1143 行,bashSecurity.ts 2592 行,commandSemantics.ts 140 行)

Bash 是万能适配器npm installpython test.pygit pushdocker build……通过 Bash Claude 就能操作几乎所有命令行工具。这也是 Claude Code 能在任意技术栈工作的原因,不靠为每个语言/框架写专门插件。

代价是 Bash 能做的太多,必须严格限制。system prompt 里写得很明白:

Do NOT use the Bash to run commands when a relevant dedicated tool is provided.

展开成几条具体规则:

  • 读文件 → Read,不用 cat / head / tail
  • 编辑文件 → Edit,不用 sed / awk
  • 查文件 → Glob,不用 find / ls
  • 查内容 → Grep,不用 grep / rg

为什么不能让 AI 自由用 Bash?

专用工具比万能工具更可控。Read 工具的权限审查很简单(只读文件),Bash 可以做任何事,权限系统必须理解命令语义才能判断安全——这要难得多。所以 Bash 是最后手段,不是首选。

源码里 Bash 安全分析用了三层把关

  • commandSemantics.ts 把命令按语义分类(搜索类、读取类、列表类、语义中立类),判断整个管道是不是 read-only
  • bashSecurity.ts 做 AST 级解析,检测输出重定向、命令替换等可能隐藏危险的模式

关键规则:管道中所有部分都必须是搜索/读取命令,整个管道才被认作 read-onlycat file | jq .field 安全(都是读),但 find . | xargs rm 不行(包含写)。

Deferred Tools:把 59 个工具的 token 成本压下来

本节源码:src/tools/ToolSearchTool/ToolSearchTool.ts

每个工具的描述(名字、参数 schema、使用说明)都要占 system prompt 的 token。59 个全展开可能要占几万 token——但实际任务往往只需要 5–10 个工具。

Deferred Tools 的机制是分层加载

  1. 启动时只告诉模型「有这些工具可用」(名称列表),不提供完整参数定义
  2. 模型用到某个工具时,先调 ToolSearch 拿到完整定义
  3. 然后再调用实际工具

MCP 场景下这套机制尤为关键。如果你接了 5 个 MCP 服务器各暴露 10 个工具,就是 50 个额外定义,全加载可能 20,000–30,000 token 的上下文。每个 MCP 服务器的工具定义大约固定消耗 4,000–6,000 token。工具不是越多越好,每一个都有认知成本

插件式架构

本节源码:src/Tool.tsTool interface 是插件契约;各工具目录如 src/tools/<ToolName>/ 实现它)

每个工具是一个独立目录,标准结构如下:

src/tools/<ToolName>/
├── <ToolName>.tsx # 工具实现(含 UI 组件)
├── prompt.ts # 描述和参数定义
├── toolName.ts # 工具名常量
├── (security.ts) # 安全分析(仅 Bash 等需要)
├── (semantics.ts) # 语义分析(仅 Bash 等需要)
└── utils.ts # 工具函数

新加一个工具就是「建目录 + 实现标准接口 + 注册到列表」——不碰核心代码。这就是 Claude Code 能从最开始的十几个工具快速长到 59 个的工程基础。

统一的 Tool 接口只有 5 个字段:

字段用途
name工具名称
description给模型看的工具描述
parametersZod schema 定义的参数格式
execute实际执行逻辑
permissionContext权限检查需要的上下文

FileEdit:唯一匹配比正则靠谱

本节源码:src/tools/FileEditTool/FileEditTool.ts

让 AI 编辑文件最直接的方案是给它 sed 或重写整个文件。Claude Code 没走这条路,而是做了一个唯一匹配的 FileEdit 工具。

调用 Edit 时传 old_string(要替换的内容)和 new_string(替换后的内容)。系统在文件里搜 old_string,找到多于一处就直接报错

if (matches > 1 && !replace_all) {
return {
result: false,
message: `Found ${matches} matches of the string to replace,
but replace_all is false. To replace all occurrences,
set replace_all to true. To replace only one occurrence,
please provide more context to uniquely identify the instance.`,
errorCode: 9,
}
}

这个设计直接治了 AI 编辑文件最容易犯的病——改错地方。函数名在文件里出现 5 次,AI 想改其中一处,唯一匹配约束会迫使它提供足够上下文(前后几行代码)来精确定位。这比行号匹配可靠得多:行号在编辑过程中会变,文本上下文不会变。

需要批量重命名的场景用 replace_all=true。prompt 里也明确告诉模型:「Use replace_all for replacing and renaming strings across the file.」

另外两道防线

  1. Edit 之前必须先 Read。源码检查 readFileState,文件没读过直接拒。这阻止 AI 在不了解内容时盲目改,也确保唯一匹配检查基于最新状态。
  2. 读取后是否被改过(时间戳比对)。用户或 linter 修改过文件的话,Edit 要求 AI 重新读取,避免基于陈旧内容替换。

为什么不用 sed? sed 是行导向的文本处理工具,靠正则匹配。AI 生成的正则经常踩坑——转义字符、贪婪匹配、多行模式全是雷。Edit 走精确字符串匹配,AI 只需要复制粘贴它刚刚读到的文本,写出修改版。错误率从「经常出问题」降到「几乎不出问题」。

工具结果的大小控制

本节源码:src/Tool.tsmaxResultSizeChars 字段定义于 line 466);src/tools/FileReadTool/FileReadTool.ts:342(Read 设为 Infinity 的特例)

容易被忽视但很关键的一个机制。每个工具有个 maxResultSizeChars 属性:

/**
* Maximum size in characters for tool result
* before it gets persisted to disk.
* When exceeded, the result is saved to a file
* and Claude receives a preview with the file
* path instead of the full content.
*/
maxResultSizeChars: number

默认值 100,000 字符。工具结果超过阈值时,系统不直接截断,而是把完整结果写到磁盘临时文件,对话里只放摘要 + 文件路径。Claude 需要完整结果时用 Read 工具去读。

为什么不直接截断?截断会丢信息。落盘的方案保留完整数据,只是不塞进上下文窗口。这就是把上下文窗口当稀缺资源管理的思路——一次 Grep 返回 10 万字符塞满上下文,后续对话质量必然下降。这个机制把工具结果的「存储」和「可见性」解耦了。

例外FileReadToolmaxResultSizeCharsInfinity,源码注释解释:

// Read tools whose output must // never be persisted (e.g. Read, where persisting // creates a circular Read→file→Read loop and the // tool already self-bounds via its own limits).

ToolSearch 的匹配算法

本节源码:src/tools/ToolSearchTool/(含 ToolSearchTool.tsconstants.tsprompt.ts 三文件)

Deferred Tools 加载靠的是 ToolSearch 工具。它支持两种查询模式

精确选择模式select: 开头按名字匹配,逗号分隔多个工具(如 select:Read,Edit,Grep)。模型已经知道要用什么工具时的快速路径。

关键词搜索模式:不带 select: 前缀就进关键词搜索。ToolSearch 按两种规则解析工具名:

  • 普通工具名(CamelCase)按驼峰分词:FileEditTool → ['file', 'edit', 'tool']
  • MCP 工具名mcp__server__action)按双下划线和单下划线拆分:mcp__slack__send_message → ['slack', 'send', 'message']

支持 + 前缀标记必须匹配的关键词(如 +slack send 要求工具名里必须有 slack),也支持按 MCP 前缀批量匹配(查 mcp__slack 返回所有 Slack 相关工具)。

匹配得分综合考虑:工具名分词 + 工具描述关键词 + 可选的 searchHint 字段。模型用自然语言描述需求(比如「modify file」),ToolSearch 就能找到 FileEdit。

给 AI 产品开发者的教训

本节源码:src/Tool.ts

工具系统是约束工程最直接的应用,几个朴素的结论:

  • 控制 AI 的最好方式不是写更长的 prompt,而是设计更好的工具。工具的参数 schema 就是一组约束,AI 只能在约束范围内操作。
  • 万能工具是后备,不是首选。Bash 能做一切,但正因如此最难控制。优先用功能明确、权限清晰的专用工具。
  • 工具有 token 成本。每个工具描述都占上下文空间,全加载代价大。Deferred 加载在 MCP 扩展场景下几乎必备。
  • 插件式架构支持快速迭代。新工具不动核心代码,团队可以并行开发。AI 产品的能力更新节奏快,这种架构灵活性至关重要。