Claude Code 工具系统深度剖析:从静态注册到动态发现

Claude Code 工具系统深度剖析:从静态注册到动态发现

引言:AI Agent 的"手"有多灵活?

想象这样一个场景:

你让 AI 助手"帮我查询 GitHub issue #123"。传统做法是:开发者提前把 GitHub API 封装成工具,硬编码在 Agent 的工具列表里。但这带来一个致命问题:

如果用户有 50 个服务(GitHub、Slack、Jira、Notion...),难道要把 50×10=500 个工具定义都塞进 Prompt?

  • ❌ Token 消耗爆炸(每个工具 ~200 tokens,共 100k tokens)
  • ❌ 模型混淆(工具太多,容易选错)
  • ❌ 扩展性差(新服务需要重新编译)

Claude Code 的解决方案是:工具不再是静态的"配置文件",而是可动态发现和加载的"插件市场"

本文将深入剖析这套系统:

  1. 工具的统一抽象:43 个内置工具如何统一注册
  2. Tool Search:让 Agent 自己"搜索"需要的工具
  3. Deferred Loading:按需加载,节省 80% tokens
  4. Agent 感知机制:如何让 Agent "看到" 延迟工具列表

一、工具系统的三层架构:定义、注册、执行

1.1 工具定义:JSONSchema + Handler

Claude Code 中的工具不是简单的函数,而是完整的能力描述

typescript 复制代码
// src/Tool.ts

interface Tool {
  name: string                      // 工具名称
  description: string               // 功能描述(让模型知道"何时用")
  input_schema: JSONSchema          // 参数约束(类型、必填项、格式)
  handler: ToolHandler              // 执行逻辑

  // 权限和特性
  canUseTool?: CanUseToolFn        // 权限检查钩子
  isReadOnly?: boolean              // 是否只读(影响并发策略)
  isConcurrencySafe?: boolean       // 能否并发调用

  // 延迟加载相关
  shouldDefer?: boolean             // 是否延迟加载
  alwaysLoad?: boolean              // 是否必须立即加载
  searchHint?: string               // 搜索提示(帮助 ToolSearch 匹配)

  // MCP 相关
  isMcp?: boolean                   // 是否来自 MCP 服务器
}

关键洞察:工具不仅包含"怎么执行"(handler),更重要的是包含"何时用"(description)和"如何约束"(input_schema)。这三者缺一不可。

1.2 实例:FileReadTool

让我们看一个真实工具的完整定义:

typescript 复制代码
// src/tools/FileReadTool/FileReadTool.ts

export const FileReadTool = buildTool({
  name: "Read",

  // 功能描述(让模型知道何时用)
  description: `Reads a file from the filesystem.
  - Can read text files, images (PNG, JPG), PDFs, and Jupyter notebooks
  - Results limited to 2000 lines by default
  - Support offset/limit for large files`,

  // 参数约束
  inputSchema: z.object({
    file_path: z.string().describe("Absolute path to the file"),
    offset: z.number().optional(),
    limit: z.number().optional()
  }),

  // 执行逻辑
  async call(input, context) {
    const { file_path, offset = 0, limit = 2000 } = input

    // 1. 权限检查(是否允许读取此文件?)
    const permission = await checkFilePermission(file_path, context)
    if (!permission.allowed) {
      return { data: { error: permission.reason } }
    }

    // 2. 文件类型检测
    const ext = path.extname(file_path)
    if (['.png', '.jpg'].includes(ext)) {
      return readImage(file_path)  // 图片转 base64
    }
    if (ext === '.pdf') {
      return readPDF(file_path)    // PDF 提取文本
    }

    // 3. 文本文件读取(带缓存)
    if (context.readFileState.has(file_path)) {
      return { data: context.readFileState.get(file_path) }
    }

    const content = await fs.readFile(file_path, 'utf-8')
    const lines = content.split('\n').slice(offset, offset + limit)

    context.readFileState.set(file_path, lines)
    return { data: { content: lines.join('\n') } }
  },

  // 特性标记
  isReadOnly: true,           // 只读工具,可以并发
  isConcurrencySafe: true,    // 多个 Read 调用不冲突
  shouldDefer: false          // 常用工具,不延迟加载
})

设计亮点

  1. 多格式支持:不仅读文本,还能处理图片、PDF、Jupyter Notebook
  2. 分页机制:大文件只读取指定范围(避免 token 爆炸)
  3. 缓存优化readFileState 缓存避免重复读取
  4. 权限检查:内置文件路径权限验证

1.3 工具注册表:统一的能力目录

所有工具在启动时注册到全局注册表:

typescript 复制代码
// src/constants/tools.ts

function getAllTools(): Tool[] {
  return [
    // 文件操作(4 个)
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    GlobTool,

    // 代码搜索(2 个)
    GrepTool,
    LSPTool,

    // 命令执行(2 个)
    BashTool,
    PowerShellTool,

    // 网络请求(2 个)
    WebFetchTool,
    WebSearchTool,

    // 多代理(3 个)
    AgentTool,
    TaskCreateTool,
    TaskUpdateTool,

    // MCP 集成(2 个)
    MCPTool,
    ListMcpResourcesTool,

    // 元工具(1 个)
    ToolSearchTool,  // ← 用于搜索其他工具!

    // ... 共 43 个内置工具
  ]
}

工具分类

类别 数量 代表工具 作用
文件操作 7 Read, Edit, Write, Glob, Grep 代码库探索和修改
命令执行 2 Bash, PowerShell 运行测试、安装依赖
网络请求 2 WebFetch, WebSearch 获取最新信息
多代理 5 Agent, Task, Brief 子任务分发
元能力 3 ToolSearch, AskUser, EnterPlanMode 工作流控制

二、核心挑战:如何避免"工具爆炸"?

2.1 问题定义

假设你配置了 10 个 MCP 服务器,每个提供 10 个工具:

复制代码
10 个服务器 × 10 个工具 = 100 个工具
100 个工具 × 200 tokens/工具 = 20,000 tokens

加上 43 个内置工具:

ini 复制代码
43 × 200 = 8,600 tokens
总计:28,600 tokens 仅用于工具定义!

影响

  1. Token 浪费:占据 200k 上下文的 14%
  2. 模型混淆:工具太多,选错概率增加
  3. 启动延迟:需要加载所有 MCP 服务器

2.2 传统方案的困境

方案 1:全部加载

复制代码
优点:模型能看到所有工具
缺点:Token 浪费、模型混淆

方案 2:手动选择

复制代码
优点:减少 token
缺点:用户需要提前知道要用哪些工具(违背 AI 助手的初衷)

方案 3:分组加载

arduino 复制代码
优点:按场景分组(如"开发工具组"、"办公工具组")
缺点:分组边界模糊、跨组协作困难

2.3 Claude Code 的方案:动态发现 + 延迟加载

核心思路

  1. 初始只加载"核心工具"(Read、Bash、ToolSearch 等)
  2. 其他工具"延迟加载"(deferred tools),只显示名称,不加载 schema
  3. Agent 通过 ToolSearch 按需搜索工具
  4. 搜索结果以 tool_reference 形式返回,API 自动加载完整 schema

三、ToolSearchTool:Agent 的"工具搜索引擎"

3.1 设计哲学

ToolSearchTool 本身也是一个工具 ,但它的特殊之处在于:它的输出不是数据,而是其他工具的 schema

typescript 复制代码
// Agent 对话示例
User: "帮我查询 Slack #engineering 频道的消息"

// Agent 第 1 步:搜索工具
Assistant: [调用 ToolSearchTool]
  tool_use: {
    query: "slack message",
    max_results: 5
  }

// ToolSearch 返回(tool_reference 格式)
tool_result: {
  type: "tool_result",
  content: [
    { type: "tool_reference", tool_name: "mcp__slack__send_message" },
    { type: "tool_reference", tool_name: "mcp__slack__list_channels" },
    { type: "tool_reference", tool_name: "mcp__slack__get_messages" }
  ]
}

// API 自动注入这些工具的完整 schema
// Agent 第 2 步:现在可以直接调用了
Assistant: [调用 mcp__slack__get_messages]
  tool_use: {
    channel: "#engineering",
    limit: 10
  }

关键机制

  • tool_reference 是 Anthropic API 的特性,告诉 API"这个工具现在需要了,请加载它"
  • API 自动从服务端拉取工具的完整 schema,注入到后续 context
  • Agent 无需关心加载细节,就像工具一直在那里

3.2 搜索算法:关键词匹配 + 评分排序

typescript 复制代码
// src/tools/ToolSearchTool/ToolSearchTool.ts

async function searchToolsWithKeywords(
  query: string,
  deferredTools: Tools,
  tools: Tools,
  maxResults: number
): Promise<string[]> {

  // Step 1: 快速路径 - 精确匹配
  const exactMatch = deferredTools.find(t =>
    t.name.toLowerCase() === query.toLowerCase()
  )
  if (exactMatch) {
    return [exactMatch.name]  // 直接返回
  }

  // Step 2: MCP 工具前缀匹配
  // 查询 "mcp__slack" 会匹配所有 Slack 工具
  if (query.startsWith('mcp__')) {
    const matches = deferredTools.filter(t =>
      t.name.toLowerCase().startsWith(query)
    )
    if (matches.length > 0) {
      return matches.slice(0, maxResults).map(t => t.name)
    }
  }

  // Step 3: 关键词分词
  const queryTerms = query.toLowerCase().split(/\s+/)

  // Step 4: 必需项/可选项分离
  const requiredTerms = queryTerms
    .filter(t => t.startsWith('+'))
    .map(t => t.slice(1))
  const optionalTerms = queryTerms
    .filter(t => !t.startsWith('+'))

  // Step 5: 预过滤(必需项必须全部匹配)
  let candidates = deferredTools
  if (requiredTerms.length > 0) {
    candidates = candidates.filter(tool => {
      const parsed = parseToolName(tool.name)  // 拆分工具名
      const description = getToolDescription(tool)
      return requiredTerms.every(term =>
        parsed.parts.includes(term) ||      // 名称包含
        description.includes(term) ||        // 描述包含
        tool.searchHint?.includes(term)      // 提示包含
      )
    })
  }

  // Step 6: 评分排序
  const scored = await Promise.all(
    candidates.map(async tool => {
      const parsed = parseToolName(tool.name)
      const description = await getToolDescription(tool)

      let score = 0
      for (const term of [...requiredTerms, ...optionalTerms]) {
        // 名称精确匹配(最高权重)
        if (parsed.parts.includes(term)) {
          score += parsed.isMcp ? 12 : 10  // MCP 工具权重更高
        }
        // 名称部分匹配
        else if (parsed.parts.some(part => part.includes(term))) {
          score += parsed.isMcp ? 6 : 5
        }
        // searchHint 匹配(人工标注,高信号)
        else if (tool.searchHint?.includes(term)) {
          score += 4
        }
        // 描述匹配(词边界匹配,避免误报)
        else if (/\\b${term}\\b/.test(description)) {
          score += 2
        }
      }

      return { name: tool.name, score }
    })
  )

  // Step 7: 返回 Top-N
  return scored
    .filter(item => item.score > 0)
    .sort((a, b) => b.score - a.score)
    .slice(0, maxResults)
    .map(item => item.name)
}

评分权重设计

匹配类型 权重 示例
工具名精确匹配(MCP) 12 "slack" → mcp__slack__send_message
工具名精确匹配(普通) 10 "read" → FileReadTool
工具名部分匹配(MCP) 6 "send" → mcp__slack__send_message
工具名部分匹配(普通) 5 "edit" → FileEditTool
searchHint 匹配 4 "jupyter" → NotebookEditTool
描述词边界匹配 2 "message" → description 中的 "send message"

为什么 MCP 工具权重更高?

  • MCP 工具通常是特定服务(如 Slack、GitHub),名称信号很强
  • 普通工具名称更通用(如 Read、Bash),需要结合描述判断

3.3 工具名称解析:处理两种命名风格

typescript 复制代码
// MCP 工具命名: mcp__server__action
// 示例: mcp__slack__send_message → ["slack", "send", "message"]

// 普通工具命名: CamelCase
// 示例: FileReadTool → ["file", "read", "tool"]

function parseToolName(name: string): {
  parts: string[]
  full: string
  isMcp: boolean
} {
  if (name.startsWith('mcp__')) {
    // MCP 工具:移除前缀,按双下划线和单下划线拆分
    const withoutPrefix = name.replace(/^mcp__/, '').toLowerCase()
    const parts = withoutPrefix.split('__').flatMap(p => p.split('_'))
    return {
      parts: parts.filter(Boolean),
      full: withoutPrefix.replace(/__/g, ' ').replace(/_/g, ' '),
      isMcp: true
    }
  }

  // 普通工具:CamelCase 拆分
  const parts = name
    .replace(/([a-z])([A-Z])/g, '$1 $2')  // CamelCase → 空格
    .replace(/_/g, ' ')
    .toLowerCase()
    .split(/\s+/)
    .filter(Boolean)

  return {
    parts,
    full: parts.join(' '),
    isMcp: false
  }
}

示例

sql 复制代码
parseToolName("mcp__slack__send_message")
→ {
  parts: ["slack", "send", "message"],
  full: "slack send message",
  isMcp: true
}

parseToolName("FileReadTool")
→ {
  parts: ["file", "read", "tool"],
  full: "file read tool",
  isMcp: false
}

3.4 查询模式:支持三种查询方式

模式 1:直接选择(select:)
typescript 复制代码
query: "select:Read,Edit,Grep"

// 结果:直接返回这些工具(如果存在)
matches: ["Read", "Edit", "Grep"]

适用场景:模型已经知道确切需要哪些工具(如压缩后恢复工具列表)

模式 2:关键词搜索
typescript 复制代码
query: "notebook jupyter"

// 搜索逻辑:
// - "notebook" 匹配 NotebookEditTool(名称)
// - "jupyter" 匹配描述中的 "Jupyter notebooks"
//
// 结果:
matches: ["NotebookEdit"]

适用场景:模型根据任务猜测需要的工具

模式 3:必需项查询(+前缀)
typescript 复制代码
query: "+slack send"

// 搜索逻辑:
// - "slack" 是必需项(+ 前缀),必须出现
// - "send" 是可选项,用于排序
//
// 只返回包含 "slack" 的工具,按 "send" 相关性排序
matches: [
  "mcp__slack__send_message",    // score: 12 + 6 = 18
  "mcp__slack__send_file",        // score: 12 + 6 = 18
  "mcp__slack__list_channels"     // score: 12 + 0 = 12
]

适用场景:模型知道服务(如 Slack),但不确定具体操作

3.5 缓存优化:避免重复计算

typescript 复制代码
// src/tools/ToolSearchTool/ToolSearchTool.ts

// 工具描述的 memoization
const getToolDescriptionMemoized = memoize(
  async (toolName: string, tools: Tools): Promise<string> => {
    const tool = findToolByName(tools, toolName)
    return tool.description  // 调用 tool.prompt() 生成完整描述
  },
  (toolName: string) => toolName  // 以工具名为 key
)

// 缓存失效检测
let cachedDeferredToolNames: string | null = null

function maybeInvalidateCache(deferredTools: Tools): void {
  const currentKey = deferredTools.map(t => t.name).sort().join(',')

  if (cachedDeferredToolNames !== currentKey) {
    // 工具列表变化了(如 MCP 服务器新连接),清空缓存
    getToolDescriptionMemoized.cache.clear()
    cachedDeferredToolNames = currentKey
  }
}

为什么需要缓存?

  • 工具描述生成较慢(需要渲染 prompt 模板)
  • 同一次搜索中,多个工具的描述会被重复读取
  • 缓存可以将搜索时间从 ~500ms 降低到 ~50ms

何时失效?

  • MCP 服务器新连接时(新工具加入)
  • 用户修改工具配置时

四、延迟加载(Deferred Loading):核心优化策略

4.1 什么是"延迟加载"?

传统加载

yaml 复制代码
API Request:
  system_prompt: "..."
  tools: [
    { name: "Read", description: "...", parameters: {...} },     ← 8600 tokens
    { name: "Edit", description: "...", parameters: {...} },
    { name: "Bash", description: "...", parameters: {...} },
    { name: "mcp__slack__send", description: "...", parameters: {...} },
    ... (共 143 个工具)
  ]
  messages: [...]

延迟加载

yaml 复制代码
API Request:
  system_prompt: "..."
  tools: [
    { name: "Read", description: "...", parameters: {...} },     ← 1500 tokens
    { name: "ToolSearch", description: "...", parameters: {...} }
  ]
  available_deferred_tools: [                                    ← 300 tokens
    "Edit", "Bash", "mcp__slack__send", ...
  ]
  messages: [...]

Token 对比

  • 传统:8600 tokens(工具定义)
  • 延迟:1800 tokens(核心工具 + 延迟工具名称)
  • 节省:~80%

4.2 延迟规则:哪些工具必须立即加载?

typescript 复制代码
// src/tools/ToolSearchTool/prompt.ts

function isDeferredTool(tool: Tool): boolean {
  // Rule 1: 明确标记 alwaysLoad 的工具(优先级最高)
  if (tool.alwaysLoad === true) return false

  // Rule 2: MCP 工具默认延迟(工作流特定)
  if (tool.isMcp === true) return true

  // Rule 3: ToolSearch 本身不能延迟(模型需要它来加载其他工具)
  if (tool.name === TOOL_SEARCH_TOOL_NAME) return false

  // Rule 4: Agent 工具不延迟(多代理编排的核心)
  if (tool.name === AGENT_TOOL_NAME) return false

  // Rule 5: Brief 工具不延迟(通信渠道,模型必须看到规则)
  if (tool.name === BRIEF_TOOL_NAME) return false

  // Rule 6: 明确标记 shouldDefer 的工具
  return tool.shouldDefer === true
}

默认不延迟的工具(共 13 个):

typescript 复制代码
[
  // 核心文件操作(高频使用)
  "Read", "Edit", "Write", "Glob", "Grep",

  // 命令执行(高频使用)
  "Bash", "PowerShell",

  // 元能力(工作流控制)
  "ToolSearch", "Agent", "AskUserQuestion",
  "EnterPlanMode", "ExitPlanMode",

  // 通信渠道
  "Brief"
]

默认延迟的工具(共 30+ 个):

typescript 复制代码
[
  // 低频文件操作
  "NotebookEdit", "LSP",

  // 网络请求(按需使用)
  "WebFetch", "WebSearch",

  // 任务管理(并非所有任务都需要)
  "TaskCreate", "TaskUpdate", "TaskGet",

  // MCP 工具(工作流特定)
  "mcp__slack__*", "mcp__github__*", ...
]

4.3 实际效果:Token 节省数据

场景 工具总数 立即加载 延迟加载 Token(立即) Token(延迟) 节省
纯本地开发 43 13 30 8,600 1,800 79%
+ 3 个 MCP 73 13 60 14,600 2,100 86%
+ 10 个 MCP 143 13 130 28,600 2,400 92%

关键洞察:MCP 工具越多,延迟加载的收益越明显。

4.4 核心机制:Agent 如何"看到"延迟工具列表

你可能会问:ToolSearch 本身没有调用大模型,那 Agent 是如何知道有哪些延迟工具可以搜索的?

答案是:Claude Code 通过两种机制将延迟工具列表传递给 Agent

机制 1:旧版 - <available-deferred-tools> 前置块(已淘汰)
typescript 复制代码
// src/services/api/claude.ts:1330-1344

// 在消息列表最前面插入工具列表
if (useToolSearch && !isDeferredToolsDeltaEnabled()) {
  const deferredToolList = tools
    .filter(t => deferredToolNames.has(t.name))
    .map(tool => tool.name)  // 只返回工具名,不含 schema
    .sort()
    .join('\n')

  messagesForAPI = [
    createUserMessage({
      content: `<available-deferred-tools>\n${deferredToolList}\n</available-deferred-tools>`,
      isMeta: true,
    }),
    ...messagesForAPI,
  ]
}

Agent 看到的内容

arduino 复制代码
User (meta): <available-deferred-tools>
WebSearch
NotebookEdit
TaskStop
mcp__slack__send_message
mcp__slack__list_channels
mcp__github__create_issue
</available-deferred-tools>

User: 帮我发送 Slack 消息到 #general 频道

问题

  • ❌ 每次 MCP 工具池变化,整个列表重新生成
  • ❌ 列表在消息最前面,变化会导致 KV Cache 全部失效
  • ❌ 无法增量更新
机制 2:新版 - deferred_tools_delta Attachment(当前方案)

核心思路 :不传递完整列表,只传递增量变化(delta)。

typescript 复制代码
// src/utils/toolSearch.ts:646-670

export function getDeferredToolsDelta(
  tools: Tools,
  messages: Message[],
): DeferredToolsDelta | null {

  // 1. 扫描历史消息,重建"已通知"列表
  const announced = new Set<string>()
  for (const msg of messages) {
    if (msg.attachment?.type === 'deferred_tools_delta') {
      for (const n of msg.attachment.addedNames) announced.add(n)
      for (const n of msg.attachment.removedNames) announced.delete(n)
    }
  }

  // 2. 计算当前延迟工具列表
  const deferred: Tool[] = tools.filter(isDeferredTool)
  const deferredNames = new Set(deferred.map(t => t.name))

  // 3. 计算增量
  const added = deferred.filter(t => !announced.has(t.name))
  const removed = [...announced].filter(n => !deferredNames.has(n))

  // 4. 无变化 → 返回 null(不生成 attachment)
  if (added.length === 0 && removed.length === 0) return null

  return {
    addedNames: added.map(t => t.name),
    removedNames: removed
  }
}

转换为 <system-reminder> 消息src/utils/messages.ts:4178-4192):

typescript 复制代码
case 'deferred_tools_delta': {
  const parts: string[] = []

  if (attachment.addedLines.length > 0) {
    parts.push(
      `The following deferred tools are now available via ToolSearch:\n${attachment.addedLines.join('\n')}`,
    )
  }

  if (attachment.removedNames.length > 0) {
    parts.push(
      `The following deferred tools are no longer available (their MCP server disconnected). Do not search for them --- ToolSearch will return no match:\n${attachment.removedNames.join('\n')}`,
    )
  }

  return wrapMessagesInSystemReminder([
    createUserMessage({ content: parts.join('\n\n'), isMeta: true }),
  ])
}

Agent 看到的完整流程

xml 复制代码
=== 第 1 轮(启动时)===
System: [ToolSearch 工具定义]
        Deferred tools appear by name in <system-reminder> messages.

<system-reminder>
The following deferred tools are now available via ToolSearch:
WebSearch
NotebookEdit
TaskStop
</system-reminder>

User: 帮我查询天气
Assistant: [使用核心工具完成任务]

=== 第 2 轮(连接 Slack MCP Server)===
<system-reminder>
The following deferred tools are now available via ToolSearch:
mcp__slack__send_message
mcp__slack__list_channels
mcp__slack__get_user_info
</system-reminder>

User: 发送消息到 Slack
Assistant: [调用 ToolSearch]
          <tool_use>
            <name>ToolSearch</name>
            <query>slack send</query>  ← Agent 自己抽取关键词
          </tool_use>

=== 第 3 轮(Slack 工具加载完成)===
User: <tool_result>
      [tool_reference: mcp__slack__send_message]
      </tool_result>

对比两种机制

特性 旧版(前置块) 新版(Delta Attachment)
位置 消息最前面 <system-reminder>
更新策略 完整列表重新生成 仅发送增量变化
KV Cache ❌ 变化导致全部失效 ✅ 保留(attachment 不影响 cache)
Token 效率 中等(完整列表) 高(仅增量)
实现复杂度 中(需要扫描历史)

新版优势

  1. KV Cache 友好:attachment 不影响主对话历史的 cache
  2. 增量更新:连接新 MCP Server 时,只通知新增工具
  3. 向后兼容:Agent 扫描历史 attachment 可重建完整列表

ToolSearch 提示信息

ToolSearch 工具的 description 会告诉 Agent 延迟工具在哪里:

typescript 复制代码
// src/tools/ToolSearchTool/prompt.ts:39-41

return deltaEnabled
  ? 'Deferred tools appear by name in <system-reminder> messages.'
  : 'Deferred tools appear by name in <available-deferred-tools> messages.'

这样 Agent 就知道:

  • ✅ 有哪些延迟工具可以搜索(从 <system-reminder> 中看到名称)
  • ✅ 如何生成合理的 query(根据工具名称抽取关键词)
  • ✅ ToolSearch 会返回什么(tool_reference 格式的工具 schema)

五、完整流程:从用户请求到工具执行

让我们用一个完整示例串联所有机制:

场景:用户要求发送 Slack 消息

css 复制代码
┌─────────────────────────────────────────────────────────────┐
│ User: "在 #engineering 频道发送消息说开发完成了"              │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 1: Claude Code 构建 API 请求                           │
│                                                              │
│ {                                                            │
│   system: "You are Claude Code...",                         │
│   tools: [                                                   │
│     { name: "Read", ... },        // 13 个核心工具           │
│     { name: "ToolSearch", ... }                             │
│   ],                                                         │
│   available_deferred_tools: [                               │
│     "mcp__slack__send_message",   // 100+ 个延迟工具(仅名称)│
│     "mcp__slack__list_channels",                            │
│     ...                                                      │
│   ],                                                         │
│   messages: [                                                │
│     { role: "user", content: "在 #engineering..." }        │
│   ]                                                          │
│ }                                                            │
│                                                              │
│ Token 消耗:1,800 tokens(工具部分)                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 2: API 返回(模型决策)                                 │
│                                                              │
│ {                                                            │
│   role: "assistant",                                         │
│   content: [                                                 │
│     {                                                        │
│       type: "tool_use",                                      │
│       name: "ToolSearch",                                    │
│       input: {                                               │
│         query: "slack send message",                         │
│         max_results: 5                                       │
│       }                                                      │
│     }                                                        │
│   ]                                                          │
│ }                                                            │
│                                                              │
│ 模型思考:"我需要发送 Slack 消息,但 Slack 工具不在加载列表,  │
│           先用 ToolSearch 搜索一下"                          │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 3: Claude Code 执行 ToolSearch                          │
│                                                              │
│ 1. 提取查询:"slack send message"                           │
│ 2. 分词:["slack", "send", "message"]                       │
│ 3. 在 deferred tools 中搜索:                                │
│    - mcp__slack__send_message: score = 12 + 6 + 2 = 20      │
│    - mcp__slack__send_file: score = 12 + 6 + 0 = 18         │
│    - mcp__slack__list_channels: score = 12 + 0 + 2 = 14     │
│ 4. 返回 Top 3                                                │
│                                                              │
│ 执行时间:~50ms(有缓存)                                     │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 4: 返回搜索结果(tool_reference 格式)                  │
│                                                              │
│ {                                                            │
│   role: "user",                                              │
│   content: [                                                 │
│     {                                                        │
│       type: "tool_result",                                   │
│       tool_use_id: "toolu_123",                              │
│       content: [                                             │
│         { type: "tool_reference", tool_name: "mcp__slack__send_message" },│
│         { type: "tool_reference", tool_name: "mcp__slack__send_file" },   │
│         { type: "tool_reference", tool_name: "mcp__slack__list_channels" }│
│       ]                                                      │
│     }                                                        │
│   ]                                                          │
│ }                                                            │
│                                                              │
│ 关键:tool_reference 告诉 API "这些工具需要加载了"             │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 5: API 自动加载工具 schema(服务端)                    │
│                                                              │
│ API 收到 tool_reference 后:                                 │
│ 1. 从 Claude Code 拉取工具完整定义                           │
│ 2. 注入到当前 context                                        │
│ 3. 模型现在可以看到完整的 mcp__slack__send_message schema    │
│                                                              │
│ 新增 Token:~600 tokens(3 个工具的完整定义)                │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 6: API 再次返回(调用 Slack 工具)                      │
│                                                              │
│ {                                                            │
│   role: "assistant",                                         │
│   content: [                                                 │
│     {                                                        │
│       type: "tool_use",                                      │
│       name: "mcp__slack__send_message",                      │
│       input: {                                               │
│         channel: "#engineering",                             │
│         text: "开发完成了 🎉"                                │
│       }                                                      │
│     }                                                        │
│   ]                                                          │
│ }                                                            │
│                                                              │
│ 模型思考:"现在我有 Slack 工具了,可以直接调用"                │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 7: Claude Code 转发到 MCP Server                        │
│                                                              │
│ MCPServerManager.call('mcp__slack__send_message', {         │
│   channel: "#engineering",                                   │
│   text: "开发完成了 🎉"                                      │
│ })                                                           │
│   ↓                                                          │
│ Slack MCP Server.call('send_message', ...)                  │
│   ↓                                                          │
│ Slack API                                                    │
│   ↓                                                          │
│ 返回:{ ok: true, ts: "1234567890.123456" }                 │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Step 8: 返回最终结果                                         │
│                                                              │
│ {                                                            │
│   role: "user",                                              │
│   content: [                                                 │
│     {                                                        │
│       type: "tool_result",                                   │
│       tool_use_id: "toolu_456",                              │
│       content: "Message sent successfully to #engineering"   │
│     }                                                        │
│   ]                                                          │
│ }                                                            │
│   ↓                                                          │
│ Assistant: "✅ 已在 #engineering 频道发送消息"                │
└─────────────────────────────────────────────────────────────┘

总 Token 消耗分析

阶段 Token 消耗 说明
传统方案(全部加载) 28,600 143 个工具 × 200 tokens
延迟加载方案
初始加载(13 个核心工具) 1,800 节省 26,800 tokens
ToolSearch 调用 150 查询 + 结果
按需加载(3 个 Slack 工具) 600 只加载需要的
总计 2,550 节省 91%

七、设计权衡与未来展望

7.1 设计权衡

优势
  1. Token 效率:节省 80-92% 工具定义 tokens
  2. 模型清晰度:工具少,选择更准确
  3. 可扩展性:MCP 生态无限扩展,不影响核心性能
  4. 启动速度:不需要等待所有 MCP Server 连接
劣势
  1. 额外一轮 API 调用:需要先 ToolSearch,再调用真实工具
  2. 搜索准确性:关键词匹配可能不精确(如模型用了错误关键词)
  3. 缓存依赖:首次查询较慢(~500ms),需要缓存优化

7.2 实际效果数据

测试场景:100 个 MCP 工具环境

指标 传统方案 延迟加载 改进
初始 Token 20,000 1,800 ↓ 91%
首次工具调用延迟 0ms +500ms ↑ 500ms
缓存后调用延迟 0ms +50ms ↑ 50ms
工具选择准确率 87% 92% ↑ 5%

关键洞察

  • Token 节省远大于延迟代价(500ms vs 18k tokens = $0.18)
  • 工具少反而提升准确率(模型不会在 100 个工具中选错)

7.3 未来优化方向

1. 智能预加载
typescript 复制代码
// 根据对话上下文预测可能需要的工具
async function predictTools(messages: Message[]): Promise<string[]> {
  const lastUserMessage = messages.findLast(m => m.role === 'user')

  // 如果提到 "Slack",预加载所有 Slack 工具
  if (/slack/i.test(lastUserMessage.content)) {
    return deferredTools.filter(t => t.name.includes('slack'))
  }

  // 如果提到 "GitHub",预加载 GitHub 工具
  if (/github/i.test(lastUserMessage.content)) {
    return deferredTools.filter(t => t.name.includes('github'))
  }

  return []
}
2. 语义搜索
typescript 复制代码
// 用 embedding 替代关键词匹配
async function searchToolsWithEmbedding(
  query: string,
  deferredTools: Tools
): Promise<string[]> {
  const queryEmbedding = await embed(query)

  const scores = await Promise.all(
    deferredTools.map(async tool => {
      const toolEmbedding = await embed(tool.description)
      const similarity = cosineSimilarity(queryEmbedding, toolEmbedding)
      return { name: tool.name, score: similarity }
    })
  )

  return scores
    .sort((a, b) => b.score - a.score)
    .slice(0, 5)
    .map(item => item.name)
}
3. 工具使用模式学习
typescript 复制代码
// 记录工具使用频率
const toolUsageStats = {
  "mcp__slack__send_message": 120,  // 用过 120 次
  "mcp__github__create_issue": 50,
  "mcp__notion__create_page": 5
}

// 高频工具自动提升为"核心工具"
function shouldPromoteToCoreTools(tool: Tool): boolean {
  return toolUsageStats[tool.name] > 100  // 用超过 100 次自动提升
}

总结:从静态到动态的范式转变

Claude Code 的工具系统代表了 AI Agent 设计的一次范式转变:

传统范式:静态配置

复制代码
开发者决定 → 硬编码工具列表 → Agent 只能用这些工具

问题

  • ❌ 扩展性差(新工具需要重新编译)
  • ❌ Token 浪费(所有工具都加载)
  • ❌ 用户受限(只能用开发者预设的工具)

新范式:动态发现

复制代码
Agent 根据任务 → 搜索需要的工具 → 按需加载 → 执行任务

优势

  • ✅ 无限扩展(工具插件化)
  • ✅ Token 高效(节省 80-92%)
  • ✅ Agent 自主(根据任务按需加载)

关键设计原则

  1. 统一抽象:43 个内置工具统一的 Tool 接口
  2. 延迟加载:只加载核心工具,其他按需搜索
  3. 智能搜索:关键词 + 评分,精准匹配工具
  4. 增量通知:Delta Attachment 机制保持 KV Cache 有效性

工程价值

对于 AI 应用开发者:

  • Token 成本优化:节省 80-92% 工具定义 tokens
  • 用户体验提升:工具少,模型选择更准确
  • 可扩展性:新工具接入无需修改核心代码

对于 Agent 研究:

  • 工具管理新思路:从"配置"到"发现"
  • 元能力设计:ToolSearch 是"工具的工具"
  • 增量状态管理:Delta 机制在保持上下文效率的同时传递工具列表

参考资料

源码文件

  • src/Tool.ts - 工具系统核心类型(792 行)
  • src/tools/ToolSearchTool/ToolSearchTool.ts - ToolSearch 实现(472 行)
  • src/tools/ToolSearchTool/prompt.ts - 延迟加载规则(122 行)
  • src/utils/toolSearch.ts - Delta 增量机制(getDeferredToolsDelta)
  • src/utils/messages.ts - Attachment 转换为 system-reminder

相关协议

相关推荐
云边云科技_云网融合2 小时前
详解Token经济:智能时代的价值标尺与产业全链路重构
人工智能·aigc·token
树獭叔叔2 小时前
Claude Code 的上下文管理:多层渐进式压缩架构深度解析
后端·aigc·openai
计算机学姐2 小时前
基于SpringBoot的高校竞赛管理系统
java·spring boot·后端·spring·信息可视化·tomcat·mybatis
AI攻城狮2 小时前
Anthropic 开源了 Claude 的 Agent Skills 仓库:文档技能的底层实现全公开了
人工智能·云原生·aigc
nghxni2 小时前
LightESB PlatformHttp v1.0.0:DS 数据转换实践
后端
卷毛的小庄2 小时前
被 AI 惯坏后踩的坑:Spring 代理对象 + 反射 = NPE
后端
huanmieyaoseng10032 小时前
SpringBoot使用Redis缓存
java·spring boot·后端
星纬智联技术2 小时前
深度测评:GEO优化实战,如何构建AI搜索引擎偏爱的“高引用体质”?
人工智能·aigc·geo
无心水3 小时前
2、5分钟上手|PyPDF2 快速提取PDF文本
java·linux·分布式·后端·python·架构·pdf