Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • Claude Code Tool System 架构分析

Claude Code Tool System 架构分析

1. 系统概览

Claude Code 的 Tool 系统是整个 CLI 的核心执行引擎。每个工具都是一个实现了统一接口的 TypeScript 对象,通过 buildTool() 工厂函数构造,支持权限校验、输入验证、并发控制、UI 渲染和结果映射。工具系统约 43 个工具目录,覆盖文件操作、Shell 执行、Web 搜索、子 Agent 调度、MCP 协议集成等场景。

关键设计原则:

  • 工厂模式统一接口: 所有工具通过 buildTool() 构建,自动填充安全默认值
  • Fail-closed 权限模型: isReadOnly/isConcurrencySafe 默认为 false,未实现时假设不可读且不可并发
  • Zod schema 驱动: 输入/输出使用 zod/v4 做运行时类型校验
  • 懒加载 schema: 使用 lazySchema() 避免循环依赖和启动开销
  • 条件编译: 使用 feature() 做 dead code elimination,按环境动态启用/禁用工具

2. 核心类型定义

2.1 Tool 类型 (src/Tool.ts:362-695)

Tool 是整个系统的核心泛型类型:

// src/Tool.ts:362-366
export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  readonly name: string
  aliases?: string[]             // 重命名兼容
  searchHint?: string            // ToolSearch 关键词匹配

  // --- 核心方法 ---
  call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult<Output>>
  description(input, options): Promise<string>
  prompt(options): Promise<string>

  // --- Schema ---
  readonly inputSchema: Input                    // Zod schema
  readonly inputJSONSchema?: ToolInputJSONSchema // MCP JSON Schema
  outputSchema?: z.ZodType<unknown>

  // --- 能力标志 ---
  isConcurrencySafe(input): boolean    // 默认 false
  isEnabled(): boolean                 // 默认 true
  isReadOnly(input): boolean           // 默认 false
  isDestructive?(input): boolean       // 不可逆操作标记

  // --- 权限 ---
  validateInput?(input, context): Promise<ValidationResult>
  checkPermissions(input, context): Promise<PermissionResult>

  // --- UI 渲染 ---
  renderToolUseMessage(input, options): React.ReactNode
  renderToolResultMessage(content, progressMessages, options): React.ReactNode
  renderToolUseProgressMessage?(progressMessages, options): React.ReactNode
  renderToolUseErrorMessage?(result, options): React.ReactNode
  renderToolUseRejectedMessage?(input, options): React.ReactNode

  // --- 结果映射 ---
  mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
  extractSearchText?(out: Output): string
}

2.2 ToolDef 类型 (src/Tool.ts:721-726)

ToolDef 是 Tool 的部分定义,可省略 buildTool 会自动填充的方法:

// src/Tool.ts:721-726
type DefaultableToolKeys =
  | 'isEnabled' | 'isConcurrencySafe' | 'isReadOnly'
  | 'isDestructive' | 'checkPermissions' | 'toAutoClassifierInput'
  | 'userFacingName'

export type ToolDef<Input, Output, P> =
  Omit<Tool<Input, Output, P>, DefaultableToolKeys> &
  Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>

2.3 buildTool() 工厂函数 (src/Tool.ts:783-792)

// src/Tool.ts:757-769 — 安全默认值
const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?: unknown) => false,  // 默认非并发安全
  isReadOnly: (_input?: unknown) => false,         // 默认假设写入
  isDestructive: (_input?: unknown) => false,
  checkPermissions: (input, _ctx) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
  toAutoClassifierInput: (_input?: unknown) => '',
  userFacingName: (_input?: unknown) => '',
}

// src/Tool.ts:783-792
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}

2.4 ToolResult<T> 类型 (src/Tool.ts:321-336)

export type ToolResult<T> = {
  data: T
  newMessages?: (UserMessage | AssistantMessage | AttachmentMessage | SystemMessage)[]
  contextModifier?: (context: ToolUseContext) => ToolUseContext
  mcpMeta?: { _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown> }
}

3. 工具注册与调度机制

3.1 工具注册 (src/tools.ts:193-251)

所有工具通过 getAllBaseTools() 函数注册。这是一个静态函数(非运行时注册表),直接返回工具数组:

// src/tools.ts:193-251
export function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]), // 条件注册
    ExitPlanModeV2Tool,
    FileReadTool, FileEditTool, FileWriteTool,
    NotebookEditTool, WebFetchTool, TodoWriteTool,
    WebSearchTool, TaskStopTool, AskUserQuestionTool,
    SkillTool, EnterPlanModeTool,
    // ... 更多条件工具
    ...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []),
  ]
}

3.2 工具过滤链 (src/tools.ts:271-327)

getTools() 实现了多层过滤:

  1. 简单模式过滤: CLAUDE_CODE_SIMPLE 环境变量时只保留 Bash/Read/Edit
  2. Deny 规则过滤: filterToolsByDenyRules() 移除被权限策略禁用的工具
  3. REPL 模式过滤: REPL 启用时隐藏内部原始工具(它们通过 REPL VM 间接可访问)
  4. isEnabled 过滤: 调用每个工具的 isEnabled() 方法
// src/tools.ts:271-327
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { /* 只返回 Bash/Read/Edit */ }
  const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
  let allowedTools = filterToolsByDenyRules(tools, permissionContext)
  if (isReplModeEnabled()) { /* 过滤 REPL_ONLY_TOOLS */ }
  const isEnabled = allowedTools.map(_ => _.isEnabled())
  return allowedTools.filter((_, i) => isEnabled[i])
}

3.3 工具池组装 (src/tools.ts:345-367)

assembleToolPool() 合并内置工具和 MCP 工具:

export function assembleToolPool(permissionContext, mcpTools): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
  // 内置工具在前(优先级更高),MCP 工具在后,各自按名称排序保证缓存稳定性
  const byName = (a, b) => a.name.localeCompare(b.name)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}

4. 工具执行生命周期

一个工具调用的完整流程:

模型输出 tool_use 块
  │
  ▼
1. findToolByName() — 按 name 或 aliases 查找工具
  │
  ▼
2. backfillObservableInput() — 补充/规范化输入(如展开 ~ 路径)
  │
  ▼
3. validateInput() — 纯逻辑校验(无 I/O 或轻量 I/O)
  │  ├─ 返回 { result: false, message, errorCode } → 工具执行中止,告知模型原因
  │  └─ 返回 { result: true } → 继续
  │
  ▼
4. checkPermissions() — 权限检查(工具特定逻辑 + 通用权限系统)
  │  ├─ 'deny' → 拒绝执行,通知模型
  │  ├─ 'ask'  → 弹出用户确认对话框
  │  └─ 'allow' → 继续
  │
  ▼
5. call() — 实际执行
  │  ├─ 返回 ToolResult<Output>
  │  ├─ 可通过 onProgress 回调报告进度
  │  └─ 可通过 newMessages 附加额外消息
  │
  ▼
6. mapToolResultToToolResultBlockParam() — 将输出转换为 API 格式
  │
  ▼
7. renderToolResultMessage() — 渲染 UI 结果

5. 权限模型

5.1 权限类型层次 (src/types/permissions.ts)

// 权限行为:三方决策
type PermissionBehavior = 'allow' | 'deny' | 'ask'

// 权限模式:控制整体策略
type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions'
                    | 'dontAsk' | 'plan' | 'auto' | 'bubble'

// 权限规则
type PermissionRule = {
  source: PermissionRuleSource       // userSettings, projectSettings, localSettings, ...
  ruleBehavior: PermissionBehavior
  ruleValue: { toolName: string; ruleContent?: string }
}

5.2 两层权限检查

第一层:工具内 validateInput() (src/Tool.ts:489-492)

纯逻辑校验,不依赖通用权限系统。返回 ValidationResult:

type ValidationResult =
  | { result: true }
  | { result: false; message: string; errorCode: number }

示例 — FileReadTool.validateInput() (src/tools/FileReadTool/FileReadTool.ts:418-495):

  • 检查 PDF 页码范围格式
  • 检查 deny 规则
  • 检查 UNC 路径安全
  • 检查二进制文件扩展名
  • 检查阻塞设备文件路径

第二层:checkPermissions() (src/Tool.ts:500-503)

工具特定权限 + 通用权限系统的联合检查:

// src/Tool.ts:500-503
checkPermissions(input: z.infer<Input>, context: ToolUseContext): Promise<PermissionResult>

PermissionResult 是一个 union type (src/types/permissions.ts:174-230+):

  • PermissionAllowDecision: { behavior: 'allow', updatedInput?, decisionReason? }
  • PermissionDenyDecision: { behavior: 'deny', message, decisionReason }
  • PermissionAskDecision: { behavior: 'ask', message, suggestions?, decisionReason? }

5.3 权限检查策略 (工具特定 vs 通用)

不同工具实现不同的权限策略:

工具checkPermissions 策略
BashToolbashToolHasPermission() — 完整的命令语义分析 (bashPermissions.ts, 2621行)
FileReadToolcheckReadPermissionForTool() — 文件系统读权限
FileEditTool/FileWriteTool写权限检查 + settings 文件特殊验证
WebFetchTool域名级别 allow/deny/ask 规则 + 预批准主机列表
GrepTool/GlobToolcheckReadPermissionForTool() — 读权限(与 FileReadTool 共享)
MCPTool返回 behavior: 'passthrough' — 委托给 MCP 客户端

5.4 BashTool 权限模型详解 (src/tools/BashTool/bashPermissions.ts)

BashTool 拥有最复杂的权限系统(2621行):

  • 命令 AST 解析: 使用 parseForSecurity() 解析 shell 命令的 AST
  • 子命令级权限: 复合命令中每个子命令独立检查(ls && git push 中 git push 需要权限)
  • 安全分类器: classifyBashCommand() 进行风险分级
  • sed 模拟编辑: 检测到 sed 原地编辑时,转换为模拟编辑以便用户预览 diff
  • 沙箱支持: shouldUseSandbox() 决定是否在沙箱中运行

6. 详细工具实现分析

6.1 BashTool (src/tools/BashTool/)

文件结构 (18 个文件):

  • BashTool.tsx — 主入口 (1144 行)
  • bashPermissions.ts — 权限检查 (2621 行)
  • bashSecurity.ts — 安全分析
  • sedEditParser.ts — sed 命令解析,将 sed 转换为 FileEdit 操作
  • readOnlyValidation.ts — 只读约束检查
  • commandSemantics.ts — 命令结果语义解释
  • shouldUseSandbox.ts — 沙箱决策
  • prompt.ts — 系统提示词 (369 行)

输入 Schema:

z.strictObject({
  command: z.string(),
  description: z.string().optional(),
  timeout: z.number().optional(),
  run_in_background: z.boolean().optional(),
})

关键特性:

  • isReadOnly 动态判断:分析命令是否为只读操作 (line 437-441)
  • isConcurrencySafe 基于 isReadOnly (line 434-436)
  • 自动后台化:超时命令自动转后台执行
  • sed 拦截:检测 sed 原地编辑,直接操作文件以支持用户预览 diff
  • 进度报告:通过 async generator runShellCommand() 实时输出

6.2 FileReadTool (src/tools/FileReadTool/)

文件结构 (5 个文件):

  • FileReadTool.ts — 主入口 (1183 行)
  • imageProcessor.ts — 图片处理
  • limits.ts — 读取限制
  • prompt.ts — 提示词
  • UI.tsx — UI 渲染

输入 Schema:

z.strictObject({
  file_path: z.string(),
  offset: z.number().int().nonnegative().optional(),
  limit: z.number().int().positive().optional(),
  pages: z.string().optional(),     // PDF 页码范围
})

输出 Schema: 判别联合类型 (discriminatedUnion),支持 6 种输出:

  • text — 普通文本文件
  • image — 图片(base64 编码)
  • notebook — Jupyter notebook
  • pdf — PDF 文件(base64)
  • parts — 提取的 PDF 页面图片
  • file_unchanged — 去重优化(重复读取同一文件返回 stub)

关键设计:

  • 文件去重: readFileState 缓存文件状态(mtime + content),相同范围的重复读取返回 file_unchanged stub (line 540-573)
  • Token 预算: validateContentTokens() 确保内容不超过 token 限制
  • 图片压缩: 基于 token 预算的多级压缩策略
  • maxResultSizeChars = Infinity: 结果永不被持久化到磁盘(避免 Read→file→Read 循环)
  • CYBER_RISK_MITIGATION_REMINDER: 读取文件时注入恶意代码分析提醒

6.3 AgentTool (src/tools/AgentTool/)

文件结构 (15 个文件):

  • AgentTool.tsx — 主入口 (1398 行)
  • runAgent.ts — Agent 执行引擎
  • forkSubagent.ts — Fork 子 Agent
  • loadAgentsDir.ts — Agent 定义加载
  • resumeAgent.ts — Agent 恢复
  • built-in/ — 内置 Agent 定义

输入 Schema: 基础 + 扩展

// 基础
z.object({
  description: z.string(),
  prompt: z.string(),
  subagent_type: z.string().optional(),
  model: z.enum(['sonnet', 'opus', 'haiku']).optional(),
  run_in_background: z.boolean().optional(),
})
// 扩展(feature-gated)
+ { name, team_name, mode, isolation, cwd }

输出 Schema: 两种模式

  • 同步: { status: 'completed', result, prompt, ... }
  • 异步: { status: 'async_launched', agentId, description, prompt }

关键设计:

  • 子 Agent 调度: 通过 runAgent() 启动独立的 Agent 执行上下文
  • 后台 Agent: run_in_background 支持异步 Agent,返回 async_launched 状态
  • 工作区隔离: isolation: 'worktree' 创建临时 git worktree
  • 工具池过滤: 通过 ASYNC_AGENT_ALLOWED_TOOLS 常量限制子 Agent 可用工具 (src/constants/tools.ts:55-71)
  • Fork 子 Agent: forkSubagent.ts 实现高效的子 Agent 分叉

6.4 MCPTool (src/tools/MCPTool/)

文件结构 (4 个文件):

  • MCPTool.ts — 主入口 (77 行) — 非常精简
  • classifyForCollapse.ts — UI 折叠分类
  • prompt.ts — 提示词
  • UI.tsx — UI 渲染

核心特点: 这是一个模板/原型工具,实际的 MCP 工具在 mcpClient.ts 运行时动态克隆和填充:

// src/tools/MCPTool/MCPTool.ts:27-77
export const MCPTool = buildTool({
  isMcp: true,          // MCP 标记
  name: 'mcp',          // 占位名 — 被 mcpClient.ts 覆盖
  maxResultSizeChars: 100_000,
  // 以下方法全部被覆盖
  async call() { return { data: '' } },
  async checkPermissions() { return { behavior: 'passthrough', ... } },
  inputSchema: z.object({}).passthrough(),  // 接受任意输入
})

MCP 工具特殊性:

  • isOpenWorld(): 返回 false — MCP 工具作用域不透明
  • isMcp = true: 全局标记,用于工具池过滤和权限检查
  • 权限返回 passthrough:委托给 MCP 客户端层面处理
  • inputSchema 使用 .passthrough() 允许任意额外字段

6.5 GrepTool (src/tools/GrepTool/GrepTool.ts)

简洁实现 (577 行):基于 ripgrep 的文件内容搜索工具。

输入 Schema:

z.strictObject({
  pattern: z.string(),
  path: z.string().optional(),
  glob: z.string().optional(),
  output_mode: z.enum(['content', 'files_with_matches', 'count']).optional(),
  '-B', '-A', '-C', context: z.number().optional(),  // 上下文行
  '-n': z.boolean().optional(),   // 行号
  '-i': z.boolean().optional(),   // 大小写不敏感
  type: z.string().optional(),    // 文件类型
  head_limit: z.number().optional(),
  offset: z.number().optional(),
  multiline: z.boolean().optional(),
})

关键设计:

  • isConcurrencySafe = true, isReadOnly = true — 纯读取操作
  • isSearchOrReadCommand = { isSearch: true } — UI 可折叠
  • maxResultSizeChars = 20_000 — 超过 20K 字符持久化到磁盘
  • 排除 VCS 目录(.git, .svn, .hg 等)
  • 结果按 mtime 排序(最近修改的文件优先)
  • applyHeadLimit() 实现分页,支持 offset

6.6 WebFetchTool (src/tools/WebFetchTool/WebFetchTool.ts)

URL 内容获取 (318 行)。

权限模型特别之处:

  • 预批准主机: isPreapprovedHost() 跳过权限检查
  • 域名级规则: 从 URL 提取 hostname,匹配 domain:hostname 形式的权限规则
  • 默认 ask: 没有匹配规则时,请求用户确认
// src/tools/WebFetchTool/WebFetchTool.ts:104-180
async checkPermissions(input, context): Promise<PermissionDecision> {
  // 1. 预批准主机 → allow
  if (isPreapprovedHost(hostname, pathname)) return { behavior: 'allow', ... }
  // 2. deny 规则 → deny
  // 3. ask 规则 → ask
  // 4. allow 规则 → allow
  // 5. 无规则 → ask (默认)
  return { behavior: 'ask', suggestions: buildSuggestions(ruleContent) }
}

7. 跨工具通用模式

7.1 Schema 定义模式

所有工具使用 lazySchema() 包装 Zod schema,避免循环依赖:

const inputSchema = lazySchema(() => z.strictObject({ ... }))
type InputSchema = ReturnType<typeof inputSchema>

// 在 ToolDef 中:
get inputSchema(): InputSchema { return inputSchema() }

7.2 工具目录结构

每个工具遵循标准目录结构:

src/tools/ToolName/
├── ToolName.ts(x)    — 主入口(buildTool 调用)
├── prompt.ts         — 提示词和常量
├── UI.tsx            — React UI 渲染组件
├── [utils.ts]        — 可选工具函数
├── [limits.ts]       — 可选限制配置
└── [permissions.ts]  — 可选权限逻辑

7.3 UI 渲染方法

每个工具需要实现多个 UI 方法:

  • renderToolUseMessage() — 工具调用时的输入展示
  • renderToolResultMessage() — 执行完成后的结果展示
  • renderToolUseProgressMessage?() — 执行中的进度展示
  • renderToolUseErrorMessage?() — 错误时的展示
  • renderToolUseRejectedMessage?() — 权限拒绝时的展示

这些方法返回 React.ReactNode,由 Ink 在终端中渲染。

7.4 并发安全模式

// 只读工具声明并发安全
isConcurrencySafe() { return true }
isReadOnly() { return true }

// 写入工具默认非并发安全(TOOL_DEFAULTS 中 isConcurrencySafe = false)
// BashTool 动态判断
isConcurrencySafe(input) { return this.isReadOnly(input) }

并发安全的工具可以并行执行,非并发安全的工具会被串行化。

7.5 工具结果大小管理

每个工具声明 maxResultSizeChars,超过阈值的结果会自动持久化到磁盘:

工具maxResultSizeChars说明
FileReadToolInfinity永不持久化(避免循环)
BashTool30,000
GrepTool20,000
MCPTool100,000
WebFetchTool100,000

7.6 toAutoClassifierInput() 模式

用于安全分类器的输入摘要。BashTool 返回整个命令字符串,FileReadTool 返回文件路径,空字符串表示跳过分类器:

// BashTool
toAutoClassifierInput(input) { return input.command }
// FileReadTool
toAutoClassifierInput(input) { return input.file_path }
// 无安全相关性的工具可省略(默认 '')

7.7 preparePermissionMatcher() 模式

为 Hook 的 if 条件准备匹配器。BashTool 分析命令 AST 提取子命令,FileReadTool 匹配文件路径:

// BashTool (src/tools/BashTool/BashTool.tsx:445-467)
async preparePermissionMatcher({ command }) {
  const parsed = await parseForSecurity(command)
  if (parsed.kind !== 'simple') return () => true  // fail-safe
  const subcommands = parsed.commands.map(c => c.argv.join(' '))
  return pattern => { /* 匹配逻辑 */ }
}

8. Notable Design Decisions

8.1 buildTool 工厂 vs 手写完整 Tool

所有工具使用 buildTool({ ...TOOL_DEFAULTS, ...def }) 模式,而不是直接导出 Tool 对象。好处:

  1. 安全默认值: isReadOnly/isConcurrencySafe 默认 false(fail-closed)
  2. 类型推断: BuiltTool<D> 在类型级别模拟运行时 spread,保持精确类型
  3. 零运行时开销: 只做一次对象展开

8.2 MCPTool 作为原型模式

MCPTool 是一个"空壳"工具,mcpClient.ts 在运行时克隆它并填充真实的 name、schema、call 实现。这是一种 prototype 模式,避免为每个 MCP 服务器创建独立的工具类。

8.3 权限与验证的分离

validateInput() 和 checkPermissions() 是两个独立阶段:

  • validateInput: 纯逻辑校验(格式正确性、路径存在性)—— 失败时直接返回错误给模型
  • checkPermissions: 权限决策(用户是否允许)—— 可弹出 UI 确认

8.4 条件编译与 Dead Code Elimination

通过 feature() 和 process.env 实现编译时工具过滤:

// src/tools.ts:16-18
const REPLTool = process.env.USER_TYPE === 'ant'
  ? require('./tools/REPLTool/REPLTool.js').REPLTool
  : null

这确保未启用的功能不会出现在打包产物中。

8.5 工具池分层过滤

getAllBaseTools()          — 全量工具列表
  ↓ filterToolsByDenyRules()   — 按权限策略过滤
  ↓ REPL 模式过滤              — 隐藏 REPL 内部工具
  ↓ isEnabled() 过滤           — 功能开关过滤
  ↓ + MCP 工具                  — 合并外部工具
  ↓ uniqBy('name')             — 去重(内置优先)
  = 最终工具池

8.6 子 Agent 工具限制

异步子 Agent 的工具集被严格限制(ASYNC_AGENT_ALLOWED_TOOLS,src/constants/tools.ts:55-71):

  • 只允许文件读写、搜索、Shell 等基础工具
  • 不允许 AgentTool(防止无限递归)
  • 不允许 AskUserQuestionTool(无交互环境)
  • 不允许 TaskStopTool 等管理工具

文件索引

文件行数职责
src/Tool.ts792核心类型定义、buildTool 工厂
src/tools.ts389工具注册、过滤、组装
src/constants/tools.ts112工具集合常量(子 Agent 限制等)
src/types/permissions.ts441权限类型定义
src/tools/BashTool/BashTool.tsx1144Bash 工具主实现
src/tools/BashTool/bashPermissions.ts2621Bash 权限系统
src/tools/FileReadTool/FileReadTool.ts1183文件读取工具
src/tools/GrepTool/GrepTool.ts577正则搜索工具
src/tools/WebFetchTool/WebFetchTool.ts318URL 内容获取
src/tools/MCPTool/MCPTool.ts77MCP 工具原型
src/tools/AgentTool/AgentTool.tsx1398子 Agent 调度