当你连的不是一台服务器而是五台 — 一百八十个工具怎么办
这一章讲三件事: 连多台服务器时,连接、重名、换模型这三样各要补什么; 工具多到模型读不完时怎么办;以及「这活到底值不值得你自己干」。
它在全书链条里的位置: 这一章还第 05 章那笔账—— 那里说「工具一多,模型就挑不准」,病因给了两个,治法一个都没给。 两个治法都在这一章。
1. 一个客户端只连一台,那连五台怎么办
这一节回答:第 03 章那条硬规矩,到了真实场景要付什么代价。
第 03 章那条规矩是:一个客户端只连一台服务器1。 连五台,意味着你要自己管五份连接、五个子程序、五套断开逻辑。
书里给的办法是官方 Python 工具包里的一个东西——会话组: 一个替你同时管着好几份会话的容器2。它替你干三件事:
| 它替你管什么 | 具体是 |
|---|---|
| 连接 | 一个方法连一台;也可以一个方法断一台,断的时候顺手把那台加载进来的东西清掉 |
| 清单 | 连上的时候就把那台服务器的三类东西全读进来,放在自己的属性里2 |
| 调用 | 调工具的写法和单台时一模一样;它自己记着「哪个工具属于哪一台」 |
但它换来的方便有一个明确的代价,书里写得很直白:
当时还不支持在建立单个会话时挂上回调3。
回看第 07 章:采样、根目录、日志,全都靠「建会话时挂一个函数」。 用了会话组,这几样就挂不上去。书里给的变通是去动那个会话对象的「私有」属性, 但作者自己加了一句:「这个办法应当少用」3。
自己管五份连接 会话组
───────────── ─────────
✅ 回调随便挂 ❌ 挂不上(当时)
❌ 连接、清单、断开全要自己写 ✅ 全托管
图说:这是一道真实的取舍,不是「新的更好」。
你的客户端要不要提供采样,决定了你能不能用会话组。
2. 撞名:两台服务器都有一个叫 search 的工具
这一节讲聚合多台时第一个会撞上的坑。
工具名在一台服务器内部是唯一的。但跨服务器就不是了。
你连了一台代码搜索服务器和一台文档搜索服务器,两台都有一个工具叫 search——
模型看到的那张清单上,就出现了两个一模一样的名字。
它会挑错,而且你无从判断它挑的是哪一个。
书里给的办法是会话组构造时的一个可选口子:
一个用来给组件改名、以免撞名的钩子函数4。
你给它一个函数,它把「原名 + 是哪台服务器」交给你,你返回一个新名字,
比如 code_search 和 docs_search。
这里有一条容易踩空的地方:改名要基于什么?
一个自然的想法是用服务器自报的名字当前缀。别这么做。 官方规范对服务器自报的身份有一句硬提醒: 它是服务器自己填的、协议不做任何验证,客户端「不应当」根据它改变自己的行为, 更「不应当」拿它做安全判断5。
该用什么?用你自己这一侧的配置。 你在配置文件里给每台服务器起的那个名字, 是你说了算的、别人改不了的——拿它当前缀才安全。
顺带一件事:该由谁决定把哪些工具交给模型
书里在列「客户端值得实现的进阶功能」时,提到过一项叫「资源过滤」6—— 它和改名其实是同一件事的两头:一头是让名字不撞,另一头是让不该出现的东西根本不出现。
为什么需要过滤? 一台服务器给了你 40 个工具,但你的应用只用得上 3 个。 把 40 个全摊给模型,剩下 37 个纯粹是干扰(而且要花钱,见第 5 节)。 决定权在你,不在服务器。
3. 换模型:MCP 给的格式和模型要的格式不是一个
这一节拆掉一个常见的误会。
书里把这件事列为 MCP 的一大好处:它给了你(作为应用开发者)自由, 让你的用户能用很多种模型,或者用你选的那种;有了 MCP 你不再被绑在一个模型上7。
但「不被绑死」不等于「自动支持」。 中间还差一层翻译:
MCP 给你的工具 模型厂商 A 要的形状 模型厂商 B 要的形状
──────────────── ────────────────── ──────────────────
name → name → name
description → description → description
inputSchema → inputSchema → parameters
+ type: "function"
图说: 两家的形状「非常相似」,但字段名不同、外面还多包一层。
相似不等于相同——差一个字段名,模型就收不到参数表。
书里对这层翻译给了两个写法,并明说第二个更好8:
| 写法 | 长什么样 | 问题 / 好处 |
|---|---|---|
| 朴素的 | 为「每一类东西 × 每一家模型」各写一个转换函数 | 三类 × 三家 = 9 个散落的函数,又是第 03 章那道乘法题 |
| 更好的 | 为每一类东西写一个自己的类,转换方法作为它的一个成员 | 加一家模型 = 每个类加一个方法,加一类东西 = 加一个类 |
第二种好在哪? 它把「一个工具能变成哪些形状」这件事收在一个地方。 你要加第四家模型时,只要打开三个类各加一个方法,而不是去找散在九处的函数。
4. 或者根本不自己写——两家厂商都提供了直连
这一节回答一个前面八章都没问过的问题:这活值不值得你自己干?
书里两次正面提到了绕开自建客户端的路。
第一次:Anthropic 的 MCP 连接器(测试版)。 书里说它承诺让用户直接通过模型接口访问远程 MCP 服务器9。 作者同时给了三条判断,而且都是减分项:
| 作者给的代价 | 意思 |
|---|---|
| 只支持工具和远程服务器 | 资源、话术都用不了;本机服务器也用不了——而本机是当时最常见的形态(第 04 章) |
| 远程服务器本身有安全风险 | 作者在这里明确往后指了一句(第 10 章讲那些风险) |
| 把你绑在一家厂商的模型上 | 和上一节那条「不被绑死」正好相反 |
第二次:OpenAI 的接口同样支持直接调用远程 MCP 服务器10。 书里只有一句话,没有展开。
判断(我们的,不是书里的): 这两条路的适用面很窄,但窄得很清楚—— 如果你只用工具、只连远程服务器、而且已经决定了用哪家模型, 那么本书前八章教的东西你一行都不需要写。 反过来,只要你要用资源或话术、要连本机服务器、或者要留住换模型的自由, 直连就不成立。 如果错,会错在: 如果厂商把直连扩展到资源、话术和本机服务器, 这条判断的分界线就要往自建那一侧移,自建客户端的适用面会明显缩小。 判据是:厂商的直连支持不支持本机服务器。
5. 上下文预算:180 个工具的说明书要花多少
这一节是本章主走查的第 1 步,也是全章最硬的一个数。
场景:一个宿主连 5 台服务器,一共 180 个工具。 5 台和 180 个是我们为演示编的;十几万对两千那个量级出自官方文档。
先把两个词说清楚。
模型读文字不是按字读的,是按词元——它把文字切开之后的最小单位, 可能是一个词,也可能是半个词。你可以粗略地按「一个英文词约 1.3 个词元」来估11。
而模型一次能读进去的词元有上限,这个上限叫上下文窗口—— 这一轮对话里所有要读进去的东西加起来,不能超过的那个额度。
「所有要读进去的东西」具体是这五样:
- 系统那段交代身份和规矩的话;
- 用户这一轮打进来的那句话;
- 工具说明书——本节要算的就是这一项;
- 前面几轮的来回记录;
- 模型自己这一轮要写出来的回答。
现在算账。 官方文档给的对照是12:
| 做法 | 光工具说明就要花多少 |
|---|---|
| 把所有工具一次全摊给模型 | 约 150 000 词元 |
| 按需查找(下一节) | 约 2 000 词元 |
150 000 是个什么概念?
- 用户那句「把上周的错误日志汇总成一份周报」,大约 20 个词元—— 工具说明书是它的七千多倍,而且用户那句话还一个字没被读到;
- 按我们这 180 个工具反推,平均每个工具的说明书约 800 词元—— 差不多是一页纸。180 页纸,模型每轮都要重读一遍。
官方文档给的切换判据很实用:把阈值(触发切换的那条线)定成上下文窗口的一个百分比, 比如 1% 到 5%;先照常加载工具说明,一旦超过这个比例就切到按需查找12。
注意它没有说「工具超过 N 个就切」。 判据是占了多大比例, 因为工具说明的长短差别极大——20 个啰嗦的工具可能比 100 个简洁的还占地方。
顺便还上第 06 章那笔账: 那里说资源可以当一份反复取用的底稿—— 这个做法的行话叫缓存(把取过一次的东西先存起来,下次直接拿,不再去取)。 它省的就是这里的词元。 同一份资料每轮重塞一遍,你就每轮付一遍钱。
6. 按需查找:把 180 个工具换成 1 个「帮我找工具」
这一节是主走查的第 2 步,也是第 05 章那笔账的正式还款。
这个做法我们从头到尾只用一个名字:按需查找——先不把工具说明塞进去, 等模型需要的时候再让它自己来找12。(官方文档里它叫什么,见总纲那张对照表。)
它分三层:
第 1 层 · 找 模型调:search_tools("汇总错误日志")
拿回:几个名字 + 每个一行说明 ← 便宜,几十个词元
第 2 层 · 看 模型调:get_tool_details("logs_aggregate")
拿回:这一个工具的完整参数表 ← 只有这一个的开销
第 3 层 · 用 模型调:logs_aggregate(…) ← 第 05 章那套四步
图说:180 个工具的说明书从「每轮全塞」变成「用到哪个才装哪个」。
宿主一侧仍然照常向服务器要清单,只是**先不交给模型**。
对照第 05 章那两个病因,治法正好对上:
| 第 05 章给的病因 | 这里的治法 |
|---|---|
| 工具说明重叠、含糊 → 挑不准 | 第 1 层的检索(按一句话去一堆东西里找出最相关的几个)替模型做了初筛,重叠的几个由它排序去分辨 |
| 说明加起来太大 → 读不完 | 不提前塞,总量从 150 000 降到 2 000 |
第 1 层那个「找」怎么实现,官方文档给了四条路12:
| 做法 | 特点 |
|---|---|
| 按关键词找 | 最简单;工具名和说明写得好就够用 |
| 按意思找 | 能处理同义词,「汇总」和「聚合」认得出是一回事 |
| 让一个小模型来挑 | 效果通常最好,但每次找都要花一次模型调用的钱 |
| 混着来 | 两种打分合起来排 |
还有一层可以省:连服务器本身也可以按需连。 不必启动时就把 5 台全连上,可以维护一张「有哪些服务器可用」的目录, 模型说需要哪台才连哪台,用完再断开、把位置腾出来12。
7. 让模型写代码去调工具
这一节讲另一条省钱的路,它省的是另一头。
上一节省的是工具说明的开销。这一节省的是工具结果的开销。
回看第 05 章那个四步:每调一次工具,结果都要完整地流过模型一遍。 如果一件事要连着调五个工具,中间那些结果全都要经过模型—— 哪怕模型根本不需要看它们。
另一条路是:让模型写一段脚本,一次把这几个工具串着跑完,只把最后结论送回模型13。 (这条路官方文档里的名字同样见总纲那张对照表。)
直接调: 模型 → 工具A → 模型 → 工具B → 模型 → 工具C → 模型
↑ 三次来回,每次的完整结果都进模型(可能十万词元)
写脚 本: 模型写一段脚本 → 在隔离环境里跑:工具A → 工具B → 工具C
→ 只把 console.log 那一句送回模型(可能十几个词元)
图说:官方文档给的对照是「约 200 词元的脚本换回约 15 词元的小结」,
而直接调那条路要过掉十万以上。
这条路有一个硬前提:宿主必须先建好那个隔离环境。 它叫沙箱——一个把程序关在里面跑的封闭环境:它没有网络, 碰不到你的文件,只能通过你给它开的那几个口子和外面打交道。
官方文档对这件事的安全要求列得很细,其中三条最要紧13:
| 要求 | 为什么 |
|---|---|
| 沙箱不许有网络 | 所有对外通信必须经过宿主转发,宿主才能挡住不该发的 |
| 凭证不进沙箱 | 密钥由宿主持有,转发时才加上——模型写的代码永远看不到它 |
| 批准脚本 ≠ 批准里面每一次调用 | 用户点头的是「跑这段脚本」,不是「随便调什么工具都行」;每一次调用仍要按你的规矩过一遍 |
8. 我们的判断,以及 这一章的边界
判断(我们的,不是书里的): 按需查找不是纯赚的,它把「挑工具」这件事 从模型手里挪给了检索。模型再聪明,也只能在检索给它的那几个候选里挑—— 第 1 层漏掉的工具,后面两层救不回来。 所以切到按需查找之后,你的调试重点也要跟着挪: 从「模型为什么挑错」变成「检索为什么没召回」。 如果错,会错在: 如果检索用的是「让一个小模型来挑」那一路, 那么第 1 层本身就是一次模型判断,这条「挪给检索」的说法就不成立—— 它只是把判断换了一个更便宜的模型来做。判据是:第 1 层用的是不是模型。
这一章哪些来自书、哪些是我们补的:
| 内容 | 来源 |
|---|---|
| 会话组、挂不上回调、改名钩子、翻译层的两种写法、两家厂商的直连 | 书里有 |
| 服务器自报名字不可信 | 官方规范(脚注 5) |
| 上下文预算的数量级、按需查找的三层、切换阈值、四条检索路子 | 官方客户端最佳实践文档(脚注 11) |
| 让模型写脚本去调工具、以及沙箱 | 同上(脚注 12) |
还有一处必须点明的空白: 原书这一章的最后一节标题是「最佳实践」, 正文只有一段开场白——作者说这些做法有的还没定型、有的来自社区、 有的就是通用工程经验,然后说「把它们过一遍会帮你快速建起稳 健的、能上生产的客户端」。 然后就没有了14。所以「怎么把客户端写到能上生产」这件事,书里一个字都没给。
9. 可带走的
- 一个客户端只连一台,连五台要么自己管五份,要么用会话组替你管;
- 会话组的代价很具体:当时挂不上回调——你要不要提供采样,决定了你能不能用它;
- 工具名只在单台服务器内唯一,聚合多台必须自己改名;
- 别拿服务器自报的名字当前缀——那是它自己填的、没人验证;用你自己配置里的名字;
- 改名和过滤是同一件事的两头:让名字不撞,和让不该出现的工具根本不出现;
- 用了 MCP 也还差一层翻译;写成「每类东西一个类、转换方法是它的成员」,比散着写一堆函数好;
- 两家厂商都提供了直连,代价是三条:只支持工具和远程服务器(本机的用不了、资源话术也用不了)、 远程服务器本身就是一类风险(第 10 章)、把你绑在一家厂商的模型上;
- 上下文预算是最硬的一堵墙:全塞约 150 000 词元,而用户那句话才 20 个;
- 切换判据是占上下文窗口的百分比(1%–5%),不是工具个数;
- 按需查找分三层:先搜名字、再取详情、最 后才调——第 1 层漏掉的,后面救不回来;
- 让模型写脚本在沙箱里串着跑,省的是工具结果那一头;沙箱必须没网络、拿不到凭证;
- 书里那节「最佳实践」只有一段开场白,正文是空的。
10. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 一个客户端只连一台 | Using Multiple Servers | text/11-fm-using-multiple-servers.txt:6(搜「single client can only connect to a single server」) |
| 会话组是什么、替你管什么 | Using Multiple Servers | text/11-fm-using-multiple-servers.txt:7(搜「ClientSessionGroup」) · :11(搜「loading all primitives/components」) |
| 会话组挂不上回调 | Using Multiple Servers | text/11-fm-using-multiple-servers.txt:53(搜「there isn’t support yet」) · :54(搜「for including callbacks when establishing」) |
| 改名钩子 | Using Multiple Servers | text/11-fm-using-multiple-servers.txt:61(搜「to prevent naming collisions」) |
| 最佳实践只有开场白 | Using Multiple Servers | text/11-fm-using-multiple-servers.txt:68(搜「Some best practices aren’t yet fully established」) |
| 换模型的自由 | Supporting Multiple Models | text/10-fm-supporting-multiple-models.txt:3(搜「it gives you the freedom」) |
| 翻译层的两种写法 | Supporting Multiple Models | text/10-fm-supporting-multiple-models.txt:8(搜「for each primitive-model family pair」) · :10(搜「creating a class for each」) |
| 两家格式很像但不同 | Supporting Multiple Models | text/10-fm-supporting-multiple-models.txt:34(搜「the Anthropic and OpenAI tool formats are so similar」) |
| OpenAI 也支持直连 | Supporting Multiple Models | text/10-fm-supporting-multiple-models.txt:46(搜「calling remote MCP servers」) |
| Anthropic 的直连与三条代价 | Example: A Simple Host Application | text/06-fm-example-a-simple-host-application.txt:142(搜「allow users to access remote MCP servers」) · :145(搜「supports tools and remote MCP servers」) · :147(搜「the user to Anthropic models like Claude」) |
| 资源过滤列为进阶功能 | Example: A Simple Host Application | text/06-fm-example-a-simple-host-application.txt:168(搜「Resource filtering」) |