当AI Agent接入的工具数量多到占满上下文窗口时,一个现实且棘手的问题也随之出现了:我们该如何按需加载模型真正需要的工具?本文将深入拆解这个被称为“tool search”的机制,覆盖其实现原理、匹配算法、缓存影响,并结合x-code-cli的源码进行探讨。
Tool 与 MCP Tool
大语言模型本身无法执行操作
大语言模型(LLM)本质上是一个纯粹的文本生成器。它接收文本输入,输出文本应答,无法主动调用外部服务或执行系统操作。
那么,AI Agent究竟是如何做到读取文件、编写代码、甚至执行命令的?关键在于模型可以通过“工具调用”来“遥控”这些操作。打个比方,模型输出一条指令——“我要调用readFile工具,参数是package.json”——客户端这边(也就是实现了MCP协议的AI Agent应用,如x-code-cli、Claude Code、Cursor等工具)就会解析这条指令,实际去读取文件,然后将读到的内容回传给模型。模型拿到文件内容后,继续推理,决定下一步做什么。
这些可供模型调用的操作——读文件、执行命令、搜索代码、查数据库、联网搜索——统称为Tool(工具)。每个工具由三个部分组成:一个名字(name)、一段自然语言描述(description,用于告知模型该工具的用途)、以及一份JSON Schema(inputSchema,定义了工具接受的参数)。以x-code-cli的readFile工具为例:
{"name": "readFile","description": "Read the contents of a file at the specified path.","inputSchema": {"type": "object","properties": {"path": {"type": "string","description": "The absolute path of the file to read"},"offset": {"type": "number","description": "Line number to start reading from (1-based)"},"limit": {"type": "number","description": "Maximum number of lines to read"}},"required": ["path"]}}
这三部分会被序列化后,嵌入每次API请求的tools数组中。模型依靠description判断何时使用该工具,依靠inputSchema生成合法参数。一个工具的定义越复杂(参数多、描述长),其占用的token就越多。
下面是x-code-cli中Agent Loop的执行流程:
sequenceDiagramautonumberactor U as 用户participant C as 客户端(x-code-cli)participant LLM as LLM APIU->>C: "帮我把 utils.ts 里的 foo 函数重命名为 bar"C->>LLM: 用户消息 + 工具列表(readFile, edit, shell, grep...)LLM-->>C: tool_call: readFile({path: "src/utils.ts"})Note over C: 客户端执行 readFile,读取文件内容C->>LLM: tool_result: "export function foo() { ... }"LLM-->>C: tool_call: edit({path: "src/utils.ts", old: "foo", new: "bar"})Note over C: 客户端执行 edit,替换字符串C->>LLM: tool_result: "文件已修改"LLM-->>C: 文本回复: "已将 foo 重命名为 bar"C->>U: 展示回复
在整个过程中,模型自主决定调用什么工具、传递什么参数。客户端只负责执行和传递结果。这个循环可能执行一次,也可能执行多次,完全取决于任务的复杂度。
工具数量的增长趋势
内置工具
一个Agent产品刚上线时,内置工具的数量可能不多。但随着产品迭代,工具会逐渐增多。联网搜索、网页抓取、子Agent委派、待办管理、后台任务管理等功能会相继加入。Claude Code、Codex、Cursor等主流AI Agent产品都经历了这个增长过程。一个成熟的AI Agent产品拥有几十个内置工具,是非常普遍的情况。
MCP 工具
MCP(Model Context Protocol,模型上下文协议)是一套用于Agent接入外部服务的开放协议。例如,接入GitHub MCP server,Agent就能创建issue、合并PR;接入一个数据库MCP server,Agent就能执行SQL查询。
各个MCP Server会通过tools/list接口,向客户端注册自身对外开放的全部工具。该接口采用全量返回模式——客户端在与某一服务端建立连接后,可以一次性获取该服务端完整的工具定义清单。
sequenceDiagramparticipant C as MCP 客户端(AI Agent)participant S1 as MCP Server A(交易所)participant S2 as MCP Server B(数据库)C->>S1: 建立连接C->>S1: tools/listS1-->>C: 返回 400 个工具定义C->>S2: 建立连接C->>S2: tools/listS2-->>C: 返回 15 个工具定义Note over C: 客户端拿到所有 server 的完整工具列表
共 415 个工具,全部放进后续的 LLM 请求
MCP tool和内置tool在结构上没有本质区别,都是“名字+描述+JSON Schema”。区别仅在于提供方:内置工具由产品代码静态定义,MCP工具则由外部server动态注册。
一个MCP server提供的工具数量往往远超内置工具。以加密货币交易所的MCP server为例,它覆盖现货交易、合约交易、资产划转、行情查询、余额查询等操作,总计可达400个工具。如果用户再接入其他MCP server,工具总数很容易超过400个。
工具数量增多带来的挑战
要让大模型调用这些工具,所有工具定义必须随请求一起发送给模型。关键在于,大语言模型本身是无状态的——每次发起推理请求时,都需要重新携带完整的工具列表。
一个中等复杂度的工具定义大约占用150–300 token(名字占几个token、描述占一两行、JSON Schema中的属性名和类型描述占用大头)。400个工具 × 平均160 token ≈ 64,000 token。用户尚未开始提问,仅工具定义就占据了64k token。
具体来看x-code-cli中调用LLM时的请求结构。以下是packages/core/src/agent/loop.ts里runTurn函数的核心代码(简化后):
result = streamText({model, // LLM 模型实例system: cached.system, // System Prompt(系统提示),约 2-5k tokenmessages: cached.messages,// Messages(对话历史 + 用户消息)tools: cached.tools, // Tools(工具定义)← 全量注入时 64k tokenmaxRetries: 3,abortSignal: options.abortSignal,maxOutputTokens: getMaxOutputTokens(options.modelId),providerOptions: mergedProviderOptions,})
一次API请求的输入由system、tools、messages三个字段组成。token的分布大致如下:
一次 API 请求的 Token 构成:┌────────────────────────────────────┐│ system(系统提示)│约 2-5k token├────────────────────────────────────┤│ tools(工具定义) │← 全量注入时:64k token(400 个工具)├────────────────────────────────────┤│ messages(对话历史 + 用户消息)│根据对话长度变化└────────────────────────────────────┘如果模型的上下文窗口是 128k,工具列表占掉了将近一半
关于费用,需要补充一点:主流LLM API都支持前缀缓存(prefix caching)。system + tools这些每轮不变的前缀部分,首次请求按全价计费,后续请求命中缓存后按折扣价计费(通常是原价的10%–50%)。因此,并非每轮都需要支付全价。
但即便是64k token的工具列表命中了缓存,它仍在持续产生开销——它占用了上下文窗口的物理空间,延迟了首字生成时间(TTFT),并压缩了留给对话历史和用户消息的空间。工具列表越大,可用于实际对话的窗口就越小。
这就引出了本文的核心主题——tool search。
Tool Search 机制详解
Tool search是一个用于“按需加载工具定义”的内置工具。默认情况下,所有工具的完整定义(名字、描述和JSON Schema)都会嵌入每次请求的tools数组。tool search改变了这一策略:仅在系统提示中放置一份工具名字清单,不放完整定义。当模型判断需要调用某个工具时,先通过内置的toolSearch工具加载该工具的完整schema,然后在下一轮再执行调用。
模型仍然知道有哪些工具可用——系统提示中列出了所有工具的名字。但每个工具的完整定义(描述和JSON Schema)不会预先放入请求,而是等模型实际要调用时再加载。这样一来,上下文占用从全量64k降至仅加载工具名字清单所需的3-5k,再加上按需加载的几个工具的schema。
延迟加载(Deferred)机制
Deferred直译为“延迟”。在tool search的语境下,deferred工具就是那些被延迟加载的工具——它们的完整定义(描述和JSON Schema)不随请求发送,只在模型通过toolSearch主动请求时才会加载。与之对应的是直接加载的工具(directly loaded),即每轮请求都携带完整schema的核心工具。
下面来看按需加载具体是如何实现的,主要包含以下几个步骤:
第一步:客户端照常从MCP server全量获取工具。tools/list接口仍是全量返回。客户端启动时从每个MCP server拉取所有工具的完整定义——名字、描述、JSON Schema——全部存储在本地内存中。这一步与没有tool search时完全一致。
第二步:仅将工具名称告知模型。完整的工具schema不放入请求的tools数组。工具名称按server分组,写在系统提示的## Deferred Tools段中。模型每轮都能看到这份名称清单,知道有哪些工具可用,但看不到每个工具接受什么参数。
以下是x-code-cli实际生成的## Deferred Tools段(截取部分):
## Deferred ToolsThe tools below are a vailable but NOT loaded — only their names are listed,with no schema. To use one, first call `toolSearch` (keyword search, or`select:
可以看到,这段文字包含两部分信息:一是使用说明(告诉如何通过toolSearch加载工具),二是按来源分组的工具名称清单。模型每轮都能看到这份清单,但看不到任何工具的参数定义。
第三步:为模型提供一个内置的toolSearch工具。当模型需要调用某个deferred工具时,它先调用toolSearch,传入关键词或工具名。客户端在内存中的工具目录上进行匹配,将命中的工具加入下一轮请求的tools数组。模型在下一轮就能像调用任何普通工具一样调用它。
sequenceDiagramparticipant S as MCP Serverparticipant C as 客户端(MCP 客户端)participant LLM as LLMNote over S,C: 启动时C->>S: tools/listS-->>C: 400 个工具的完整定义Note over C: 全部存在内存里Note over C,LLM: 用户提问:"帮我查一下 BTC/USDT 的最新价格"C->>LLM: 核心工具 schema + toolSearch schema + deferred 名字清单Note over LLM: 看到名单里有 cex_spot_get_ticker,
但没有它的 schema,无法直接调用LLM-->>C: toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"})Note over C: 在内存目录里匹配,把这个工具加入 activated 集合C->>LLM: 核心工具 + toolSearch + cex_spot_get_ticker(刚激活的 schema)LLM-->>C: cex_spot_get_ticker({currency_pair: "BTC_USDT"})
需要强调的是:deferred是客户端的行为。MCP协议的tools/list依旧全量返回,协议本身不具备按需发现的能力。deferred机制完全是客户端在拿到全量工具列表后,自行决定哪些放入请求、哪些延迟加载——这是客户端的策略,而非协议的功能。
模型仅看到名称,如何找到正确工具?
如果仅提供工具名称,而不附带对应的Schema,模型确实无法明确工具的入参结构。为了解决这个问题,系统设计了多层校验保障机制。
工具名称自身已具备充足的语义描述能力。mcp__github__create_issue、cex_spot_get_ticker这类名称,模型一看就能理解其功能。MCP工具名通常遵循server__action或server__resource__verb的命名规范,名称本身就是一层语义索引。如果工具名全是tool_001、tool_002,这套方案就无法生效了。
名称清单放置在模型可见的位置。系统提示的## Deferred Tools段按server分组列出了所有deferred工具名称。模型并非盲目搜索——它看到了完整清单,只是未看到每个工具的参数定义。
客户端完成toolSearch的匹配运算。toolSearch支持两种调用方式:关键词搜索和精确匹配。
方式一:关键词搜索。当模型不确定工具的确切名称时使用。客户端拿query参数与每个工具的预处理文本进行关键词匹配,按得分排序,返回前几个匹配结果。
用一个例子来说明。假设deferred目录中包含以下几个工具:
工具目录(每个工具在注册时预处理好搜索文本):工具名搜索文本(名字拆词 + 描述 + schema 属性名)─────────────────────────────────────────────────────────────────────────mcp__gate__cex_spot_get_ticker gate cex spot get ticker 获取现货行情 currency_pairmcp__gate__cex_spot_create_order gate cex spot create order 创建现货订单 currency_pair side amount pricemcp__gate__cex_spot_cancel_order gate cex spot cancel order 取消现货订单 order_idmcp__github__create_issuegithub create issue 创建 issue title body labels
模型传入toolSearch({query: "spot ticker"})后,客户端对每个工具进行打分:
query = "spot ticker"mcp__gate__cex_spot_get_ticker:"spot" → 名字拆词里有 "spot" → +10"ticker" → 名字拆词里有 "ticker" → +10总分 = 20 ✓mcp__gate__cex_spot_create_order:"spot" → 名字拆词里有 "spot" → +10"ticker" → 名字拆词里没有,搜索文本里也没有 → +0总分 = 10mcp__gate__cex_spot_cancel_order:"spot" → 名字拆词里有 "spot" → +10"ticker" → 名字拆词里没有,搜索文本里也没有 → +0总分 = 10mcp__github__create_issue:"spot" → 都没有 → +0"ticker" → 都没有 → +0总分 = 0结果(按分数降序):1. mcp__gate__cex_spot_get_ticker (20)← 返回2. mcp__gate__cex_spot_create_order (10)← 返回3. mcp__gate__cex_spot_cancel_order (10)← 返回4. mcp__github__create_issue(0) ← 过滤掉
Codex的搜索逻辑类似,也是为每个工具预先生成搜索文本,但打分采用BM25算法(信息检索领域的经典排序算法,搜索引擎常用它来排序搜索结果)。核心思路一致:用query的关键词去匹配工具的搜索文本,按相关度排序。
这两种方式都不需要使用embedding,也无需调用模型,执行的都是字符串匹配——同样的query、同样的工具集,得到的结果完全一致。
此外,关键词搜索可能会同时命中多个工具。toolSearch按打分排序,取前N个(x-code-cli默认取5个),所有命中的工具都会被激活——加入activated集合,在下一轮全部出现在tools数组中。模型拿到这几个工具的schema后,自行判断使用哪一个,客户端不进行限制。
举个例子:用户说“帮我下一个限价单买BTC”,模型调用toolSearch({query: "spot order"}),可能会同时命中cex_spot_create_order、cex_spot_get_ticker、cex_spot_cancel_order等多个工具。多激活几个工具的代价是每个工具多占一份schema的token,但这比全量注入400个工具要好得多。而且激活是持久的——一旦激活,后续轮次无需重复搜索同一个工具,可以直接使用。
方式二:select:精确匹配。模型直接传递select:工具名,客户端按名称精确查找,无需打分。
模型如何知道确切的工具名?因为系统提示的## Deferred Tools段中列出了所有deferred工具的完整名称。模型每轮都能看到这份清单,只要它在清单中找到了目标工具名,就可以直接使用select:加载。
何时使用哪种方式?这个逻辑写在toolSearch工具自身的description中,模型在调用前会读到这段说明:
Pass `query` as either:- keywords describing the capability you need(e.g. "search the web", "github create issue")→ returns the best-matching deferred tools, or- "select:
工具描述直接告诉模型:如果已经知道名称,优先使用select:;不确定名称时,用关键词描述能力。实际使用中,模型大多数情况会走select:路径,因为它能直接从清单中看到工具名。关键词搜索是模型对工具名不确定时的备选路径——例如当工具名较长或有多个相似工具时,模型可能使用"spot order"这样的关键词让客户端协助筛选。
两种调用方式对比:关键词搜索(不确定工具名时用):toolSearch({query: "spot ticker"})→ 客户端对所有工具做关键词打分,返回得分最高的几个精确匹配(已知工具名时用,推荐方式):toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"})→ 客户端直接按名字查找,找到就返回,不打分
关键词搜索是一层保底机制。模型大多数情况下能直接从清单中找到目标工具名,走select:精确匹配。只有当模型不确定具体使用哪个工具时,才会退而使用关键词搜索让客户端协助筛选。
哪些工具应被Defer,哪些应直接加载
并非所有工具都需要defer。一些核心工具会直接加载,而一些偶尔才用的工具才会被defer。
直接加载的工具(每轮请求都携带完整schema):
这类工具是Agent的基础操作能力,用户随便问个问题都可能用到:
| 工具 | 功能 | 为何必须直接加载 |
|---|---|---|
readFile | 读取文件 | 几乎所有编码任务都需要先读取文件 |
edit | 编辑文件 | 编写代码的核心操作 |
writeFile | 写入文件 | 创建新文件 |
shell | 执行命令 | 运行测试、安装依赖、git操作 |
grep | 搜索代码 | 定位代码位置 |
glob | 查找文件 | 查找文件路径 |
task | 子Agent | 委派子任务 |
toolSearch | 工具搜索 | 加载deferred工具的唯一入口 |
延迟加载(defer)的工具:
- 非核心内置工具:
webSearch(联网搜索)、webFetch(网页抓取)、todoWrite(待办清单)。这些工具并非每个任务都会被用到,因此需要时再加载。 - 所有MCP工具:MCP工具由外部server提供,面向特定场景(交易、GitHub操作、数据库查询等),不存在“每个任务都会用到”的MCP工具。
用一个具体例子来说明分类过程。假设Agent注册了以下工具:
分类示例:readFile→ 核心内置工具,每个任务都用→ 直接加载edit→ 核心内置工具,每个任务都用→ 直接加载shell → 核心内置工具,每个任务都用→ 直接加载toolSearch→ 加载入口,必须直接可用 → 直接加载webSearch → 非核心内置,偶尔才用 → deferwebFetch→ 非核心内置,偶尔才用 → defermcp__gate__cex_spot_get_ticker→ MCP 工具→ defermcp__gate__cex_spot_create_order→ MCP 工具→ defermcp__github__create_issue → MCP 工具→ defer...(其余 MCP 工具全部 defer)
以下是判断逻辑的流程图:
完整循环:搜索 → 激活 → 调用
来看一个具体的示例——以“查询BTC价格”来演示实际的调用过程:
sequenceDiagramautonumberactor U as 用户participant C as 客户端participant LLM as LLMU->>C: "帮我查一下 BTC 的现货价格"C->>LLM: 用户消息 + 核心工具 + toolSearch + deferred 名字清单Note over LLM: 看到名单里有 cex_spot_get_tickerLLM-->>C: toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"})Note over C: 精确匹配命中,加入 activated 集合C-->>LLM: tool_result: "Loaded 1 tool(s) — now callable directly on your next step"Note over C: 下一轮请求的 tools 数组追加 cex_spot_get_ticker 的完整 schemaC->>LLM: 核心工具 + toolSearch + cex_spot_get_ticker(已激活)LLM-->>C: cex_spot_get_ticker({currency_pair: "BTC_USDT"})Note over C: 执行 MCP 工具调用,拿到价格C->>LLM: 工具执行结果LLM-->>C: "BTC 现货价格是 63,521.30 USDT"C->>U: 展示回复
注意步骤3和步骤5之间:模型在第一轮调用了toolSearch工具,但不能在同一轮里调用cex_spot_get_ticker——因为这个工具在第一轮的tools数组中尚不存在。必须等客户端将其schema加入后,下一轮模型才能调用。
这就是deferred方案的代价:首次使用某个deferred工具时,至少需要2轮API调用——先搜索,下一轮才能调用。
整个流程中State的变化
从启动到多轮对话,各个state字段的变化过程如下:
| 阶段 | state 变化 |
|---|---|
| 启动 | catalog = 从所有 MCP server 拿到的 400 个 deferred 工具的完整定义 |
activated = 空集合 | |
systemPromptCache = 系统提示,包含 ## Deferred Tools 名字清单 | |
baseTools = [readFile, edit, shell, grep, ..., toolSearch] | |
| 第 1 轮请求 | tools = composeTurnTools(baseTools, activated) = baseTools(activated 为空) |
| 用户:“查 BTC 价格” | 发给 LLM:system + tools(9 个核心工具)+ messages |
| 第 1 轮响应 | LLM 返回:toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"}) |
handleToolSearch() 在 catalog 里精确匹配命中 | |
activated = { "mcp__gate__cex_spot_get_ticker" } | |
返回 tool_result 给模型:Loaded 1 tool(s) — now callable directly on your next step: | |
- mcp__gate__cex_spot_get_ticker: 获取现货行情 | |
| 第 2 轮请求 | tools = [...baseTools, cex_spot_get_ticker 的完整 schema](尾部多了一个工具) |
messages 里追加了上一轮的 tool_call + tool_result | |
发给 LLM:system + tools(10 个工具)+ messages,前缀缓存 miss 一次 | |
| 第 2 轮响应 | LLM 返回:cex_spot_get_ticker({currency_pair: "BTC_USDT"}) |
客户端执行 MCP 工具调用,拿到价格。activated 不变 | |
| 第 3 轮 | tools 同第 2 轮(activated 没变,前缀稳定,缓存命中) |
| LLM 返回文本回复 | |
| 后续轮次 | activated 持久保留,不用再搜 cex_spot_get_ticker |
| 如果激活新工具 → activated 增长 → tools 追加 → 再 miss 一次 |
几个关键点:
catalog和systemPromptCache在启动时固定,整个session中不变。activated只增不减。tools数组每轮动态合成 =baseTools+ activated对应的schema。- 前缀缓存仅在activated增长的那一轮miss,之后保持稳定。
工程实现细节
前面讲解的是tool search的核心流程。本节主要探讨实现过程中遇到的几个问题及其解决方案。
阈值判断:何时应启用defer。当工具数量较少时,全量注入更简单,也无需多一轮往返。x-code-cli使用DEFERRAL_THRESHOLD_PERCENT进行判断:将所有候选deferred工具的schema序列化后估算token量,如果不超过模型上下文窗口的10%,则跳过defer,退回全量加载模式。
模型兼容性。Tool search依赖模型主动调用toolSearch。如果模型不支持或不配合,deferred工具将永远不会被加载。各家的处理方式有所不同:
- Codex的做法最为精确:在
models.json中按具体模型版本配置supports_search_tool布尔字段。只有明确标记为支持的模型才启用tool search,未知模型默认关闭。 - Claude Code判断的是API能力而非模型智能:使用
modelSupportsToolReference()检查模型是否支持tool_reference协议块。默认排除列表只有['haiku'],但可通过远程配置动态更新,无需发布新版本。 - x-code-cli目前使用粗粒度的字符串匹配:
WEAK_MODEL_PATTERNS = ['haiku', 'nano', 'glm-4v'],模型名包含这些子串则退回全量注入。该方案的缺点是无法区分同一系列的不同版本——例如Claude 3 Haiku和Claude 3.5 Haiku能力差异很大,但都会被一刀切地禁用。
系统提示的字节稳定性。LLM API的前缀缓存依赖于请求前缀的不变性。system + tools组成的前缀在后续请求中保持一致,就能命中缓存,按折扣价计费。这就要求系统提示中的deferred名单不能在运行过程中动态修改。x-code-cli的做法是在启动时生成systemPromptCache并冻结,在整个session中不再改动。
这带来一个副作用:工具激活后,系统提示中的名单仍然标注该工具为deferred状态,但tools数组中已包含其完整schema。模型看到的系统提示和实际的工具列表之间存在不一致:系统提示说工具未加载,但工具实际上已在列表中。
在实际使用中,这种不一致很少引发问题。强模型(如Sonnet、GPT-4o等级)在收到第一次toolSearch的tool_result后,会记住工具已加载,后续直接调用而不会重复搜索。重复搜索仅在极端情况下出现——例如对话轮次非常多,早期的tool_result被压缩,模型再次看到系统提示中写着“未加载”时,才可能再次搜索。x-code-cli增加了一层防御:如果搜索的工具已在activated集合中,返回Already loaded — call xxx directly now.,避免模型陷入搜索循环。这是防御性代码,并非常规路径。
Claude Code和Codex从架构上避免了这种不一致。Claude Code依靠Anthropic API的tool_reference机制,激活的工具由服务端展开schema,客户端不修改tools数组。Codex依靠OpenAI Responses API的tool_search_output历史项,同样由服务端处理。两者都无需在系统提示中维护一份可能过时的状态。x-code-cli由于需要跨多个provider,无法依赖单一API的服务端能力,只能走客户端重注入——但从实际效果看,强模型下这种不一致几乎不会触发重复搜索。
子Agent不开启defer。子Agent的工具集由白名单控制,大多数子Agent只有几个内置工具。例如explore子Agent仅包含readFile、glob、grep、listDir、shell五个工具,plan子Agent更少,只有四个只读工具。工具数量如此少,添加tool search没有意义。唯一的例外是general-purpose子Agent——它继承了父session的MCP工具,工具数量可能很多,但即便如此,子Agent目前也走全量注入,因为子Agent的生命周期短、轮次少,defer带来的节省不明显。
总结
回顾一下tool search的完整流程:启动时从MCP server全量获取工具定义,根据使用频率分为直接加载和deferred两类。直接加载的工具,其完整schema放入每轮请求的tools数组;deferred工具仅在系统提示中列出名称。当模型需要某个deferred工具时,调用toolSearch加载其schema,下一轮即可正常调用。
几个主流AI Agent CLI的实现对比:
| 全量注入? | 动态机制 | 匹配算法 | |
|---|---|---|---|
| Claude Code | 否 | ToolSearch 工具 | 关键词打分 + select: |
| Codex | 否 | tool_search 工具 | BM25 全文检索 |
| Gemini CLI | 是 | 无 | — |
| x-code-cli | 否 | toolSearch 工具 | 关键词打分 + select: |
Claude Code、Codex、x-code-cli三家的思路几乎一致,区别在于激活后的schema如何对模型可用——Claude Code和Codex依靠各自API的服务端能力,x-code-cli依靠客户端重注入(直接将schema加入tools数组)。Gemini CLI目前没有内置tool search功能。
参考资料
- Claude Code 源码
- Codex 源码
- x-code-cli 源码
- MCP 协议规范
