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 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-only
  • bashSecurity.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 的机制是分层加载:

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

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

插件式架构​

本节源码:src/Tool.ts(Tool 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.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 产品的能力更新节奏快,这种架构灵活性至关重要。