深入浅出 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 开启前提

必须同时满足两个条件:

  1. Client(Host)在 initialize 的 capabilities 里声明 sampling: {};
  2. Server 在响应的 capabilities 里声明支持接收采样请求。

只有两边都声明,会话内才允许使用 sampling/createMessage。如果 Host 不支持 sampling,Server 绝对不能发起这个调用,否则收到 -32601 方法不存在错误。

1.3 请求报文(Server → Host)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"jsonrpc": "2.0",
"id": "sample-001",
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "请总结下面这段日志,只列出错误:\nConnection refused at port 8080"
}
}
],
"modelPreferences": {
"hints": [{"name": "gpt-4o-mini"}],
"intelligencePriority": 0.7,
"speedPriority": 0.2,
"costPriority": 0.1
},
"systemPrompt": "你是日志分析助手,输出简洁。",
"maxTokens": 512,
"temperature": 0.3,
"stopSequences": ["---"]
}
}

字段说明:

  • messages:消息数组,支持 text / image / resource 内容;
  • modelPreferences:偏好提示,不是强制。Host 可以忽略,按照用户配置、配额、可用模型自行选择;
  • maxTokens、temperature、stopSequences:推理参数,Host 有权限做上限拦截;
  • 没有 stream 参数:标准 sampling 接口默认返回完整消息,不强制流式。

1.4 Host 返回报文

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"jsonrpc": "2.0",
"id": "sample-001",
"result": {
"role": "assistant",
"content": {
"type": "text",
"text": "错误:8080 端口连接被拒绝"
},
"model": "gpt-4o-mini",
"stopReason": "endTurn"
}
}

stopReason 可选枚举:endTurn、maxTokens、stopSequence。

1.5 安全与审批流(非常重要的工程点)

  1. Host 拥有最终否决权:收到 sampling/createMessage,Host 可以直接调用模型返回结果、弹出审批让用户确认、或直接拒绝返回错误;
  2. 计费、token 消耗归属 Host;多租户 Host 必须按用户隔离统计;
  3. 提示注入风险:Server 传入的 messages 属于外部不可信输入,Host 需要做边界隔离;
  4. 禁止 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
2
3
4
5
6
7
8
9
10
11
12
{
"jsonrpc": "2.0",
"method": "notifications/roots/list_changed",
"params": {
"roots": [
{
"uri": "file:///home/user/work",
"name": "工作目录"
}
]
}
}
  • 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 会话基础通知

  1. initialized:Client → Server,握手完成;
  2. 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
2
3
4
5
6
7
8
9
10
{
"jsonrpc": "2.0",
"method": "notifications/logging/message",
"params": {
"level": "info",
"logger": "git-server",
"data": "开始拉取远程分支",
"timestamp": "2026-11-22T10:00:00Z"
}
}

level 取值:debug / info / warning / error。stdio 模式下 stderr 日志和协议内 logging 通知可以共存。

进度通知 notifications/progress:用于长时间任务(大文件扫描、批量导入、数据库备份),给前端进度条:

1
2
3
4
5
6
7
8
9
10
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "job-001",
"progress": 0.4,
"total": 100,
"message": "已扫描 40/100 文件"
}
}
  • progressToken:任务令牌,由发起请求的一方生成,绑定这个任务;
  • progress:0~1 比例;total:可选总量。

进度通知是非常重要的用户体验能力:没有它,Host 只能傻傻等待,不知道工具是不是卡死。

3.5 通知的通用约束

  1. 通知不保证送达、不保证顺序(网络代理、重连会丢);
  2. 通知不要求应答,接收方可以选择忽略;
  3. 通知不能用来传递核心返回结果,结果必须放在 Response;
  4. 会话断开之后,所有未送达通知全部丢弃,重连不会补发历史通知。

四、精细化错误模型

4.1 三层错误

  1. 传输层错误:连接断开、解析失败、缓冲区溢出、HTTP 401/404,由传输层向上抛出;
  2. JSON-RPC 标准错误:协议方法层面错误;
  3. 业务语义错误:方法存在、参数合法,但业务执行失败(数据库查询失败、文件不存在、权限不足)。

标准 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
2
3
4
5
6
7
8
9
10
11
12
{
"jsonrpc": "2.0",
"id": "res-read-01",
"error": {
"code": -32001,
"message": "访问超出根目录范围",
"data": {
"requestedUri": "file:///etc/passwd",
"allowedRoots": ["file:///home/user/work"]
}
}
}

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。

  1. Host 和 Server 握手,双方同时开启 tools、resources(subscribe)、sampling、roots;
  2. Host 下发 roots:只允许访问 file:///work;
  3. Host 调用 tools/list,拿到扫描工具;Server 插件加载完成后推送 notifications/tools/list_changed;
  4. Host 调用 tools/call 启动批量日志扫描;
  5. Server 持续推送 notifications/progress 进度;
  6. Server 在扫描中发现一段日志,发起 sampling/createMessage,请求 Host 的模型做风险识别;
  7. Host 返回模型结果,Server 继续处理;
  8. 某个日志文件变更,Server 推送 notifications/resources/updated;
  9. 用户取消文件夹权限,Host 推送 roots/list_changed,Server 立刻拒绝后续越界读取。

这一套组合,就是企业落地里真正有生产力的 MCP 形态,而不只是简单调用工具。

七、第四篇小结与高频误区

核心复盘

  1. Sampling:反向推理,Server 请求 Host 的 LLM;Host 掌握审批、模型选择、配额;
  2. Roots:Host 下发资源白名单,由 Server 执行 URI 前缀校验,作为资源沙箱;
  3. 通知体系:资源更新、工具列表变更、结构化日志、进度条,全部是异步无应答事件;通知只做提醒,不携带返回数据;
  4. 错误分层:传输错误、标准 JSON-RPC 错误、业务错误;业务错误可以附带结构化 data;
  5. 扩展原则:依靠 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 的对比与选型决策。