跳到主要内容

反过来 — 客户端能给服务器什么

这一章讲三件事: 客户端能向服务器开放哪几样东西;为什么其中一样必须拦一道人工确认; 以及这几样东西在代码里的形状为什么完全一致。

它在全书链条里的位置: 前三章方向都是「客户端向服务器要」。 这一章把箭头掉过来。它也是全书里最容易让你直接破财的一章。

1. 到这里为止都是客户端在拿,现在轮到它给

这一节回答:MCP 是单向的吗?

不是。书里在这一章开门见山:客户端能给服务器的主要有两样—— 一样是让服务器用上客户端连着的那个模型, 另一样是告诉服务器,宿主应用的文件系统里哪几块是它能碰的1

但有一条前提必须先说,否则你写完代码会发现什么都没发生:

你提供了什么,必须先声明出去;不声明,服务器根本不会来问。

书里写得很硬:服务器不是必须用、也不是必须尊重这些能力, 但只要你的客户端提供了,你就「必须」把这件事宣告出去—— 而宣告发生在客户端与服务器连接的初始化阶段2, 也就是第 04 章那三条报文里的第一条。

所以这一章和第 04 章是绑在一起的: 你在那里往 capabilities 里填了什么, 决定了这里会不会被调用。

2. 采样:服务器借用你的模型

这一节是本章主走查的主体;它在第 4 节还有一段续(根目录那一样能力走在那儿)。

这一样能力的名字叫采样——服务器通过客户端,去用客户端和宿主应用连着的那个模型3

为什么服务器不自己接一个模型? 因为那样它就得自己拿一把模型的钥匙、自己付钱、 自己绑定某一家厂商。借客户端的,这三件麻烦事全省了。

书里给的用途有两个:让服务器内部也能挑工具、用工具; 以及服务器向模型问一个问题,拿答案去填自己要提供的那段话3

下面这条走查里的服务器、航班、价格、目录名和文件名,全部是我们为演示编的,不是真实数值。 书里这一节只有代码,没有场景。

场景: 一台订机票服务器。用户说:「帮我订下周二去上海最便宜的直飞。」

第几步谁在动手里的具体东西
0客户端建会话时声明了两样:我支持采样;我的根目录是 file:///Users/you/trips
1模型 → 宿主挑了 search_flights 工具,宿主替它调服务器
2服务器照根目录去读 file:///Users/you/trips/2026-09-shanghai.txt,读到一行「预算上限 ¥1 500」
3服务器查到 3 个航班:¥780(一次经停)、¥1 240(直飞)、¥1 460(直飞)
4服务器 → 客户端它自己没有模型,于是回头借:「这三个里哪个最符合『最便宜的直飞』、且不超 ¥1 500?最多用 200 个词。」
5客户端那个挂上去的函数被叫醒,先拦一道人工确认(见下一节)
6客户端 → 模型用户点了允许,请求发给模型
7模型 → 客户端 → 服务器「第二个,¥1 240 那班」——它排除了 ¥780 那班,因为要经停
8服务器拿着答案跑完 search_flights,把最终结果交回客户端

第 0 步一次报了两样东西,后面各走一条线: 采样那一样走第 4 到第 7 步(本节和下一节), 根目录那一样走第 2 步,而它的真面目要到第 4 节才露出来。

看清楚第 4 步到第 7 步这一借的四步:服务器发问 → 客户端的函数被叫醒 → 客户端替它调模型 → 把模型的回答交回服务器。

而这四步里有一件事最要命:第 6 步那次模型调用,账单记在客户端头上。 不是服务器的账,是你的账。¥780 那班被排除掉这个判断,是花你的钱做出来的。

3. 这里必须站一个人:确认要拦在哪一步

这一节回答:那道人工确认放在哪都行吗?不行。

书里这条警告的措辞非常罕见——作者把自己和 Anthropic 并列:

因为把「你的应用正在用、而且是你在付钱的那个模型」交给一台外部服务器有风险, 我本人和 Anthropic 都强烈建议:在服务器被允许通过你的客户端和应用向模型发请求之前, 实现某种形式的人工确认流程4

这条警告里有一个词值得单独拎出来:「在……之前」。

回看上一节那张表:账单产生在第 6 步。 所以确认必须拦在第 5 步和第 6 步之间。

确认拦在哪结果
第 5 步之后、第 6 步之前✅ 用户说不,一分钱不花
第 7 步之后(拿到模型回答再问用户)钱已经花了,用户点不点都一样

这种「关键动作前必须有人点头」的做法有个名字:人在回路—— 在自动流程里留一个必须由人做决定的口子

书里在代码示例后面又强调了一遍,而且用词更硬: 「实际中不要这么写。你的处理函数应当在把请求发给应用的模型之前,先向用户申请许可」5书里那段示例代码是故意没做确认的,作者在下一句就自己指出来了。

4. 根目录:告诉服务器「只准在这几个目录里动」

这一节要拆掉一个很常见的误解,而拆它的办法是把主走查再往前走一步。

第二样能力叫根目录——客户端告诉服务器,宿主应用的文件系统里哪几块是相关的1。 比如你在编辑器里打开了一个项目,客户端就把这个项目的目录报给服务器。

误解是:很多人以为这是一道权限限制,越界会被拦下来。不是。

主走查续:同一台订机票服务器,再读一个文件

回到第 2 节那条走查。第 0 步报出去的是 file:///Users/you/trips, 第 2 步服务器照着读了 file:///Users/you/trips/2026-09-shanghai.txt——规规矩矩。

现在看第 2 步之后,它还能干什么(路径同样是我们为演示编的):

服务器要读的路径在不在根目录里实际发生了什么
file:///Users/you/trips/2026-09-shanghai.txt✅ 在读到,拿到「预算上限 ¥1 500」
file:///Users/you/trips/../.ssh/id_rsa❌ 越出去了照样读到——协议这一层一个字都没拦
file:///etc/passwd❌ 根本不沾边照样读到

第二行和第三行是这一节的全部要点: 你报了根目录,服务器越界读了别的路径, 客户端不会收到任何错误,用户也看不到任何提示——因为这条路上没有任何一处会去比对路径。

书里两处都说得很清楚:

  • 「根目录不是被严格强制执行的,所以要靠服务器自己去尊重它, 也要靠客户端的用户在用一台服务器之前先审一审它」6;
  • 「在协议里,服务器『应当』尊重这些边界,但它们并不是必须这么做」7
你以为的: 实际是:
┌──────────────┐ ┌──────────────┐
│ 根目录 = 围栏 │ │ 根目录 = 路牌 │
│ 越界 → 被拦下 │ │ 越界 → 没人拦 │
└──────────────┘ └──────────────┘

图说:真正的把关不在协议这一层,在「用户决定要不要装这台服务器」那一刻。
一台不怀好意的服务器,不会因为你报了根目录就老实。

所以根目录的正确用法是:把它当成「帮好服务器少走弯路」的提示, 而不是「防坏服务器」的锁。 防坏服务器的那些手段在第 10 章。

顺带一句边界:书里这一节只写了三行,标题下面还留着作者自己的「☐ TODO」7。 所以关于根目录,书里能给你的就是上面这两句。

5. 征询:服务器反过来问用户一句

这一节讲一样书里没有的能力,但你今天一定会遇到它。

书里没有,是因为它在书稿之后才加进协议8

场景很常见:服务器跑到一半发现缺一条信息——比如上面那台订机票服务器, 需要知道你的常旅客号。在书里那套能力下,它只能报错退出。

后来加的这一样能力叫征询——服务器可以在处理一次请求的过程中, 要求客户端替它向用户问一句8。它有两种问法:

问法怎么问数据经不经过客户端
表单式服务器给一份「我要这几个字段」的说明,客户端弹一个表单经过
跳转式服务器给一个网址,客户端把用户带过去不经过(除了网址本身)

这两种的分界线是一条硬规矩,不是风格选择:

服务器「禁止」用表单式去要密码、接口密钥、通行凭据、支付凭证这类东西; 这类交互「必须」走跳转式8

理由很直白:表单式的数据会经过客户端。 你的口令没理由让一个第三方客户端看见—— 它应当直接交给认它的那一方。这条规矩在第 10 章会以另一个形式再出现一次。

客户端这一侧的义务规范也写死了:必须让用户看清楚是哪一台服务器在要东西, 必须提供明确的拒绝和取消,表单式还要让用户能先改再发8

6. 这几样能力的共同写法:留一个回调

这一节告诉你一件省力的事:上面三样东西,代码形状是同一个。

那个形状叫回调——你写好一个函数交给别人,别人在某个时刻替你把它叫起来。 你不知道它什么时候被叫,你只负责写好「被叫的时候干什么」。

书里把这个套路总结成三步,而且明说这个套路是重复的9:

① 在你的客户端类里写一个函数,等着被服务器触发的时候调用
② 让这个函数的参数长相符合协议规定的样子(每样能力各有一份规定)
③ 建会话的时候,把这个函数从对应的那个参数口塞进去

图说:三样能力、三个参数口,函数体不同,挂法完全一样。
第 08 章的日志也用同一个套路——所以学会一次就够了。

作者对这个套路的评价是:「就这样,当然,绝大部分力气都花在第 ① 步」9—— 函数体里干什么,取决于你的产品要怎么和用户交互。

这个套路会在第 09 章撞上一堵墙: 官方工具包里那个「一次管好几台服务器」的东西, 当时还不支持在建会话时挂回调。那一章讲这件事的代价。

7. 边界:这几样在最新规范里全换了身份

这一节必须说清楚,否则你会照着这一章去写一套已经过时的东西。

2026-07-28 那版规范对本章的三样能力做了两件事:

第一件:采样和根目录被标记为弃用——意思是它还在规范里、还能用, 但已经排进了移除队列,新写的东西不应该再用它10。规范给的替代路子是:

原来用什么现在应该改用什么
采样(借客户端的模型)直接对接模型厂商的接口
根目录(报几个目录过去)把目录或文件当成工具的参数传进去,或者写在服务器的配置里

规范同时给了一个时间承诺:从这一版发布起至少保留十二个月,之后才有资格被移除10

第二件:服务器向客户端发问的方式被整个换掉了。 本章第 2 节那条「服务器 → 客户端」的请求,在新规范里不再存在。 新的形状是:服务器把这次调用先答成「我还缺这几样东西」, 客户端补齐之后换一个新编号、把原来那条请求整条重发一遍11

征询没有被弃用,但它同样改走这条新路。

为什么要这么改?一句话说不清,整条推导在第 11 章。

8. 可带走的

  1. 方向可以反过来:客户端能向服务器开放采样和根目录两样能力;
  2. 不声明就等于没有——声明发生在第 04 章那次打招呼里;
  3. 采样 = 服务器借用你连着的那个模型,省掉它自己拿钥匙、付钱、绑定厂商这三件事;
  4. 借一次的四步:服务器发问 → 你的函数被叫醒 → 你替它调模型 → 把回答交回去;
  5. 账单记在你头上——所以必须有人工确认,而且必须拦在请求发给模型之前;
  6. 书里那段示例代码故意没做确认,作者下一句就说了「实际中不要这么写」;
  7. 根目录是路牌不是围栏:规范说服务器「应当」尊重,但不尊重也没人拦;真正的把关在用户挑服务器那一刻;
  8. 征询是后来加的:服务器可以中途要客户端替它问用户一句;要密码密钥必须走不经过客户端的那条路;
  9. 三样能力的写法是同一个:写函数 → 对上参数长相 → 建会话时挂上去;
  10. 采样和根目录已被标记为弃用(至少保留十二个月),服务器发问的方式也被整个换掉——理由在第 11 章。

9. 原文地图

主题原书章原文位置
两样能力各是什么Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:8(搜「request chat completions from the LLM」) · :11(搜「roots, which indicate to servers」)
必须声明、声明在哪一步Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:14(搜「you are required to advertise」)
三步套路Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:25(搜「Pass the callback function to the ClientSession constructor」)
采样的定义与两个用途Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:33(搜「allows MCP server to make use of」) · :36(搜「ask the LLM a question」)
人工确认那条警告Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:50(搜「that you are paying for」) · :52(搜「human-in-the-loop」)
示例代码故意没做确认Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:107(搜「request permission from the user before」)
根目录不被强制Example: A Simple Host Applicationtext/06-fm-example-a-simple-host-application.txt:126(搜「Roots are not strictly enforced」)
协议里服务器「应当」尊重Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:122(搜「servers SHOULD respect boundaries」) · :117(搜「TODO」)

Footnotes

  1. 出处:「Providing MCP Client Capabilities」第 8 段(text/09-fm-providing-mcp-client-capabilities.txt:8,搜「request chat completions from the LLM」)与第 9 段(text/09-fm-providing-mcp-client-capabilities.txt:9,搜「roots, which indicate to servers」)。原文对根目录的描述是:向服务器指明宿主应用的文件系统里哪些区域是它可以访问的。 2

  2. 出处:「Providing MCP Client Capabilities」第 14 段(text/09-fm-providing-mcp-client-capabilities.txt:14,搜「you are required to advertise」)。原文用的是 required(必须)这个词,和前半句的「服务器不是必须用或尊重」形成对照:给不给由你,给了就必须说。

  3. 出处:「Providing MCP Client Capabilities」第 33 段(text/09-fm-providing-mcp-client-capabilities.txt:33,搜「allows MCP server to make use of」)与第 36 段(text/09-fm-providing-mcp-client-capabilities.txt:36,搜「ask the LLM a question」)。补充(不在书里):官方规范在同一件事上多给了一句关键的好处——服务器不需要自己的模型接口密钥。来源:MCP 官方规范采样页 https://modelcontextprotocol.io/specification/2026-07-28/client/sampling(查阅于 2026-08-25)。 2

  4. 出处:「Providing MCP Client Capabilities」第 50 段(text/09-fm-providing-mcp-client-capabilities.txt:50,搜「that you are paying for」)与第 52 段(text/09-fm-providing-mcp-client-capabilities.txt:52,搜「human-in-the-loop」)。补充(不在书里):官方规范的说法与作者一致——「应当始终有一个人在回路里,并且有权拒绝采样请求」,还建议让用户在发出前能查看并修改那段话、生成的回答也应先给用户过目。来源同脚注 3。

  5. 出处:「Providing MCP Client Capabilities」第 107 段(text/09-fm-providing-mcp-client-capabilities.txt:107,搜「request permission from the user before」)。原文的前一句是「Do not do this in practice」(实际中不要这么写)——作者是在提醒读者他刚给的那段示例代码不能照抄。

  6. 出处:「Example: A Simple Host Application」第 126 段(text/06-fm-example-a-simple-host-application.txt:126,搜「Roots are not strictly enforced」)。原文把责任分成两头:服务器自己去尊重,以及客户端的用户在使用一台服务器之前先审一审它

  7. 出处:「Providing MCP Client Capabilities」第 122 段(text/09-fm-providing-mcp-client-capabilities.txt:122,搜「servers SHOULD respect boundaries」)。这一节的标题下面还留着作者的待办标记(第 117 段,text/09-fm-providing-mcp-client-capabilities.txt:117,搜「TODO」)——整节只有三行,而且其中一行注明是从协议里抄过来的。 2

  8. 补充(不在书里):征询(elicitation)在官方规范里分表单式(form)和跳转式(url)两种。规范用「MUST NOT / MUST」写死了一条:服务器禁止用表单式索取密码、接口密钥、访问令牌或支付凭证这类敏感信息,必须改用跳转式;姓名、邮箱这类一般信息不在禁止之列。客户端一侧则必须让用户看清是哪台服务器在要、必须提供拒绝与取消、表单式还要允许用户先改再发。来源:MCP 官方规范征询页 https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation(查阅于 2026-08-25)。 2 3 4

  9. 出处:「Providing MCP Client Capabilities」第 25 段(text/09-fm-providing-mcp-client-capabilities.txt:25,搜「Pass the callback function to the ClientSession constructor」)。三步的原文依次是:写一个会被服务器触发时调用的函数;确保它符合该能力规定的协议;把它从对应的参数口传进会话的构造函数。作者接着说「就这样——当然,绝大部分力气在第一步」。 2

  10. 补充(不在书里):2026-07-28 版规范把根目录(Roots)、采样(Sampling)和日志(Logging)三样一起标记为 Deprecated,依据是 SEP-2577。规范给的迁移路径是:根目录改为「通过工具参数、资源地址或服务器配置传目录/文件」,采样改为「直接对接模型厂商接口」,日志改为「stdio 下写标准错误流,或用 OpenTelemetry」。同时规定 Deprecated 状态下至少保留十二个月才有资格被移除。来源:MCP 规范弃用登记页 https://modelcontextprotocol.io/specification/2026-07-28/deprecated(查阅于 2026-08-25)。 2

  11. 补充(不在书里):2026-07-28 版引入了「多轮往返请求」模式,取代了原先由服务器主动发起请求的做法。规范用 MUST 写死:服务器必须用这个模式来发送 roots/listsampling/createMessageelicitation/create 这类请求,原先那种服务器主动发请求的做法不再被支持,这是一次破坏性变更。来源:MCP 官方规范多轮往返请求页 https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr(查阅于 2026-08-25)。