2.3 Tool Management¶
本节摘要
从State Management角度看,Tool Management回答的核心问题是:Agent 在每一步决策时,能「看到」和「使用」哪些工具? 本节按「定义了什么 → 怎么组装 → LLM 看到什么 → 何时变化 → 执行门控」的主线展开。
一、每个工具定义了什么¶
每个工具在 Tool.ts(792 行)中携带两类信息:供 LLM 消费的 Schema 和供框架使用的行为标记。
LLM 可见:Schema 定义¶
| 字段 | 说明 | LLM 如何使用 |
|---|---|---|
name |
工具名称(如 FileRead) |
LLM 在 tool_use 中引用 |
description |
功能描述 | LLM 据此判断何时调用 |
inputSchema |
Zod 定义的参数 Schema → 转 JSON Schema | LLM 据此构造调用参数 |
LLM 不可见:行为标记¶
| 标记 | 含义 | 对State Management的影响 |
|---|---|---|
isReadOnly |
是否为只读 | 只读工具跳过权限审批,Sub-agent 可安全使用 |
isDestructive |
是否有破坏性 | 强制进入审批流程,即使在 auto 模式 |
isConcurrencySafe |
是否可并发执行 | 决定 Sub-agent 能否并行调用 |
maxResultSizeChars |
结果最大字符数 | 直接影响Context 占用——防止单个结果挤爆窗口 |
shouldDefer |
是否可延迟执行 | 影响工具调用的优先级调度 |
这些标记是框架内部的State Management元数据——LLM 看不到它们,但它们决定了工具在运行时的过滤、权限、并发行为。
二、工具池怎么组装¶
Agent 可用的工具集不是一个静态列表,而是在每次会话启动时从三个来源动态组装的。
三个来源¶
| 来源 | 数量 | 命名方式 | 示例 |
|---|---|---|---|
| 内置工具 | 30+ | 直接名称 | FileRead、BashTool、AgentTool |
| MCP 工具 | 可变 | mcp__<server>__<tool> |
mcp__github__create_issue |
| 插件工具 | 可变 | plugin-name:skill-name |
cursor:create-rule |
组装流程:assembleToolPool()¶
function assembleToolPool(config: ToolPoolConfig): Tool[] {
const baseTools = getAllBaseTools(); // 30+ 内置工具
const mcpTools = await getMcpTools(); // MCP 服务器工具
const pluginTools = getPluginTools(); // 插件工具
// 合并:内置优先,按 name 去重
return uniqBy(
[...baseTools, ...mcpTools, ...pluginTools],
t => t.name
);
}
关键设计:内置优先。 uniqBy 按数组顺序去重,内置工具排在前面,同名时内置胜出。这是安全设计——防止恶意 MCP 服务器注册 FileWrite 等同名工具覆盖核心功能。
三、LLM 每次调用看到什么¶
工具池组装完成后,在每次 API 调用时,工具信息以 JSON Schema 数组的形式传给 LLM:
LLM 收到的工具信息¶
对于每个工具,LLM 看到的是 Anthropic API tool_use 格式:
| 字段 | 内容 | 来源 |
|---|---|---|
name |
工具名称 | Tool.name |
description |
功能描述文本 | Tool.description |
input_schema |
JSON Schema 格式的参数定义 | Tool.inputSchema(Zod → JSON Schema) |
LLM 不会看到行为标记(isReadOnly、isDestructive 等)——这些信息只在框架层用于权限判断和过滤。
工具信息的 Token 开销¶
工具定义本身占用Context Window。Claude Code 提供三种精度的 token 估算(tokenEstimation.ts):
| 方法 | 实现 | 用途 |
|---|---|---|
| 粗估 | length / 4(JSON 用 / 2) |
快速预估工具列表开销 |
| 精确 | Anthropic countTokens API |
压缩阈值判断 |
| 后备 | Haiku max_tokens=1 读 usage.input_tokens |
API 不可用时 |
四、工具集何时变化¶
工具池不是组装一次就固定的——在会话生命周期内,可见工具集会在多个场景下动态变化。
场景 1:Sub-agent 工具过滤¶
Sub-agent 的工具集通过三层过滤逐步收紧:
全量工具池
→ ALL_AGENT_DISALLOWED_TOOLS // 全局黑名单(如 AgentTool 自身)
→ CUSTOM_AGENT_DISALLOWED_TOOLS // 按 Agent 类型的黑名单
→ ASYNC_AGENT_ALLOWED_TOOLS // 异步 Agent 的白名单(仅允许特定工具)
MCP 工具始终透传——用户主动配置的外部能力不被框架层过滤。
场景 2:Coordinator 模式¶
Coordinator 模式下工具集大幅裁剪,仅保留 4 个工具:
| 保留的工具 | 用途 |
|---|---|
AgentTool |
启动/管理 Sub-agent |
SendMessage |
向 Sub-agent 发消息 |
TaskStop |
终止任务 |
SyntheticOutput |
生成结构化输出 |
所有"干活"的工具(FileRead/Write、Bash 等)都交给 Worker Sub-agent。
场景 3:MCP 热更新¶
MCP 服务器支持运行时添加/移除(/mcp 命令),启动时并行连接(最长等待 30 秒),Sub-agent 也可以初始化自己的 MCP 服务器。MCP 变更后工具池会重新组装。
五、执行门控:权限与安全¶
工具可见和工具可执行是两个不同的问题。LLM 看到一个工具并决定调用后,还需要通过权限系统的门控。
6 种权限模式¶
| 模式 | 工具调用需要什么 |
|---|---|
default |
每次用户确认 |
plan |
只允许只读工具 |
acceptEdits |
自动接受文件编辑 |
bypassPermissions |
自动放行(安全路径除外) |
auto |
YOLO 分类器自动判断 |
dontAsk |
直接拒绝需权限的操作 |
YOLO 分类器:auto 模式的核心¶
auto 模式下,Claude Code 用双阶段 LLM 分类器做安全判断:
graph LR
ToolCall["工具调用"] --> White["安全白名单?\nFileRead/Grep/Glob..."]
White -->|"是"| Allow["✅ 执行"]
White -->|"否"| Fast["Stage 1: Fast\nmax_tokens=64"]
Fast -->|"允许/拒绝"| Result["结果"]
Fast -->|"不确定"| Think["Stage 2: Thinking\nmax_tokens=4096"]
Think --> Result
| 设计要点 | 说明 |
|---|---|
| 安全白名单 | FileRead、Grep、Glob 等只读工具直接放行,跳过分类器 |
| 排除 assistant 文本 | 分类器只看 user 消息 + 工具参数,防止恶意文件内容诱导放行 |
| 拒绝熔断器 | 连续拒绝 3 次或累计 20 次 → 回退人工确认 |
| 不可绕过的安全路径 | .git/、.claude/、.vscode/ 操作即使 bypass 模式也强制 ask |
关键文件索引¶
| 文件 | 职责 |
|---|---|
src/Tool.ts |
工具类型定义 + 行为标记接口(792 行) |
src/tools.ts |
getAllBaseTools() + assembleToolPool() 组装 |
src/services/mcp/ |
MCP 客户端管理(连接、热重载) |
src/utils/permissions/permissions.ts |
权限检查主流程 |
src/utils/permissions/yoloClassifier.ts |
YOLO 双阶段分类器 |
src/utils/permissions/classifierDecision.ts |
安全工具白名单 |
src/services/tokenEstimation.ts |
工具 token 开销估算 |