反过来 — 客户端能给服务器什么
这一章讲三件事: 客户端能向服务器开放哪几样东西;为什么其中一样必须拦一道人工确认; 以及这几样东西在代码里的形状为什么完全一致。
它在全书链条里的位置: 前三章方向都是「客户端向服务器要」。 这一章把箭头掉过来。它也是全书里最容易让你直接破财的一章。
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 | ❌ 根本不沾边 | 照样读到 |
第二行和第三行是这一节的全部要点: 你报了根目录,服务器越界读了别的路径, 客户端不会收到任何错误,用户也看不到任何提示——因为这条路上没有任何一处会去比对路径。
书里两处都说得很清楚:
你以为的: 实际是:
┌──────────────┐ ┌──────────────┐
│ 根目录 = 围栏 │ │ 根目录 = 路牌 │
│ 越界 → 被拦下 │ │ 越界 → 没人拦 │
└──────────────┘ └──────────────┘
图说:真正的把关不在协议这一层,在「用户决定要不要装这台服务器」那一刻。
一台不怀好意的服务器,不会因为你报了根目录就老实。
所以根目录的正确用法是:把它当成「帮好服务器少走弯路」的提示, 而不是「防坏服务器」的锁。 防坏服务器的那些手段在第 10 章。
顺带一句边界:书里这一节只写了三行,标题下面还留着作者自己的「☐ TODO」7。 所以关于根目录,书里能给你的就是上面这两句。
5. 征询:服务器反过来问用户一句
这一节讲一样书里没有的能力,但你今天一定会遇到它。
书里没有,是因 为它在书稿之后才加进协议8。
场景很常见:服务器跑到一半发现缺一条信息——比如上面那台订机票服务器, 需要知道你的常旅客号。在书里那套能力下,它只能报错退出。
后来加的这一样能力叫征询——服务器可以在处理一次请求的过程中, 要求客户端替它向用户问一句8。它有两种问法:
| 问法 | 怎么问 | 数据经不经过客户端 |
|---|---|---|
| 表单式 | 服务器给一份「我要这几个字段」的说明,客户端弹一个表单 | 经过 |
| 跳转式 | 服务器给一个网址,客户端把用户带过去 | 不经过(除了网址本身) |
这两种的分界线是一条硬规矩,不是风格选择:
服务器「禁止」用表单式去要密码、接口密钥、通行凭据、支付凭证这类东西; 这类交互「必须」走跳转式8。
理由很直白:表单式的数据会经过客户端。 你的口令没理由让一个第三方客户端看见—— 它应当直接交给认它的那一方。这条规矩在第 10 章会以另一个形式再出现一次。
客户端这一侧的义务规范也写死了:必须让用户看清楚是哪一台服务器在要东西, 必须提供明确的拒绝和取消,表单式还要让用户能先改再发8。
6. 这几样能力的共同写法:留一个回调
这一节告诉你一件省力的事:上面三样东西,代码形状是同一个。
那个形状叫回调——你写好一个函数交给别人,别人在某个时刻替你把它叫起来。 你不知道它什么时候被叫,你只负责写好「被叫的时候干什么」。
书里把这个套路总结成三步,而且明说这个套路是重复的9:
① 在你的客户端类里写一个函数,等着被服务器触发的时候调用
② 让这个函数的参数长相符合协议规定的样子(每样能力各有一份规定)
③ 建会话 的时候,把这个函数从对应的那个参数口塞进去
图说:三样能力、三个参数口,函数体不同,挂法完全一样。
第 08 章的日志也用同一个套路——所以学会一次就够了。
作者对这个套路的评价是:「就这样,当然,绝大部分力气都花在第 ① 步」9—— 函数体里干什么,取决于你的产品要怎么和用户交互。
这个套路会在第 09 章撞上一堵墙: 官方工具包里那个「一次管好几台服务器」的东西, 当时还不支持在建会话时挂回调。那一章讲这件事的代价。
7. 边界:这几样在最新规范里全换了身份
这一节必须说清楚,否则你会照着这一章去写一套已经过时的东西。
2026-07-28 那版规范对本章的三样能力做了两件事:
第一件:采样和根目录被标记为弃用——意思是它还在规范里、还能用, 但已经排进了移除队列,新写的东西不应该再用它10。规范给的替代路子是:
| 原来用什么 | 现在应该改用什么 |
|---|---|
| 采样(借客户端的模型) | 直接对接模型厂商的接口 |
| 根目录(报几个目录过去) | 把目录或文件当成工具的参数传进去,或者写在服务器的配置里 |
规范同时给了一个时间承诺:从这一版发布起至少保留十二个月,之后才有资格被移除10。
第二件:服务器向客户端发问的方式被整个换掉了。 本章第 2 节那条「服务器 → 客户端」的请求,在新规范里不再存在。 新的形状是:服务器把这次调用先答成「我还缺这几样东西」, 客户端补齐之后换一个新编号、把原来那条请求整条重发一遍11。
征询没有被弃用,但它同样改走这条新路。
为什么要这么改?一句话说不清,整条推导在第 11 章。
8. 可带走的
- 方向可以反过来:客户端能向服务器开放采样和根目录两样能力;
- 不声明就等于没有——声明发生在第 04 章那次打招呼里;
- 采样 = 服务器借用你连着的那个模型,省掉它自己拿钥匙、付钱、绑定厂商这三件事;
- 借一次的四步:服务器发问 → 你的函数被叫醒 → 你替它调模型 → 把回答交回去;
- 账单记在你头上——所以必须有人工确认,而且必须拦在请求发给模型之前;
- 书里那段示例代码故意没做确认,作者下一句就说了「实际中不要这么写」;
- 根目录是路牌不是围栏:规范说服务器「应当」尊重,但不尊重也没人拦;真正的把关在用户挑服务器那一刻;
- 征询是后来加的:服务器可以中途要客户端替它问用户一句;要密码密钥必须走不经过客户端的那条路;
- 三样能力的写法是同一个:写函数 → 对上参数长相 → 建会话时挂上去;
- 采样和根目录已被标记为弃用(至少保留十二个月),服务器发问的方式也被整个换掉——理由在第 11 章。
9. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 两样能力各是什么 | Providing MCP Client Capabilities | text/09-fm-providing-mcp-client-capabilities.txt:8(搜「request chat completions from the LLM」) · :11(搜「roots, which indicate to servers」) |
| 必须声明、声明在哪一步 | Providing MCP Client Capabilities | text/09-fm-providing-mcp-client-capabilities.txt:14(搜「you are required to advertise」) |
| 三步套路 | Providing MCP Client Capabilities | text/09-fm-providing-mcp-client-capabilities.txt:25(搜「Pass the callback function to the ClientSession constructor」) |
| 采样的定义与两个用途 | Providing MCP Client Capabilities | text/09-fm-providing-mcp-client-capabilities.txt:33(搜「allows MCP server to make use of」) · :36(搜「ask the LLM a question」) |
| 人工确认那条警告 | Providing MCP Client Capabilities | text/09-fm-providing-mcp-client-capabilities.txt:50(搜「that you are paying for」) · :52(搜「human-in-the-loop」) |
| 示例代码故意没做确认 | Providing MCP Client Capabilities | text/09-fm-providing-mcp-client-capabilities.txt:107(搜「request permission from the user before」) |
| 根目录不被强制 | Example: A Simple Host Application | text/06-fm-example-a-simple-host-application.txt:126(搜「Roots are not strictly enforced」) |
| 协议里服务器「应当」尊重 | Providing MCP Client Capabilities | text/09-fm-providing-mcp-client-capabilities.txt:122(搜「servers SHOULD respect boundaries」) · :117(搜「TODO」) |