跳到主要内容

当你连的不是一台服务器而是五台 — 一百八十个工具怎么办

这一章讲三件事: 连多台服务器时,连接、重名、换模型这三样各要补什么; 工具多到模型读不完时怎么办;以及「这活到底值不值得你自己干」。

它在全书链条里的位置: 这一章还第 05 章那笔账—— 那里说「工具一多,模型就挑不准」,病因给了两个,治法一个都没给。 两个治法都在这一章。

1. 一个客户端只连一台,那连五台怎么办

这一节回答:第 03 章那条硬规矩,到了真实场景要付什么代价。

第 03 章那条规矩是:一个客户端只连一台服务器1。 连五台,意味着你要自己管五份连接、五个子程序、五套断开逻辑。

书里给的办法是官方 Python 工具包里的一个东西——会话组: 一个替你同时管着好几份会话的容器2。它替你干三件事:

它替你管什么具体是
连接一个方法连一台;也可以一个方法断一台,断的时候顺手把那台加载进来的东西清掉
清单连上的时候就把那台服务器的三类东西全读进来,放在自己的属性里2
调用调工具的写法和单台时一模一样;它自己记着「哪个工具属于哪一台」

但它换来的方便有一个明确的代价,书里写得很直白:

当时还不支持在建立单个会话时挂上回调3

回看第 07 章:采样、根目录、日志,全都靠「建会话时挂一个函数」。 用了会话组,这几样就挂不上去。书里给的变通是去动那个会话对象的「私有」属性, 但作者自己加了一句:「这个办法应当少用」3

自己管五份连接 会话组
───────────── ─────────
✅ 回调随便挂 ❌ 挂不上(当时)
❌ 连接、清单、断开全要自己写 ✅ 全托管

图说:这是一道真实的取舍,不是「新的更好」。
你的客户端要不要提供采样,决定了你能不能用会话组。

2. 撞名:两台服务器都有一个叫 search 的工具

这一节讲聚合多台时第一个会撞上的坑。

工具名在一台服务器内部是唯一的。但跨服务器就不是了。 你连了一台代码搜索服务器和一台文档搜索服务器,两台都有一个工具叫 search—— 模型看到的那张清单上,就出现了两个一模一样的名字。

它会挑错,而且你无从判断它挑的是哪一个。

书里给的办法是会话组构造时的一个可选口子: 一个用来给组件改名、以免撞名的钩子函数4。 你给它一个函数,它把「原名 + 是哪台服务器」交给你,你返回一个新名字, 比如 code_searchdocs_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. 可带走的

  1. 一个客户端只连一台,连五台要么自己管五份,要么用会话组替你管;
  2. 会话组的代价很具体:当时挂不上回调——你要不要提供采样,决定了你能不能用它;
  3. 工具名只在单台服务器内唯一,聚合多台必须自己改名;
  4. 别拿服务器自报的名字当前缀——那是它自己填的、没人验证;用你自己配置里的名字;
  5. 改名和过滤是同一件事的两头:让名字不撞,和让不该出现的工具根本不出现;
  6. 用了 MCP 也还差一层翻译;写成「每类东西一个类、转换方法是它的成员」,比散着写一堆函数好;
  7. 两家厂商都提供了直连,代价是三条:只支持工具和远程服务器(本机的用不了、资源话术也用不了)、 远程服务器本身就是一类风险(第 10 章)、把你绑在一家厂商的模型上;
  8. 上下文预算是最硬的一堵墙:全塞约 150 000 词元,而用户那句话才 20 个;
  9. 切换判据是占上下文窗口的百分比(1%–5%),不是工具个数;
  10. 按需查找分三层:先搜名字、再取详情、最后才调——第 1 层漏掉的,后面救不回来;
  11. 让模型写脚本在沙箱里串着跑,省的是工具结果那一头;沙箱必须没网络、拿不到凭证;
  12. 书里那节「最佳实践」只有一段开场白,正文是空的。

10. 原文地图

主题原书章原文位置
一个客户端只连一台Using Multiple Serverstext/11-fm-using-multiple-servers.txt:6(搜「single client can only connect to a single server」)
会话组是什么、替你管什么Using Multiple Serverstext/11-fm-using-multiple-servers.txt:7(搜「ClientSessionGroup」) · :11(搜「loading all primitives/components」)
会话组挂不上回调Using Multiple Serverstext/11-fm-using-multiple-servers.txt:53(搜「there isn’t support yet」) · :54(搜「for including callbacks when establishing」)
改名钩子Using Multiple Serverstext/11-fm-using-multiple-servers.txt:61(搜「to prevent naming collisions」)
最佳实践只有开场白Using Multiple Serverstext/11-fm-using-multiple-servers.txt:68(搜「Some best practices aren’t yet fully established」)
换模型的自由Supporting Multiple Modelstext/10-fm-supporting-multiple-models.txt:3(搜「it gives you the freedom」)
翻译层的两种写法Supporting Multiple Modelstext/10-fm-supporting-multiple-models.txt:8(搜「for each primitive-model family pair」) · :10(搜「creating a class for each」)
两家格式很像但不同Supporting Multiple Modelstext/10-fm-supporting-multiple-models.txt:34(搜「the Anthropic and OpenAI tool formats are so similar」)
OpenAI 也支持直连Supporting Multiple Modelstext/10-fm-supporting-multiple-models.txt:46(搜「calling remote MCP servers」)
Anthropic 的直连与三条代价Example: A Simple Host Applicationtext/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 Applicationtext/06-fm-example-a-simple-host-application.txt:168(搜「Resource filtering」)

Footnotes

  1. 出处:「Using Multiple Servers」第 6 段(text/11-fm-using-multiple-servers.txt:6,搜「single client can only connect to a single server」)。同一条规矩第 03 章已经从「Chapter 2. Hosting Clients」第 26 段(text/05-ch02-chapter-2-hosting-clients.txt:26,搜「a single client can only talk to a single」)引过一次。

  2. 出处:「Using Multiple Servers」第 7 段(text/11-fm-using-multiple-servers.txt:7,搜「ClientSessionGroup」)与第 11 段(text/11-fm-using-multiple-servers.txt:11,搜「loading all primitives/components」)。原文说它既能接管一个已有的会话,也能自己新建一个;连上之后把那台服务器提供的工具、资源、话术全读进自己的属性里,还记着每个工具属于哪一次会话。 2

  3. 出处:「Using Multiple Servers」第 53 段(text/11-fm-using-multiple-servers.txt:53,搜「there isn’t support yet」)与第 54 段(text/11-fm-using-multiple-servers.txt:54,搜「for including callbacks when establishing」)。原文给的变通办法是去访问会话对象的「私有」回调属性,并加了一句 should be used sparingly(应当少用)。 2

  4. 出处:「Using Multiple Servers」第 61 段(text/11-fm-using-multiple-servers.txt:61,搜「to prevent naming collisions」)。原文说这个钩子就是一个普通函数:收下组件名和一个描述来源的对象,返回改好的新名字。

  5. 补充(不在书里):官方规范在讲服务器身份时专门加了一条提醒——serverInfo 是服务器自报的,协议不做验证,它只用于显示、记录和排错;客户端不应当据此改变行为,也不应当用它做安全判断。来源:MCP 官方规范服务器发现页 https://modelcontextprotocol.io/specification/2026-07-28/server/discover(查阅于 2026-08-25)。

  6. 出处:「Example: A Simple Host Application」第 168 段(text/06-fm-example-a-simple-host-application.txt:168,搜「Resource filtering」)。原文把它和认证、模型无关并列为「除了基本操作之外值得实现的三件事」,但三件事在这一版里都没有展开

  7. 出处:「Supporting Multiple Models」第 3 段(text/10-fm-supporting-multiple-models.txt:3,搜「it gives you the freedom」)。原文强调:尽管本章的例子都用 Anthropic 的模型,有了 MCP 你并不会被绑在单一模型上。

  8. 出处:「Supporting Multiple Models」第 8 段(text/10-fm-supporting-multiple-models.txt:8,搜「for each primitive-model family pair」)与第 10 段(text/10-fm-supporting-multiple-models.txt:10,搜「creating a class for each」)。原文的示例把工具包成一个自己的类,类里带一个转成 OpenAI 形状的方法;并指出两家在参数表这一项上的结构其实是一样的(第 34 段,text/10-fm-supporting-multiple-models.txt:34,搜「the Anthropic and OpenAI tool formats are so similar」)。

  9. 出处:「Example: A Simple Host Application」第 142 段(text/06-fm-example-a-simple-host-application.txt:142,搜「allow users to access remote MCP servers」)。三条代价分别见第 145 段(text/06-fm-example-a-simple-host-application.txt:145,搜「supports tools and remote MCP servers」)与第 147 段(text/06-fm-example-a-simple-host-application.txt:147,搜「the user to Anthropic models like Claude」)。原文用的是「看起来这可能减少对自建客户端的需求」这样的保留语气。

  10. 出处:「Supporting Multiple Models」第 46 段(text/10-fm-supporting-multiple-models.txt:46,搜「calling remote MCP servers」)。原文只有这一句,没有给出任何取舍分析——上面那条判断块是我们补的。

  11. 补充(不在书里,来自通用知识):「一个英文词约 1.3 个词元」是一条常用的粗估率,来自英文文本按主流切分办法切出来的平均值;中文、代码、以及符号密集的文本会明显偏离它。书里没有给过任何换算率,而本节那笔「150 000 对 20」的账要读成「工具说明书是用户那句话的七千多倍」,靠的正是它。要准确的数,只能拿你实际用的那个模型自带的切分工具去数一遍。

  12. 补充(不在书里):官方客户端最佳实践文档给出的对照是:把全部工具定义提前塞进去约耗 150 000 词元,而按需查找只用约 2 000 词元;建议把切换阈值设成上下文窗口的 1%–5%;三层做法是 search_tools(名字加一行说明)→ get_tool_details(单个工具的完整格式说明书)→ 真正调用;检索可用关键词、向量语义、小模型代挑或混合四种路子;并且可以把「按需」推广到服务器本身——用到哪台连哪台,用完断开腾地方。来源:https://modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices(查阅于 2026-08-25)。 2 3 4 5

  13. 补充(不在书里):同一份官方文档把这条路称为 programmatic tool calling(也叫 code mode):宿主把工具的格式说明书转成沙箱里可调用的带类型函数,模型写脚本,沙箱执行,只有脚本打印出来的那一句回到模型;文档给的对照是「约 200 词元的脚本 + 约 15 词元的小结」对「十万以上词元的中间结果」。安全要求包括:沙箱无网络、凭证只在宿主手里、每次调用仍要单独过授权、限制超时与内存、对输出做截断与校验。来源同脚注 11。 2

  14. 出处:「Using Multiple Servers」第 68 段(text/11-fm-using-multiple-servers.txt:68,搜「Some best practices aren’t yet fully established」)。这一段是「Best Practices」这一节的全部内容,也是整本书正文的最后一段——后面接的就是作者简介。