跳转至

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+ 直接名称 FileReadBashToolAgentTool
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:

每次 API 请求 = system prompt + messages[] + tools[]
                                        工具池 → JSON Schema 序列化

LLM 收到的工具信息

对于每个工具,LLM 看到的是 Anthropic API tool_use 格式:

字段 内容 来源
name 工具名称 Tool.name
description 功能描述文本 Tool.description
input_schema JSON Schema 格式的参数定义 Tool.inputSchema(Zod → JSON Schema)

LLM 不会看到行为标记(isReadOnlyisDestructive 等)——这些信息只在框架层用于权限判断和过滤。

工具信息的 Token 开销

工具定义本身占用Context Window。Claude Code 提供三种精度的 token 估算(tokenEstimation.ts):

方法 实现 用途
粗估 length / 4(JSON 用 / 2 快速预估工具列表开销
精确 Anthropic countTokens API 压缩阈值判断
后备 Haiku max_tokens=1usage.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 开销估算