第 4 章 — 客户端原语:Agentic 行为与控制
LLM Primer IV: Designing AI Cognition with MCP 章节走读的第四篇。Server 原语暴露 server 能给什么;client 原语暴露 host 愿意借回去什么 — 每一笔借出都是用户授予的能力,也是 host 替用户接住的风险。
这一章为什么存在
一个只暴露 resource、prompt、tool 的 server 对它的 host 一无所知:不知道 host 跑的是什么模型、不知道 host 看得到哪些文件、不知道用户人还在不在键盘前。对很多集成,那堵墙就是要这么高。可还有一整类有用的行为,墙太高就做不了。一个要总结长文档的 server 不应该被迫自己出货一份 LLM。一个要在某个项目上动手的 server 应该知道是哪一个项目。一个在执行破坏性操作前要拿用户同意的 server 应该能开口问。Client 原语就是 MCP 在那堵墙上打的几个小、受控的洞。
三个原语 — Sampling、Roots、Elicitation — 各自以一种精确、协商过的方式,把 server 的触手伸进 host 的领地。每一个也是 host 替用户接下的一个安全表面,每一个还跟其他原语组合,做出比单项之和更 agentic 的行为。
4.1 Sampling:借 host 的脑子
Sampling 启用后,server 可以请求 host 跑一次推理,把结果返给它。Server 不出货模型、不拿 API key、也根本不知道 host 实际用的是哪个模型 — 它发消息加一些软偏好(成本、速度、智力),由 host 跑这一次。从 server 的视角看,host 变成了一个通用 LLM 端点。
杠杆是实的。一个文档库 server 可以在自己逻辑里跑一些小型推理步骤 — 检索、排序、比较 — 既不向 host 泄露领域知识,也不用自己出货推理。风险同样是实的。一个恶意 server 可以用 host 的模型 — 也是用户的预算 — 干用户从没批准的事。经典攻击是把 sampling 载荷伪装成用户指令。Host 是唯一的防线,所以成熟的 host 默认拒绝 sampling,除非用户对某个具体 server 显式开启过同意、把每一次调用的 prompt 暴露给用户检查、每会话设次数上限、把成本算到用户的钱包上而不是 server 的。
更微妙的一点:sampling 能把一个内部 agentic 循环封装起来。在 host 看是一次 sampling 调用;在 server 内部,跑过了一整个子 agent。值得起名的模式是 有界子 agent — 用户授予 N 次调用、T 秒、X 美元的预算;server 在这个信封里爱怎么跑都行,要么返回结果要么优雅地半途交差。Sampling 也不让 server 接触对话历史;它只看到自己发出去的内容,而 host 如果把对话上下文偷偷塞进 sampling 载荷,就削弱了信任边界,不管动机如何。
4.2 Roots:文件系统边界与项目范围
一个 root 是一条 URI — 通常是 file://,虽然规范是通用的 — host 宣布它在范围内。Server 调 roots/list 问 "我在跟什么工作?"。Host 在初始化时宣告 roots 能力,在用户切换项目时发出 notifications/roots/list_changed。这个列表就是个列表;没有嵌套权限语言、没有 glob 模式、没有 include/exclude 规则。
这种粗粒度是有意的,而且反映了 MCP 一以贯之的一个设计选择,值得搞清楚。在协议层做细粒度的权限语言会造成虚假的安全感:用户看一长串作用域以为协议在强制,事实上协议挡不住 server 用那个语言没预料的方式作妖。粗粒度的原语配上外部隔离 — 进程边界、容器挂载、操作系统级访问控制 — 对 "强制到底住在哪里" 这件事是诚实的。Root 在协议层是君子协议,在运行时层(配上沙盒)才变得可强制。
两个实际后果。第一,root 变化时,守规矩的 server 会丢掉跟旧 root 关联的缓存状态 — 索引、解析过的 AST、监听的文件订阅 — 否则就会跨项目泄露状态。第二,root 划的是 server 应该看哪儿,不是 server 被允许干什么。授予的 root 里一个敏感文件 — 一份凭据 dump、一份 envfile、一段私人通信 — 依然敏感,有礼貌的 server 依然要对要不要暴露做判断。
4.3 Elicitation:让 server 问问题
Elicitation 三个里最新,也最能反映设计者从第一波 agent 部署里学到的东西。Server 发出 elicitation/create,带一段消息(问题)和一份 requestedSchema(期望答案的形状)。Host 在自己 UI 里渲染那个问题,收集答案,校验,返回。Server 拿到答案继续。
Schema 是让 elicitation 比自由 prompt 更安全的那一层。布尔不能用散文回答;枚举不能给出第四个选项。Host 能在清晰的 UI 里告诉用户在被问的是一个是/否,拒绝其他形状。规范故意把 schema 限制成扁平的基本类型对象 — 真正的表单,请用一个参数 schema 就是那张表单的 tool,让用户在调用前能审查模型填好的值。Elicitation 是给一两个字段的情况用的。
风险形状是钓鱼形:一个表面写着 "系统需要你的 AWS access key 才能继续" 的 server 在试图让用户在错的地方敲下机密。Host 用清晰的归属来缓解 — 每一条 elicitation 都标明发起的 server,跟 assistant 的口吻分开 — 并拒绝看起来像凭据形状的 schema。它正面的镜像是破坏性 tool 的确认模式:一个 tool 的第一份工作是 elicit "你确定吗?",只有在确认后才动手。这是生产里最有效的 MCP 加固做法之一。
4.4 三个组合起来
这三个原语设计上就是要组合的。三样都有的 server 等于把一个完整的 agent runtime 委托给了 host:它能读范围(roots)、对它做推理(sampling)、问澄清问题(elicitation)、通过自己的 tool 动手。这些组合本身有意义。Sampling 加 roots 是自主 agent server;sampling 加 elicitation 是没有文件系统触手的对话型 server;roots 加 elicitation 是确定性的助手。每一项单独是有界的;凑在一起就放大,而 host 的同意 UI 最好的形态,是能给那个组合起个名字给用户,而不是孤立地一项项问同意。
第 4 章接下去会怎么走
Server 原语和 client 原语合起来描述了 host 和 server 互相能做什么的全部。还没讲的是这些消息怎么在线上走。tools/list 和 sampling/createMessage 不是抽象的消息 — 它们跑在传输上,而传输选择悄悄决定了一个 MCP 集成几乎所有运维属性。Server 还要能被找到:一个 host 想用一个 server,得先知道它存在、在哪里、那个声明是不是可信的。第 5 章一起处理这两件事。
明天 — 第 5 章:传输协议与发现。MCP 支持的三种传输 — stdio、SSE、Streamable HTTP — 老实地对比一遍,以及 .well-known/mcp.json 加 Server Card 这一层,怎么把点对点集成变成像生态的东西。