MCP 工具进注册表 · 远程工具怎么变成本地工具

连上 MCP 服务器后,它的远程工具怎么进 grok 的工具注册表、怎么和内置工具并存、又怎么在成千上万个工具面前不撑爆上下文。这一章看工具限定名、动态注册,以及 search_tool/use_tool 的规模化解法。

作者 David更新于 第 36 篇(共 60 篇)

MCP 工具进注册表 · 远程工具怎么变成本地工具

上一章连上了 MCP 服务器。这一章看关键的下一步:它暴露的远程工具,怎么变成模型能调的工具——还要和内置工具系统无缝并存。

限定名:server__tool

多个 MCP 服务器可能有同名工具(都叫 search),所以要限定名:MCP_TOOL_NAME_DELIMITER = "__",工具叫 <服务器>__<工具>。into_registration 有个严格守卫:分隔符数量必须恰好是 1——foo__bar 服务器、my__thing 工具、边界歧义(连 ___ 都按两个边界算),统统跳过并打一条 error 日志。工具名还过一道 validate_tool_name 正则(取各家模型 API 命名规则的最严交集),保证在哪家模型下都合法;校验的是整个限定名,所以服务器名加工具名合计不能超过 64 个字符。

动态注册进 ToolBridge

每个远程工具包成 McpErasedTool,它实现了和内置工具一样的 Tool trait(Args = Value、Output = ToolOutput),id() 就是限定名——同一个原始工具挂在两个服务器上,自然成为注册表里两个不同条目。然后 ToolBridge::register_mcp_tools 运行时动态把它塞进 FinalizedToolset。所以 MCP 工具和内置工具在注册、派发层完全一视同仁——这正是工具协议那章「一套契约」抽象的回报。只是模型并不直接调它们:调用由元工具 use_tool 经 InnerDispatch::call_terminal 转发,绕开外层 ToolBridge 的锁,也不重复跑提醒与持久化。

还有个 model_visible(读 meta.ui.visibility):有些 MCP 工具是纯 UI 的(只给前端界面用,走 x.ai/mcp/call 调),不注册给模型。

规模化:成千上万个工具怎么办

一个真实困境:接几个 MCP 服务器,可能冒出几千个工具。全塞进模型的工具列表,上下文直接爆,还严重干扰选择。grok 的解法效果上就是工具协议那章提过的 Concise 模式,但 CLI 并没有构造那个枚举,而是一套常开的机制:

  • 每轮组装工具定义时只取内置工具(tool_definitions_builtins_only,调用处注释写着「本地模式下工具搜索总是开启」),名字里带 __ 的 MCP 工具一律滤掉;
  • 模型面对 MCP 只看到两个内置元工具 search_tool / use_tool:前者在内存里的 BM25 索引上搜(默认返回 5 条),直接给出输入 schema,后者按限定名转发调用;
  • 提醒明确要求:「调 use_tool 前必须先 search_tool 拿到该工具的输入 schema」,绝不许猜参数名。

于是无论挂多少 MCP 工具,模型看到的工具集始终稳定(search_tool 的描述本身是静态的,不破 KV 缓存;服务器清单以 <system-reminder> 增量注入)。服务器连不上或还在连接时也各有提醒,免得模型把空的搜索结果误读成「没有这个工具」。这和读与搜那章澄清的「search_tool 搜的是工具」正好闭环。

源码里还有一个 materialize_descriptors,能把每个工具的描述落盘成 <server>/tools/<name>.json。但它服务的是 Cursor 风格的另一套 prompt 模板,开源构建里写死关闭(cursor_mcps_root() 恒为 None),和 search/use 这条路无关。

和《从 LLM 到 Coding Agent》对照

对照 mcp:最小实现里,MCP 工具列出来直接加进工具数组就完事。grok 的增量在两件事:无缝并入(远程工具走和内置工具一样的 ToolHandle 契约)和规模化(几千工具靠常开的过滤 + search/use 元工具收敛)。下一章看另一类扩展点:hooks。


上一篇:MCP 客户端 · 下一篇:Hooks

本页目录