云上绿洽会
85.91M · 2026-04-12
想象这样一个场景:
你让 AI 助手"帮我查询 GitHub issue #123"。传统做法是:开发者提前把 GitHub API 封装成工具,硬编码在 Agent 的工具列表里。但这带来一个致命问题:
如果用户有 50 个服务(GitHub、Slack、Jira、Notion...),难道要把 50×10=500 个工具定义都塞进 Prompt?
Claude Code 的解决方案是:工具不再是静态的"配置文件",而是可动态发现和加载的"插件市场"。
本文将深入剖析这套系统:
Claude Code 中的工具不是简单的函数,而是完整的能力描述:
// 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)。这三者缺一不可。
让我们看一个真实工具的完整定义:
// 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 // 常用工具,不延迟加载
})
设计亮点:
readFileState 缓存避免重复读取所有工具在启动时注册到全局注册表:
// 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 | 工作流控制 |
假设你配置了 10 个 MCP 服务器,每个提供 10 个工具:
10 个服务器 × 10 个工具 = 100 个工具
100 个工具 × 200 tokens/工具 = 20,000 tokens
加上 43 个内置工具:
43 × 200 = 8,600 tokens
总计:28,600 tokens 仅用于工具定义!
影响:
方案 1:全部加载
优点:模型能看到所有工具
缺点:Token 浪费、模型混淆
方案 2:手动选择
优点:减少 token
缺点:用户需要提前知道要用哪些工具(违背 AI 助手的初衷)
方案 3:分组加载
优点:按场景分组(如"开发工具组"、"办公工具组")
缺点:分组边界模糊、跨组协作困难
核心思路:
tool_reference 形式返回,API 自动加载完整 schemaToolSearchTool 本身也是一个工具,但它的特殊之处在于:它的输出不是数据,而是其他工具的 schema。
// 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"这个工具现在需要了,请加载它"// 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 工具命名: 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
}
}
示例:
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
}
query: "select:Read,Edit,Grep"
// 结果:直接返回这些工具(如果存在)
matches: ["Read", "Edit", "Grep"]
适用场景:模型已经知道确切需要哪些工具(如压缩后恢复工具列表)
query: "notebook jupyter"
// 搜索逻辑:
// - "notebook" 匹配 NotebookEditTool(名称)
// - "jupyter" 匹配描述中的 "Jupyter notebooks"
//
// 结果:
matches: ["NotebookEdit"]
适用场景:模型根据任务猜测需要的工具
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),但不确定具体操作
// 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
}
}
为什么需要缓存?
何时失效?
传统加载:
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: [...]
延迟加载:
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 对比:
// 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 个):
[
// 核心文件操作(高频使用)
"Read", "Edit", "Write", "Glob", "Grep",
// 命令执行(高频使用)
"Bash", "PowerShell",
// 元能力(工作流控制)
"ToolSearch", "Agent", "AskUserQuestion",
"EnterPlanMode", "ExitPlanMode",
// 通信渠道
"Brief"
]
默认延迟的工具(共 30+ 个):
[
// 低频文件操作
"NotebookEdit", "LSP",
// 网络请求(按需使用)
"WebFetch", "WebSearch",
// 任务管理(并非所有任务都需要)
"TaskCreate", "TaskUpdate", "TaskGet",
// MCP 工具(工作流特定)
"mcp__slack__*", "mcp__github__*", ...
]
| 场景 | 工具总数 | 立即加载 | 延迟加载 | 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 工具越多,延迟加载的收益越明显。
你可能会问:ToolSearch 本身没有调用大模型,那 Agent 是如何知道有哪些延迟工具可以搜索的?
答案是:Claude Code 通过两种机制将延迟工具列表传递给 Agent。
<available-deferred-tools> 前置块(已淘汰)// 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 看到的内容:
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 频道
问题:
deferred_tools_delta Attachment(当前方案)核心思路:不传递完整列表,只传递增量变化(delta)。
// 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):
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('nn'), isMeta: true }),
])
}
Agent 看到的完整流程:
=== 第 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 效率 | 中等(完整列表) | 高(仅增量) |
| 实现复杂度 | 低 | 中(需要扫描历史) |
新版优势:
ToolSearch 提示信息:
ToolSearch 工具的 description 会告诉 Agent 延迟工具在哪里:
// 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> 中看到名称)让我们用一个完整示例串联所有机制:
┌─────────────────────────────────────────────────────────────┐
│ 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 消耗 | 说明 |
|---|---|---|
| 传统方案(全部加载) | 28,600 | 143 个工具 × 200 tokens |
| 延迟加载方案 | ||
| 初始加载(13 个核心工具) | 1,800 | 节省 26,800 tokens |
| ToolSearch 调用 | 150 | 查询 + 结果 |
| 按需加载(3 个 Slack 工具) | 600 | 只加载需要的 |
| 总计 | 2,550 | 节省 91% |
测试场景:100 个 MCP 工具环境
| 指标 | 传统方案 | 延迟加载 | 改进 |
|---|---|---|---|
| 初始 Token | 20,000 | 1,800 | ↓ 91% |
| 首次工具调用延迟 | 0ms | +500ms | ↑ 500ms |
| 缓存后调用延迟 | 0ms | +50ms | ↑ 50ms |
| 工具选择准确率 | 87% | 92% | ↑ 5% |
关键洞察:
// 根据对话上下文预测可能需要的工具
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 []
}
// 用 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)
}
// 记录工具使用频率
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 只能用这些工具
问题:
Agent 根据任务 → 搜索需要的工具 → 按需加载 → 执行任务
优势:
对于 AI 应用开发者:
对于 Agent 研究:
源码文件:
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相关协议: