工具系统:4个原语,59个工具
工具系统大概是 Claude Code 源码里规模最大的板块——约 50,000 行代码、59 个独立的工具目录。但把所有复杂度剥开,工具的本质可以归结为 4 种动作。
四个能力原语
本节源码:
src/tools/(按能力分类来自各工具的Toolinterface 推断)
| 原语 | 做什么 | 代表工具 | 风险等级 |
|---|---|---|---|
| Read | 读取信息 | FileRead、Grep、Glob、WebFetch、WebSearch | 低风险(不改任何东西) |
| Write | 创建或修改 | FileWrite、FileEdit、NotebookEdit | 中风险 |
| Execute | 运行命令 | Bash、PowerShell、REPL | 中高风险(改系统状态) |
| Connect | 连接外部系统 | MCP 工具、Agent、SendMessage、TeamCreate | 视场景而定 |
四分类不是事后归纳,而是从源码权限设计就能看出来——权限系统直接按这四个层级设防。提醒我们:先想清楚能力边界,再写代码。
工具全景
本节源码:
src/tools/(目录结构就是工具清单本身)
src/tools/ 目录下 59 个工具按功能归类:
| 类别 | 数量 | 代表工具 |
|---|---|---|
| 文件操作 | 5 | FileRead, FileWrite, FileEdit, Glob, Grep |
| 命令执行 | 3 | Bash, PowerShell, REPL |
| Web 访问 | 3 | WebFetch, WebSearch, WebBrowser |
| Agent 协调 | 9 | Agent, SendMessage, TeamCreate, TeamDelete, TaskCreate/Get/List/Update/Stop |
| 计划与工作区 | 4 | EnterPlanMode, ExitPlanMode, EnterWorktree, ExitWorktree |
| MCP 相关 | 4 | MCPTool, McpAuth, ListMcpResources, ReadMcpResource |
| 技能与配置 | 4 | Skill, DiscoverSkills, Config, ToolSearch |
| 内部专用 | 7+ | Tungsten, Monitor, ReviewArtifact, SuggestBackgroundPR... |
| KAIROS 专用 | 3 | PushNotification, SendUserFile, SubscribePR |
| 其他 | 若干 | Sleep, Brief, TodoWrite, NotebookEdit, RemoteTrigger... |
Bash:59 个工具里最特殊的那一个
本节源码:
src/tools/BashTool/BashTool.tsx(1143 行,bashSecurity.ts2592 行,commandSemantics.ts140 行)
Bash 是万能适配器。npm install、python test.py、git push、docker 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-onlybashSecurity.ts做 AST 级解析,检测输出重定向、命令替换等可能隐藏危险的模式
关键规则:管道中所有部分都必须是搜索/读取命令,整个管道才被认作 read-only。cat 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 的机制是分层加载:
- 启动时只告诉模型「有这些工具可用」(名称列表),不提供完整参数定义
- 模型用到某个工具时,先调
ToolSearch拿到完整定义 - 然后再调用实际工具
MCP 场景下这套机制尤为关键。如果你接了 5 个 MCP 服务器各暴露 10 个工具,就是 50 个额外定义,全加载可能 20,000–30,000 token 的上下文。每个 MCP 服务器的工具定义大约固定消耗 4,000–6,000 token。工具不是越多越好,每一个都有认知成本。
插件式架构
本节源码:
src/Tool.ts(Toolinterface 是插件契约;各工具目录如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 | 给模型看的工具描述 |
parameters | Zod 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.」
另外两道防线
- Edit 之前必须先 Read。源码检查
readFileState,文件没读过直接拒。这阻止 AI 在不了解内容时盲目改,也确保唯一匹配检查基于最新状态。 - 读取后是否被改过(时间戳比对)。用户或 linter 修改过文件的话,Edit 要求 AI 重新读取,避免基于陈旧内容替换。
为什么不用 sed? sed 是行导向的文本处理工具,靠正则匹配。AI 生成的正则经常踩坑——转义字符、贪婪匹配、多行模式全是雷。Edit 走精确字符串匹配,AI 只需要复制粘贴它刚刚读到的文本,写出修改版。错误率从「经常出问题」降到「几乎不出问题」。
工具结果的大小控制
本节源码:
src/Tool.ts(maxResultSizeChars字段定义于 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 万字符塞满上下文,后续对话质量必然下降。这个机制把工具结果的「存储」和「可见性」解耦了。
例外:FileReadTool 的 maxResultSizeChars 是 Infinity,源码注释解释:
// 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.ts、constants.ts、prompt.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 产品的能力更新节奏快,这种架构灵活性至关重要。