# 深入拆解 Claude Code 源码(五):35 个内置工具如何让 AI “手眼通天“

深入拆解 Claude Code 源码(五):35 个内置工具如何让 AI “手眼通天”

系列:深入拆解 Claude Code 源码 | 第 5 篇 / 共 8 篇
关键词:Claude Code, 工具系统, buildTool, BashTool, FileEditTool, AgentTool, 并发安全


开篇:AI 的"手"和"眼"

一个 AI 助手能做什么,完全取决于它拥有什么工具。Claude Code 之所以能真正帮你写代码,而不是只给建议,就是因为它有 35+ 个内置工具,可以:

  • 读你的文件(FileRead)
  • 改你的代码(FileEdit, FileWrite)
  • 跑你的命令(Bash, PowerShell)
  • 搜索你的项目(Glob, Grep)
  • 派子 Agent 去干活(Agent)
  • 上网查资料(WebFetch, WebSearch)

这些工具不是随意堆砌的,它们遵循统一的架构模式。今天我们来拆解这个"瑞士军刀"的设计。


一、buildTool 模式 — 工厂函数如何统一 35 把刀

所有工具都通过 buildTool() 工厂函数构建。这不仅仅是创建对象那么简单——它定义了一套完整的契约,让每个工具都有统一的行为接口。

完整 Tool 接口(20+ 字段)

直接看源码 src/Tool.ts,这个接口可不简单,分为 7 大类:

// src/Tool.ts — 精简展示核心字段
export type Tool<Input, Output, P> = {
  // ① 基本信息
  name: string; aliases?: string[]; searchHint?: string
  inputSchema: Input; outputSchema?: z.ZodType<unknown>
  maxResultSizeChars: number; strict?: boolean

  // ② 核心行为
  description(input, options): Promise<string>
  prompt(options): Promise<string>
  call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult<Output>>

  // ③ 验证与权限
  validateInput?(input, context): Promise<ValidationResult>
  checkPermissions(input, context): Promise<PermissionResult>
  preparePermissionMatcher?(input): Promise<(pattern: string) => boolean>

  // ④ 标记与分类(7 个布尔标记)
  isEnabled(): boolean; isConcurrencySafe(input): boolean; isReadOnly(input): boolean
  isDestructive?(input): boolean; isMcp?: boolean; shouldDefer?: boolean; alwaysLoad?: boolean

  // ⑤ 中断与交互
  interruptBehavior?(): 'cancel' | 'block'
  isSearchOrReadCommand?(input): { isSearch: boolean; isRead: boolean; isList?: boolean }

  // ⑥ UI 渲染(8 个渲染方法)
  renderToolUseMessage; renderToolResultMessage?; renderToolUseProgressMessage?
  renderToolUseRejectedMessage?; renderToolUseErrorMessage?; renderToolUseTag?
  renderToolUseQueuedMessage?; renderGroupedToolUse?
  isResultTruncated?(output): boolean

  // ⑦ 数据转换
  mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
  toAutoClassifierInput(input): unknown
  getPath?(input): string
  getActivityDescription?(input): string | null
  getToolUseSummary?(input): string | null
  extractSearchText?(out): string
  mcpInfo?: { serverName: string; toolName: string }
  backfillObservableInput?(input: Record<string, unknown>): void
}

30+ 个方法/属性,覆盖 UI 渲染、权限控制、并发调度、自动分类、安全审计——每个工具都"承诺"实现了这些能力。

buildTool() 工厂如何工作

看实际实现:

// src/Tool.ts
const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?) => false,    // 默认不安全!保守策略
  isReadOnly: (_input?) => false,           // 默认假设有写操作
  isDestructive: (_input?) => false,
  checkPermissions: (input, _ctx?) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
  toAutoClassifierInput: (_input?) => '',   // 默认跳过分类器
  userFacingName: (_input?) => '',
}

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,           // 先铺默认值
    userFacingName: () => def.name,  // 默认用工具名
    ...def,                     // 再覆盖自定义部分
  } as BuiltTool<D>
}

关键设计:默认值采用"失败即关闭"策略isConcurrencySafe 默认 false(假定不安全),isReadOnly 默认 false(假定有写操作)。只有只读工具(如 FileReadTool)会显式设置 isConcurrencySafe: () => true

构建示例 — FileReadTool

const FileReadTool = buildTool({
  name: 'Read',
  searchHint: 'read file contents',   // ToolSearch 关键词
  description: 'Read a file from the filesystem',
  inputSchema: z.object({
    file_path: z.string().describe('Absolute path to the file'),
    offset: z.number().optional().describe('Starting line number'),
    limit: z.number().optional().describe('Number of lines to read'),
    pages: z.string().optional().describe('PDF page range'),
  }),
  isConcurrencySafe: () => true,   // 只读,并发安全
  isReadOnly: () => true,
  maxResultSizeChars: Infinity,     // Read 工具自己限制,不持久化
  toAutoClassifierInput: () => '',  // 文件读取对安全分类无意义
  getPath(input) { return input.file_path },
  getActivityDescription(input) {
    return `Reading ${path.basename(input.file_path)}`
  },
  // ...
})

二、文件操作工具族 — 四把"手术刀"

2.1 FileReadTool — 文件读取(最精细的读取器)

属性
并发安全
只读
最大结果Infinity(Read 工具自行限制)
延迟加载

输入 Schema:

z.object({
  file_path: z.string(),        // 绝对路径
  offset: z.number().optional(), // 起始行号
  limit: z.number().optional(),  // 读取行数
  pages: z.string().optional(),  // PDF 页码范围(如 "1-5", "3", "10-20")
})

PDF 支持

import { PDF_EXTRACT_SIZE_THRESHOLD, PDF_MAX_PAGES_PER_READ } from '../../constants/apiLimits.js'
import { extractPDFPages, getPDFPageCount, readPDF } from '../../utils/pdf.js'

处理流程:先 isPDFSupported() 检查,再 parsePDFPageRange() 解析页码,超阈值时分页提取。每次最多读 PDF_MAX_PAGES_PER_READ 页。

图片处理(多模态核心):

import {
  compressImageBufferWithTokenLimit,   // 按 token 限制压缩
  detectImageFormatFromBuffer,         // 检测格式
  maybeResizeAndDownsampleImageBuffer, // 智能缩放
} from '../../utils/imageResizer.js'

读取图片时自动检测格式、缩放尺寸、压缩到 token 限制内,以多模态格式返回。

Token 估算:两种策略并用——roughTokenCountEstimationForFileType() 按扩展名粗估(如 .py 约 3.5 字符/token),countTokensWithAPI() 在关键场景精确计数。

文件去重缓存:当同一文件、同样 offset/limit 被二次读取时,返回 FILE_UNCHANGED_STUB 标记而非重传内容,节省大量 token。

设备文件保护/dev/zero, /dev/random, /dev/stdin 等路径直接拒绝,避免进程挂起。

Read-before-Edit 追踪:读取记录存入 readFileState(FileStateCache),FileEditTool 执行前检查——没读过就拒绝编辑。

技能目录发现:读取文件时顺带扫描 discoverSkillDirsForPaths(),实现"边读边发现"。


2.2 FileEditTool — 文件编辑(精确字符串替换)

属性
并发安全
只读
最大结果100,000 字符
严格模式

输入 Schema

z.strictObject({
  file_path: z.string(),         // 绝对路径
  old_string: z.string(),        // 被替换的文本
  new_string: z.string(),        // 新文本
  replace_all: z.boolean().optional(),  // 全局替换
})

核心机制:精确字符串替换(非正则)

注意,这里不是正则替换,而是精确的字符串匹配。源码里用 findActualString() 处理引号风格差异:

// 处理引号风格差异(单引号 ↔ 双引号自动兼容)
const actualOldString = findActualString(file, old_string)
if (!actualOldString) {
  return { result: false, message: '...', errorCode: 8 }
}

安全检查流水线(10 步验证,这是重点):

async validateInput(input, toolUseContext) {
  const fullFilePath = expandPath(file_path)
  // ① 密钥泄露检查 → ② 空变更检查 → ③ 权限拒绝规则
  // ④ UNC 路径安全(见下文) → ⑤ 文件大小限制(1 GiB)
  // ⑥ .ipynb 拒绝 → ⑦ Read-before-Edit → ⑧ 修改时间检查
  // ⑨ old_string 存在性 → ⑩ new_string 重复检查
}

UNC 路径安全检查(一个很细节的安全防护):

// SECURITY: Skip filesystem operations for UNC paths to prevent NTLM credential leaks.
// On Windows, fs.existsSync() on UNC paths triggers SMB authentication which could
// leak credentials to malicious servers.
if (fullFilePath.startsWith('\\\\') || fullFilePath.startsWith('//')) {
  return { result: true }
}

Windows 上的 UNC 路径(如 \\evil-server\share)如果调用 fs.existsSync(),会触发 SMB 认证,可能泄露 NTLM 凭证给恶意服务器。所以直接跳过文件系统操作。

编辑完成后的副作用链

// 编辑成功后依次执行:
checkTeamMemSecrets(...)           // ① 密钥检查(再次确认)
fileHistoryTrackEdit(...)          // ② 文件历史追踪
diagnosticTracker.clearForFile(...) // ③ 诊断追踪(LSP 集成)
notifyVscodeFileUpdated(...)       // ④ VSCode 通知
activateConditionalSkillsForPaths(...)  // ⑤ 技能激活
fetchSingleFileGitDiff(...)        // ⑥ Git diff 获取

2.3 FileWriteTool — 文件写入(创建或完全覆盖)

属性
并发安全
只读
最大结果100,000 字符
严格模式

输入 Schema

z.strictObject({
  file_path: z.string(),    // 绝对路径
  content: z.string(),      // 文件内容
})

输出结构(比你想象的丰富):

z.object({
  type: z.enum(['create', 'update']),  // 新建还是覆盖
  filePath: z.string(),                 // 写入的文件路径
  content: z.string(),                  // 写入的内容
  structuredPatch: z.array(hunkSchema), // diff 补丁
  originalFile: z.string().nullable(),  // 原文件内容(新建时为 null)
  gitDiff: gitDiffSchema().optional(),  // Git diff 信息
})

输出包含 type 字段区分新建和更新,以及完整的 structuredPatchoriginalFile——这让 UI 层能显示精确的 diff 视图。

安全检查:与 FileEditTool 类似,包含密钥检查、权限检查、Read-before-Edit 检查(覆盖现有文件时)。新增文件不需要先读取。


2.4 NotebookEditTool — Jupyter Notebook 编辑

属性
并发安全
只读
最大结果100,000 字符
延迟加载是(shouldDefer: true

输入 Schema

z.strictObject({
  notebook_path: z.string(),        // 绝对路径(.ipynb)
  cell_id: z.string().optional(),   // 单元格 ID
  new_source: z.string(),           // 新源码
  cell_type: z.enum(['code', 'markdown']).optional(),
  edit_mode: z.enum(['replace', 'insert', 'delete']).optional(),
})

单元格 ID 查找逻辑(两级查找):

// 先按实际 ID 查找,再按 cell-N 格式索引
function parseCellId(notebook: NotebookContent, cellId: string) {
  // 第一级:精确 ID 匹配
  const byId = notebook.cells.find(c => c.id === cellId)
  if (byId) return byId

  // 第二级:cell-N 格式索引(cell-0, cell-1, ...)
  const match = cellId.match(/^cell-(\d+)$/)
  if (match) {
    const index = parseInt(match[1], 10)
    if (index < notebook.cells.length) {
      return notebook.cells[index]
    }
  }
  return null
}

其他细节

  • 代码单元格修改时自动重置 execution_countoutputs 为空
  • nbformat 4.5+ 自动生成新的 cell ID(使用 UUID)
  • replace 模式越界时自动转为 insert
  • JSON 写入使用 1 空格缩进(标准 .ipynb 格式)
  • 输出包含 original_fileupdated_file 用于 diff 显示

三、搜索与发现工具

GlobTool — 文件模式匹配

input: {
  pattern: string    // 如 "**/*.ts"
  path?: string      // 搜索目录
}

并发安全、只读、最大 100 文件结果、按修改时间排序。当 ant 版本有内嵌搜索工具(hasEmbeddedSearchTools())时不注册。

GrepTool — 内容搜索

使用 ripgrep 引擎,支持正则、文件类型过滤、多行匹配、上下文行数控制、分页偏移。完整参数:

input: {
  pattern: string,     // 正则表达式
  path?: string,       // 搜索路径
  glob?: string,       // 文件过滤 glob
  output_mode: 'content' | 'files_with_matches' | 'count',
  -B?, -A?, -C?: number,  // 上下文行数
  -n?: boolean,        // 行号
  -i?: boolean,        // 大小写不敏感
  -o?: boolean,        // 仅匹配部分
  type?: string,       // 文件类型
  head_limit?: number, // 结果限制
  offset?: number,     // 分页偏移
  multiline?: boolean, // 多行匹配
}

ToolSearchTool — 工具搜索(延迟加载的"开关")

input: {
  query: string,          // 查询字符串(支持 select:<name> 直接选择)
  max_results?: number,   // 最大结果数(默认 5)
}

当工具集过大时,部分工具标记 shouldDefer: true 不会出现在初始 prompt 中。模型需要用 ToolSearchTool 搜索并激活它们。基于关键词评分匹配,select:<name> 前缀可直接选择。


四、Shell 执行工具 — 最复杂的两个工具

4.1 BashTool — Bash 命令执行

这是 Claude Code 中最复杂的工具之一,涉及安全解析(bash AST)、沙箱(SandboxManager)、Git 追踪、图片输出检测、任务系统等子系统。

输入 Schema

z.object({
  command: z.string(),                              // Bash 命令
  timeout: z.number().optional(),                   // 超时毫秒(最大 600,000)
  description: z.string().optional(),               // 命令描述
  run_in_background: z.boolean().optional(),        // 后台运行
  dangerouslyDisableSandbox: z.boolean().optional(), // 禁用沙箱
})

五套命令分类系统

// 搜索类(UI 可折叠显示)
const BASH_SEARCH_COMMANDS = new Set([
  'find', 'grep', 'rg', 'ag', 'ack', 'locate', 'which', 'whereis'
])

// 读取类
const BASH_READ_COMMANDS = new Set([
  'cat', 'head', 'tail', 'less', 'more',
  // 分析命令
  'wc', 'stat', 'file', 'strings',
  // 数据处理
  'jq', 'awk', 'cut', 'sort', 'uniq', 'tr'
])

// 目录列表类(从 READ 分出来,UI 显示 "Listed N directories" 而非 "Read N files")
const BASH_LIST_COMMANDS = new Set(['ls', 'tree', 'du'])

// 语义中性命令(管道中可跳过,不影响整体分类)
const BASH_SEMANTIC_NEUTRAL_COMMANDS = new Set([
  'echo', 'printf', 'true', 'false', ':'  // bash no-op
])

// 静默类(成功时通常无 stdout)
const BASH_SILENT_COMMANDS = new Set([
  'mv', 'cp', 'rm', 'mkdir', 'rmdir', 'chmod',
  'chown', 'chgrp', 'touch', 'ln', 'cd', 'export', 'unset', 'wait'
])

命令分类逻辑(核心函数):

export function isSearchOrReadBashCommand(command: string): {
  isSearch: boolean; isRead: boolean; isList: boolean
} {
  // 对于管道命令(如 `cat file | grep pattern`),ALL 部分都必须是搜索/读取
  // 语义中性命令(echo, printf, true, false, :) 在任何位置都跳过
  // 例如 `ls dir && echo "---" && ls dir2` 仍被分类为只读
}

时间常量

const PROGRESS_THRESHOLD_MS = 2000       // 2 秒后显示进度
const ASSISTANT_BLOCKING_BUDGET_MS = 15_000  // 15 秒后自动后台化

其他特性

  • 沙箱支持SandboxManager 提供沙箱隔离,shouldUseSandbox.ts 决定是否启用,dangerouslyDisableSandbox 可强制禁用
  • Git 操作追踪trackGitOperations() 检测 commit/push/cherry-pick/merge/rebase 和 gh pr 操作,用于指标统计
  • 图片输出检测isImageOutput() 检测命令输出图片(如 matplotlib),自动 resizeShellImageOutput() 并返回多模态结果

4.2 PowerShellTool — PowerShell 命令执行

Windows 专用,门控:isPowerShellToolEnabled()

命令分类集合

const PS_SEARCH_COMMANDS = new Set([
  'Select-String', 'Get-ChildItem', 'FindStr', 'Where.exe'
])

const PS_READ_COMMANDS = new Set([
  'Get-Content', 'Get-Item', 'Test-Path', 'Resolve-Path',
  'Get-Process', 'Get-Service', 'Get-ChildItem'
])

Windows PowerShell 5.1 兼容:Claude Code 必须同时兼容 Windows PowerShell 5.1 和 PowerShell 7+,源码中大量使用兼容性处理。

安全机制isReadOnlyCommand() 只读验证、hasSyncSecurityConcerns() 安全检测、resolveToCanonical() 命令规范化(别名转完整命令)、interpretCommandResult() 结果解释。共享 BashTool 的沙箱、UI 和工具结果存储逻辑。


五、Agent 与多 Agent 工具 — 最依赖的工具

5.1 AgentTool — Agent 执行

这是 Claude Code 中依赖最多的工具(约 45 个依赖项),横跨任务系统、工具池、worktree、远程执行、系统提示词等子系统。

双重 Schema 设计(baseInputSchema + fullInputSchema):

// 基础 schema(5 个字段)
const baseInputSchema = lazySchema(() => z.object({
  description: z.string().describe('A short (3-5 word) description'),
  prompt: z.string().describe('The task for the agent to perform'),
  subagent_type: z.string().optional(),
  model: z.enum(['sonnet', 'opus', 'haiku']).optional(),
  run_in_background: z.boolean().optional(),
}))

// 完整 schema(基础 + 多 agent + 隔离,共 10 个字段)
const fullInputSchema = lazySchema(() => {
  const multiAgentInputSchema = z.object({
    name: z.string().optional(),        // agent 名称(可通过 SendMessage 寻址)
    team_name: z.string().optional(),   // 团队名称
    mode: permissionModeSchema().optional(), // 权限模式覆盖
  })
  return baseInputSchema().merge(multiAgentInputSchema).extend({
    isolation: z.enum(['worktree', 'remote']).optional(),
    cwd: z.string().optional(),         // 工作目录覆盖
  })
})

120 秒自动后台化

function getAutoBackgroundMs(): number {
  if (isEnvTruthy(process.env.CLAUDE_AUTO_BACKGROUND_TASKS) ||
      getFeatureValue_CACHED_MAY_BE_STALE('tengu_auto_background_agents', false)) {
    return 120_000  // 2 分钟后自动转为后台
  }
  return 0  // 禁用
}

进度阈值

const PROGRESS_THRESHOLD_MS = 2000  // 2 秒后显示后台提示

5.2 SendMessageTool — 多 Agent 消息

input: {
  to: string,              // 接收者标识
  message: string | object, // 消息(支持结构化 JSON)
  summary?: string,        // UI 预览摘要
}

接收者类型(四种寻址方式):

to 值含义
"teammate_name"发送给指定队友
"*"广播给所有队友
"uds:<path>"通过 Unix Domain Socket 发送给对等进程
"bridge:<id>"通过 bridge 发送给远程实例

消息支持结构化 JSON,包括 shutdown_requestshutdown_responseplan_approval_response 等内部协议。


5.3 TeamCreateTool / TeamDeleteTool

// TeamCreateTool
input: {
  team_name: string,           // 团队名称
  description?: string,        // 团队描述
  agent_type?: string,         // agent 类型
}
// 通过 generateWordSlug() 生成唯一团队名称

// TeamDeleteTool
input: {}  // 空!使用 AppState 中的当前团队上下文
// 清理 ~/.claude/teams/ 和 ~/.claude/tasks/ 目录
// 仅在所有队友关闭后可用

门控:isAgentSwarmsEnabled()


六、工具注册与过滤 — 一把一把地选刀

6.1 getAllBaseTools() — 完整工具清单

src/tools.ts 中的 getAllBaseTools() 是所有工具的"注册表",分三大类注册:

export function getAllBaseTools(): Tools {
  return [
    // ─── 始终注册的核心工具(15 个)───
    AgentTool, TaskOutputTool, BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    ExitPlanModeV2Tool, FileReadTool, FileEditTool, FileWriteTool,
    NotebookEditTool, WebFetchTool, TodoWriteTool, WebSearchTool,
    TaskStopTool, AskUserQuestionTool, SkillTool, EnterPlanModeTool,
    getSendMessageTool(), BriefTool,

    // ─── 条件编译工具(feature flag 门控)───
    ...(process.env.USER_TYPE === 'ant' ? [ConfigTool, TungstenTool] : []),
    ...(isTodoV2Enabled() ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] : []),
    ...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
    ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
    ...(isAgentSwarmsEnabled() ? [getTeamCreateTool(), getTeamDeleteTool()] : []),
    ...(process.env.USER_TYPE === 'ant' && REPLTool ? [REPLTool] : []),
    ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
    ...cronTools,   // AGENT_TRIGGERS feature flag

    // ─── 实验性/平台工具 ───
    ...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []),
    ...(WebBrowserTool ? [WebBrowserTool] : []),
    ...(OverflowTestTool ? [OverflowTestTool] : []),
    ...(CtxInspectTool ? [CtxInspectTool] : []),
    ...(TerminalCaptureTool ? [TerminalCaptureTool] : []),
    ...(WorkflowTool ? [WorkflowTool] : []),
    ...(SleepTool ? [SleepTool] : []),
    ...(RemoteTriggerTool ? [RemoteTriggerTool] : []),
    ...(MonitorTool ? [MonitorTool] : []),
    ...(SendUserFileTool ? [SendUserFileTool] : []),
    ...(PushNotificationTool ? [PushNotificationTool] : []),
    ...(SubscribePRTool ? [SubscribePRTool] : []),
    ...(getPowerShellTool() ? [getPowerShellTool()] : []),
    ...(SnipTool ? [SnipTool] : []),
    ...(ListPeersTool ? [ListPeersTool] : []),
    ...(VerifyPlanExecutionTool ? [VerifyPlanExecutionTool] : []),
    ...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []),
    ListMcpResourcesTool, ReadMcpResourceTool,
  ]
}

注意 TeamCreateTool 和 TeamDeleteTool 使用了 lazy require 来打破循环依赖:

// Lazy require to break circular dependency
const getTeamCreateTool = () =>
  require('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool
const getTeamDeleteTool = () =>
  require('./tools/TeamDeleteTool/TeamDeleteTool.js').TeamDeleteTool
const getSendMessageTool = () =>
  require('./tools/SendMessageTool/SendMessageTool.js').SendMessageTool

6.2 getTools() — 权限过滤

export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  // SIMPLE 模式:只有 Bash, Read, Edit
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    // REPL 模式下用 REPLTool 包装原始工具
    if (isReplModeEnabled() && REPLTool) {
      return filterToolsByDenyRules([REPLTool], permissionContext)
    }
    return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], permissionContext)
  }

  const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
  let allowedTools = filterToolsByDenyRules(tools, permissionContext)

  // REPL 模式下隐藏原始工具(它们在 REPL VM 内部可用)
  if (isReplModeEnabled()) {
    allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
  }

  return allowedTools.filter((_, i) => isEnabled[i])
}

6.3 assembleToolPool() — 合并策略

export function assembleToolPool(
  permissionContext: ToolPermissionContext,
  mcpTools: Tools,
): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)

  // 各自排序以保证 prompt-cache 稳定性
  // 内置工具作为前缀,MCP 工具追加
  // uniqBy 保证内置工具在名称冲突时优先
  const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}

这里有个很巧的设计:内置工具和 MCP 工具各自按名称排序后拼接,但 内置工具永远在前面。这样 claude_code_system_cache_policy 的缓存断点就能稳定命中,不会因为 MCP 工具插入而失效。

6.4 filterToolsByDenyRules() — MCP 前缀支持

支持 MCP 服务器前缀规则——比如 mcp__server 规则会屏蔽该服务器的所有工具,模型根本看不到它们。与运行时权限检查使用同一匹配器。


七、并发控制 — 谁能一起跑,谁必须排队

7.1 partitionToolCalls() — 分区逻辑

src/services/tools/toolOrchestration.ts 负责并发调度:

async function* runTools(toolCalls, context) {
  // 1. 分区:并发安全 vs 必须串行
  const { concurrent, serial } = partitionToolCalls(toolCalls)

  // 2. 并发执行安全批次
  if (concurrent.length > 0) {
    yield* runToolsConcurrently(concurrent, context)
  }

  // 3. 串行执行敏感操作
  for (const call of serial) {
    yield* runToolsSerially([call], context)
  }
}

并发安全判断

  • isConcurrencySafe() === true:Read, Glob, Grep, WebFetch 等只读操作
  • isConcurrencySafe() === false:Bash, FileEdit, FileWrite 等写操作

7.2 最大并发数

// 环境变量控制,默认 10
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY = 10

7.3 contextModifier 排队

非并发安全的工具可返回 contextModifier 修改后续工具的上下文,这些修改器必须串行执行,不能并行。


八、工具权限集常量 — 5 个安全"围栏"

src/constants/tools.ts 定义了 5 个关键的工具权限集合,控制不同场景下哪些工具可用:

ALL_AGENT_DISALLOWED_TOOLS — 所有 Agent 禁用工具

export const ALL_AGENT_DISALLOWED_TOOLS = new Set([
  TASK_OUTPUT_TOOL_NAME,        // 防止递归
  EXIT_PLAN_MODE_V2_TOOL_NAME,  // 计划模式是主线程抽象
  ENTER_PLAN_MODE_TOOL_NAME,
  // 非 ant 用户禁止嵌套 Agent
  ...(process.env.USER_TYPE === 'ant' ? [] : [AGENT_TOOL_NAME]),
  ASK_USER_QUESTION_TOOL_NAME,  // Agent 不能直接问用户
  TASK_STOP_TOOL_NAME,          // 需要主线程任务状态
  // 防止递归工作流
  ...(feature('WORKFLOW_SCRIPTS') ? [WORKFLOW_TOOL_NAME] : []),
])

CUSTOM_AGENT_DISALLOWED_TOOLS — 自定义 Agent 禁用工具

export const CUSTOM_AGENT_DISALLOWED_TOOLS = new Set([
  ...ALL_AGENT_DISALLOWED_TOOLS,  // 继承全部
  // 目前没有额外限制,但预留了扩展点
])

ASYNC_AGENT_ALLOWED_TOOLS — 异步 Agent 允许工具(白名单)

export const ASYNC_AGENT_ALLOWED_TOOLS = new Set([
  FILE_READ_TOOL_NAME,
  WEB_SEARCH_TOOL_NAME,
  TODO_WRITE_TOOL_NAME,
  GREP_TOOL_NAME,
  WEB_FETCH_TOOL_NAME,
  GLOB_TOOL_NAME,
  ...SHELL_TOOL_NAMES,            // Bash, PowerShell
  FILE_EDIT_TOOL_NAME,
  FILE_WRITE_TOOL_NAME,
  NOTEBOOK_EDIT_TOOL_NAME,
  SKILL_TOOL_NAME,
  SYNTHETIC_OUTPUT_TOOL_NAME,
  TOOL_SEARCH_TOOL_NAME,
  ENTER_WORKTREE_TOOL_NAME,
  EXIT_WORKTREE_TOOL_NAME,
])

IN_PROCESS_TEAMMATE_ALLOWED_TOOLS — 进程内队友额外工具

export const IN_PROCESS_TEAMMATE_ALLOWED_TOOLS = new Set([
  TASK_CREATE_TOOL_NAME,
  TASK_GET_TOOL_NAME,
  TASK_LIST_TOOL_NAME,
  TASK_UPDATE_TOOL_NAME,
  SEND_MESSAGE_TOOL_NAME,
  // 定时任务(带 agentId 标记,路由到队友的消息队列)
  ...(feature('AGENT_TRIGGERS')
    ? [CRON_CREATE_TOOL_NAME, CRON_DELETE_TOOL_NAME, CRON_LIST_TOOL_NAME]
    : []),
])

COORDINATOR_MODE_ALLOWED_TOOLS — 协调者模式工具

export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
  AGENT_TOOL_NAME,           // 只能派活
  TASK_STOP_TOOL_NAME,       // 停止任务
  SEND_MESSAGE_TOOL_NAME,    // 发消息
  SYNTHETIC_OUTPUT_TOOL_NAME, // 结构化输出
])

协调者模式下,主 Agent 只有 4 个工具——它不干活,只指挥。


九、条件加载汇总

工具启用条件
GlobTool, GrepTool!hasEmbeddedSearchTools()
TaskCreate/Get/Update/ListisTodoV2Enabled()
EnterWorktree, ExitWorktreeisWorktreeModeEnabled()
TeamCreate, TeamDeleteisAgentSwarmsEnabled()
PowerShellToolisPowerShellToolEnabled()
LSPToolENABLE_LSP_TOOL 环境变量
ToolSearchToolisToolSearchEnabledOptimistic()
CronCreate/Delete/ListAGENT_TRIGGERS feature flag
RemoteTriggerToolAGENT_TRIGGERS_REMOTE feature flag
BriefToolKAIROS / KAIROS_BRIEF feature flag
SleepToolPROACTIVE / KAIROS feature flag
MonitorToolMONITOR_TOOL feature flag
ConfigTool, TungstenTool, REPLToolUSER_TYPE === 'ant'
WebBrowserToolWEB_BROWSER_TOOL feature flag
SyntheticOutputTool非交互会话
TestingPermissionToolNODE_ENV === 'test'

十、设计亮点总结

设计决策价值
buildTool() 工厂 + 默认值统一接口,新工具只需覆盖差异部分
默认不安全(fail-closed)新工具不会意外获得并发安全等权限
五套命令分类系统BashTool 根据命令类型智能调整 UI 展示
UNC 路径安全检查防止 Windows 上 NTLM 凭证泄露
Read-before-Edit 强制防止 AI 盲目编辑未读文件
120s 自动后台化长时间 Agent 不阻塞用户交互
权限集常量协调者/异步 Agent/自定义 Agent 各有边界
延迟 require 打破循环工具间复杂依赖关系优雅解决
prompt-cache 稳定排序内置+MCP 工具合并不影响缓存命中
条件编译(feature()编译时死代码消除,不同版本有不同工具集

下篇预告

第六篇:20 个后端服务的微服务架构

工具是 AI 的"手脚",服务层就是 AI 的"后勤保障"。Claude Code 的 services/ 目录包含了 20+ 个子系统:API 通信、MCP 集成、OAuth 认证、推测执行、团队记忆同步、自动梦境……

下一篇,我们将深入服务层,看看这些"幕后英雄"是如何支撑整个系统的。


标签: Claude Code 工具系统 buildTool BashTool AgentTool 并发安全 源码分析 TypeScript

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值