深入浅出 MCP 系列教程(四):进阶能力
本系列共 5 篇。前三篇覆盖了协议基础、三大原语和传输层,已构成一个最简可用的 MCP 服务。本篇讲解协议里用于双向协作、安全约束、异步反馈、扩展的高阶能力:Sampling、Roots、通知体系、精细化错误模型与扩展机制。这些能力全部是可选 capability,在
initialize阶段协商开启,不是必选。
一、Sampling —— Server 反向调用大模型
Sampling 是 MCP 最有特色、也最容易被低估的能力。常规流程是 Host 调用 Server;Sampling 反过来,由 MCP Server 发起请求,请 Host 调用它持有的大模型,再把模型返回结果交还给 Server。
1.1 适用场景
普通 function calling 是:LLM → 调用工具。Sampling 是:工具服务 → 请求 LLM 帮忙做推理。典型场景:
- 代码解析 Server:拿到一段代码,请求模型做摘要、风险识别;
- 文档 Server:读取文档后,让模型抽取要点;
- 工单 Server:拿到工单上下文,调用模型生成初步回复;
- 多 Agent 编排:一个 MCP Server 本身作为子 Agent,需要上层 Host 的模型能力完成子任务。
关键约束:Sampling 不是”Server 自己持有模型 Key”。模型密钥、模型选择、上下文配额、用户权限全部在 Host 一侧。Server 只负责描述:我想要什么样的模型输出。
1.2 开启前提
必须同时满足两个条件:
- Client(Host)在
initialize的capabilities里声明sampling: {}; - Server 在响应的
capabilities里声明支持接收采样请求。
只有两边都声明,会话内才允许使用 sampling/createMessage。如果 Host 不支持 sampling,Server 绝对不能发起这个调用,否则收到 -32601 方法不存在错误。
1.3 请求报文(Server → Host)
1 | { |
字段说明:
messages:消息数组,支持 text / image / resource 内容;modelPreferences:偏好提示,不是强制。Host 可以忽略,按照用户配置、配额、可用模型自行选择;maxTokens、temperature、stopSequences:推理参数,Host 有权限做上限拦截;- 没有
stream参数:标准 sampling 接口默认返回完整消息,不强制流式。
1.4 Host 返回报文
1 | { |
stopReason 可选枚举:endTurn、maxTokens、stopSequence。
1.5 安全与审批流(非常重要的工程点)
- Host 拥有最终否决权:收到
sampling/createMessage,Host 可以直接调用模型返回结果、弹出审批让用户确认、或直接拒绝返回错误; - 计费、token 消耗归属 Host;多租户 Host 必须按用户隔离统计;
- 提示注入风险:Server 传入的 messages 属于外部不可信输入,Host 需要做边界隔离;
- 禁止 Server 通过 sampling 绕过 Host 的安全策略,例如内容审核必须统一经过。
1.6 典型误区
| 误区 | 正解 |
|---|---|
| Sampling 代表 Server 可以绕过 Host 随便调用模型 | Host 可以拦截、拒绝、替换模型、覆盖参数 |
| modelPreferences 是硬性要求 | 只是建议,Host 可以无视 |
| Sampling 等同于把模型 Key 交给 Server | Server 永远拿不到密钥 |
二、Roots —— 资源访问边界控制
Roots 是 MCP 内置的沙箱机制,专门约束 Resources 的读取范围,解决一个核心风险:如果 filesystem MCP Server 可以读取本机任意文件,一旦被 LLM 诱导,很容易泄露密钥、配置文件。
Roots 是 Host 下发给 Server 的一组允许访问的根 URI 列表。
2.1 开启前提
握手时 Client(Host)声明 roots: {} 能力,Server 识别之后就必须遵守 Host 下发的根列表。Roots 属于 Host → Server 的单向配置,由 Host 主动推送通知,不是一次性握手参数。
2.2 下发通知
这是一条通知(无 id),Host 在握手完成之后随时可以推送,包括后续动态更新权限:
1 | { |
uri:根地址;name:人类可读备注;- 可以下发多个 roots;空数组代表:不允许访问任何资源。
Server 收到通知后,更新自己的访问白名单。
2.3 校验规则
Server 需要判断:目标 URI 是否以其中一个 root URI 为前缀(按 URI 语义,不是简单字符串 startsWith)。
允许 root:file:///home/user/work
- ✅
file:///home/user/work/readme.md - ✅
file:///home/user/work/src/index.js - ❌
file:///home/user/.ssh/id_rsa(不在根下) - ❌
file:///etc/passwd
校验是 Server 的责任,协议约定 Server 主动拒绝越界访问;Host 也可以叠加一层校验作为第二层防线。
2.4 动态变更
Host 在会话存活期间可以多次推送 roots/list_changed:增加目录、删除目录、替换成全新列表、设置为空冻结所有资源读取。
典型场景:用户在客户端 UI 里勾选/取消允许访问的文件夹,Host 立刻推送更新给 filesystem server。
2.5 适用范围与局限
- Roots 主要面向
file://,但语义上是通用 URI,postgres://、git://等 scheme 也可以实现自己的 root 校验; - Roots 只约束 Resources,协议层面不强制约束 Tools;工具的权限校验需要 Server / Host 单独实现;
- Roots 是语义约定,不是操作系统级沙箱;恶意实现的 Server 可以无视,所以 Host 仍需要不信任不受控的第三方 MCP Server。
三、完整通知体系
MCP 大量异步事件依靠 Notification(不带 id,不需要回复)。通知分为两类:
- 标准通知:协议规范内定义,所有兼容实现语义统一;
- 自定义通知:以
x-前缀,用于业务扩展。
3.1 会话基础通知
initialized:Client → Server,握手完成;notifications/roots/list_changed:Host → Server,根目录权限更新。
3.2 Resources 相关通知
notifications/resources/updated:Server → Host,某个已订阅的资源发生变更。
再次强调:通知只代表”变了”,不会携带新内容;Host 需要最新内容时主动调用
resources/read。
3.3 Tools / Prompts 列表变更通知
notifications/tools/list_changed:Server → Host,可用工具列表动态变化。适用场景:Server 的工具根据登录用户、配置、插件加载动态增减。Host 收到通知后主动调用 tools/list 刷新缓存。同理有 notifications/prompts/list_changed。
3.4 运行时反馈通知(日志 + 进度)
日志通知 notifications/logging/message:Server 把结构化日志推送给 Host,在 UI 展示日志面板:
1 | { |
level 取值:debug / info / warning / error。stdio 模式下 stderr 日志和协议内 logging 通知可以共存。
进度通知 notifications/progress:用于长时间任务(大文件扫描、批量导入、数据库备份),给前端进度条:
1 | { |
progressToken:任务令牌,由发起请求的一方生成,绑定这个任务;progress:0~1 比例;total:可选总量。
进度通知是非常重要的用户体验能力:没有它,Host 只能傻傻等待,不知道工具是不是卡死。
3.5 通知的通用约束
- 通知不保证送达、不保证顺序(网络代理、重连会丢);
- 通知不要求应答,接收方可以选择忽略;
- 通知不能用来传递核心返回结果,结果必须放在 Response;
- 会话断开之后,所有未送达通知全部丢弃,重连不会补发历史通知。
四、精细化错误模型
4.1 三层错误
- 传输层错误:连接断开、解析失败、缓冲区溢出、HTTP 401/404,由传输层向上抛出;
- JSON-RPC 标准错误:协议方法层面错误;
- 业务语义错误:方法存在、参数合法,但业务执行失败(数据库查询失败、文件不存在、权限不足)。
标准 JSON-RPC 错误码:
| code | 含义 |
|---|---|
| -32600 | Invalid Request:消息格式非法 |
| -32601 | Method not found:方法不存在,或能力没有协商开启 |
| -32602 | Invalid params:参数不符合 schema / 必填缺失 |
| -32603 | Internal error:服务内部异常 |
| -32700 | Parse error:JSON 解析失败 |
业务自定义错误:官方建议从 -32000 开始分配,不要占用预留段。
4.2 错误携带附加数据
error 对象可以增加 data 字段,携带结构化附加信息:
1 | { |
Host 可以读取 data,给用户展示更友好的报错、告警。
4.3 错误处理策略(工程最佳实践)
- Parse error:直接丢弃这条消息,打日志,会话继续;
- Method not found:代表能力协商不一致,Host 应当停止调用该系列接口;
- 业务执行失败:把错误文本原样送入 LLM,让模型感知失败,进行重试或调整策略;
- 致命内部错误:Server 可以主动关闭会话;stdio 模式一般直接退出进程。
一个坑:不要把业务异常做成通知;调用失败必须返回 error response,不能只发一条日志通知了事。发起方需要知道调用结果。
五、协议版本与扩展机制
MCP 的扩展哲学:优先能力协商,而不是版本号强绑定;标准之外允许自定义扩展,但有严格命名规范。
5.1 protocolVersion
initialize 里的 protocolVersion,当前稳定版本 0.1.0。版本兼容规则:
- 主版本变更代表不兼容;次版本、补丁版本向后兼容;
- Server 收到不支持的主版本,可以拒绝握手;
- 新增能力走 capabilities,而不是靠升级版本号强制开启。
同一个版本号内,可以出现”有的 Server 支持 sampling,有的不支持”,靠能力交集区分,而不是版本。
5.2 扩展命名规范(强制)
所有不属于官方标准的 method、通知,必须加前缀 x-:
- ✅
x-query-metrics(自定义请求) - ✅
notifications/x-job-finished(自定义通知) - ❌
query-metrics(无前缀,未来官方如果新增同名方法,直接冲突)
5.3 自定义方法的限制
- 自定义方法同样走 JSON-RPC request / response;
- 自定义能力不能在顶层 capabilities 里随便新增;如果需要协商是否支持某个扩展,两种方案:在标准 tools 里封装成工具;或用一个自定义
x-capabilities通知交换扩展支持列表; - 自定义扩展只在自己的 Host / Server 生态互通;通用客户端(Claude Desktop)会忽略不认识的 x-* 接口。
5.4 什么时候适合扩展
适合:企业内部专属运维指令、任务状态查询;专属流式传输、大文件分片;内部审计、特权接口。
不适合:需求具备通用性时应优先提官方规范提案,而不是自己造私有 method;也不要用自定义 method 重新实现已经存在的原语。
六、进阶能力组合实战
场景:一个带采样、roots、进度、动态工具列表的文件分析 MCP Server。
- Host 和 Server 握手,双方同时开启
tools、resources(subscribe)、sampling、roots; - Host 下发 roots:只允许访问
file:///work; - Host 调用
tools/list,拿到扫描工具;Server 插件加载完成后推送notifications/tools/list_changed; - Host 调用
tools/call启动批量日志扫描; - Server 持续推送
notifications/progress进度; - Server 在扫描中发现一段日志,发起
sampling/createMessage,请求 Host 的模型做风险识别; - Host 返回模型结果,Server 继续处理;
- 某个日志文件变更,Server 推送
notifications/resources/updated; - 用户取消文件夹权限,Host 推送
roots/list_changed,Server 立刻拒绝后续越界读取。
这一套组合,就是企业落地里真正有生产力的 MCP 形态,而不只是简单调用工具。
七、第四篇小结与高频误区
核心复盘
- Sampling:反向推理,Server 请求 Host 的 LLM;Host 掌握审批、模型选择、配额;
- Roots:Host 下发资源白名单,由 Server 执行 URI 前缀校验,作为资源沙箱;
- 通知体系:资源更新、工具列表变更、结构化日志、进度条,全部是异步无应答事件;通知只做提醒,不携带返回数据;
- 错误分层:传输错误、标准 JSON-RPC 错误、业务错误;业务错误可以附带结构化 data;
- 扩展原则:依靠 capabilities 做兼容;私有接口必须
x-前缀。
高频误区
| 误区 | 正解 |
|---|---|
| Roots 可以限制工具的文件写入 | Roots 只约定 Resources;工具权限需要单独管控 |
| sampling 请求过来,Host 必须调用模型 | Host 有权拒绝、审批、替换参数 |
| 收到 resources/updated 就自动拿到新内容 | 通知只是提醒,必须主动 resources/read |
| 自定义方法可以不加前缀 | 非标准 method / notification 必须使用 x- 前缀 |
| 进度通知可以作为任务完成标记 | 进度只是反馈;任务成功/失败仍依靠 tools/call 的 response 或 error |
下一篇(系列完结篇):《深入浅出 MCP 系列教程(五):工程落地与选型》,将讲解 SDK 上手、安全模型、网关架构、可观测性,以及 MCP 与 Function Calling / Agent 框架 / A2A 的对比与选型决策。