SEP-2260:要求服务器请求与客户端请求关联

SEP-2260:要求服务器请求与客户端请求关联

社区译文;来源: MCP 29affa6b

本 SEP 已达到 Final 状态,并作为已接受设计的历史记录保留。 定稿后对协议所做的更改不会反映在此处。权威要求请参阅当前规范 及其变更日志。
字段值
SEP2260
标题要求服务器请求与客户端请求关联。
状态Final
类型标准跟踪
创建日期2026-02-16
作者MCP 传输工作组
发起人@CaitieM20 - Caitie McCaffrey
PR#2260

摘要

本 SEP 明确规定,roots/list、sampling/createMessage 和 elicitation/create 请求**必须(MUST)**与一个发起该请求的客户端到服务器请求相关联 (例如,在处理 tools/call、resources/read 或 prompts/get 期间)。对于通知以外的这些类型, **不得(MUST NOT)**实现由服务器独立发起的请求。

尽管当前 MCP 数据层并未强制执行,但从逻辑上讲,这些请求必须(MUST) 与一个有效的客户端到服务器 JSON-RPC Request Id 相关联。

用于运维的服务器到客户端 Ping 不受此限制。

动机

当前规范

当前规范在传输层使用了 SHOULD 规范词:

在 Streamable HTTP 传输中响应 POST Request 时 (2025-11-25/basic/transports.mdx:121-L123):

  • “服务器在发送 JSON-RPC response 之前,**可以(MAY)发送 JSON-RPC requests 和 notifications。这些消息应该(SHOULD)**与发起该操作的客户端 request 相关。”

对于可选的 GET SSE Stream (2025-11-25/basic/transports.mdx:146-L148):

  • “服务器**可以(MAY)**在该 stream 上发送 JSON-RPC requests 和 notifications。”
  • “这些消息**应该(SHOULD)**与客户端当前并发运行的任何 JSON-RPC request 无关。”

虽然 GET stream 允许发送“未经请求”的请求,但它完全是可选功能,MCP 服务器作者不能依赖它。

设计意图

MCP 服务器请求的设计意图,是在其他 MCP 操作中以响应式方式嵌套执行:

  • **采样(Sampling)**使服务器能够在处理工具调用、资源请求或提示词时请求 LLM 协助
  • **信息征询(Elicitation)**使服务器能够收集完成操作所需的额外用户输入
  • **列出根目录(List Roots)**使服务器能够识别共享存储位置

Ping 具有特殊地位,因为它主要用作 keepalive/健康检查机制。

对于 Streamable HTTP 服务器,如果没有可发送的通知或请求,该机制可以让 SSE Stream 长时间保持连接。 在客户端到服务器请求的上下文中,这些服务器请求可以与其关联。未来的传输实现将不再需要脱离请求关联的 Ping。

当前规范已经描述了这种模式:

“MCP 中的采样允许服务器实现 Agent 行为,使 LLM 调用能够_嵌套_在其他 MCP 服务器功能中发生。”

但是,规范性要求尚未强制执行此约束。

简化带来的收益

明确此约束可带来以下收益:

  1. 简化传输实现 - 传输无需支持任意的服务器发起请求/响应流;此类流要求服务器到客户端之间存在持久连接。传输只需支持以请求为作用域的双向通信
  2. 明确用户体验 - 用户能够理解,采样/信息征询是_因为_他们发起了某项操作,而不是自发发生
  3. 缩小安全风险面 - 确保客户端了解额外请求的信息将用于什么范围,使客户端能够更充分地判断是否提供所请求的信息
  4. 与实践保持一致 - 根据对 GitHub 的扫描,除 SEP 作者拥有的一个刻意构造场景的仓库外,所有现有实现都已经遵循此模式

规范变更

1. 向功能文档添加警告块

在 client/sampling.mdx 中(现有安全警告之后):

<Warning>

**请求关联要求**

服务器**必须(MUST)**仅在与发起操作的客户端请求相关联时发送 `sampling/createMessage` 请求(例如,在处理 `tools/call`、`resources/read` 或 `prompts/get` 期间)。

不支持在独立通信 stream 上由服务器独立发起采样(与任何客户端请求无关),并且**不得(MUST NOT)**实现这种模式。未来的传输实现无需支持这种模式。

</Warning>

在 client/elicitation.mdx 中(现有安全警告之后):

<Warning>

**请求关联要求**

服务器**必须(MUST)**仅发送与发起操作的客户端请求相关联的服务器到客户端请求(例如 `roots/list`、
`sampling/createMessage` 或 `elicitation/create`)。这种关联适用于处理
客户端请求期间(例如,在处理 `tools/call`、`resources/read` 或
`prompts/get` 期间)。

不支持由服务器在独立
通信 stream 上发起这些类型的请求(与任何客户端请求无关),并且
**不得(MUST NOT)**实现这种模式。未来的传输实现
无需支持这种模式。

</Warning>

在 client/roots.mdx 中(User Interaction Model 一节):

<Warning>

服务器**必须(MUST)**仅发送与发起操作的客户端请求相关联的服务器到客户端请求(例如 `roots/list`、
`sampling/createMessage` 或 `elicitation/create`)。这种关联适用于处理
客户端请求期间(例如,在处理 `tools/call`、`resources/read` 或
`prompts/get` 期间)。

不支持由服务器在独立
通信 stream 上发起这些类型的请求(与任何客户端请求无关),并且
**不得(MUST NOT)**实现这种模式。未来的传输实现
无需支持这种模式。

</Warning>

在 basic/utilities/ping.mdx 中(Overview 一节):

<Warning>

`ping` 是 MCP 层的存活检查,连接/session 建立后,任一方都**可以(MAY)**
随时发送该请求。

在 Streamable HTTP 中,实现**应该(SHOULD)**优先使用传输层 SSE
keepalive 机制维持空闲连接;`ping` 仍可用于
协议层响应性检查。

`roots/list`、`sampling/createMessage`
和 `elicitation/create` 的请求关联要求不适用于 `ping`。

</Warning>

2. 明确传输层约束

在 basic/transports.mdx 中,POST 发起的 SSE stream(约第 121 行):

- 服务器在发送 JSON-RPC _response_ 之前,**可以(MAY)**发送 JSON-RPC
- _requests_ 和 _notifications_。这些消息**应该(SHOULD)**与发起操作的客户端
- _request_ 相关。
+ 服务器在发送 JSON-RPC _response_ 之前,**可以(MAY)**发送 JSON-RPC
+ _requests_ 和 _notifications_。这些消息**必须(MUST)**与发起操作的客户端
+ _request_ 相关。

在 basic/transports.mdx 中,GET 发起的独立 SSE stream(约第 147 行):

- 服务器**可以(MAY)**在该 stream 上发送 JSON-RPC _requests_ 和 _notifications_。
- 这些消息**应该(SHOULD)**与客户端当前并发运行的任何 JSON-RPC
- _request_ 无关。
+ 服务器**可以(MAY)**在该 stream 上发送 JSON-RPC _notifications_ 和 _pings_。
+ 这些消息**应该(SHOULD)**与客户端当前并发运行的任何 JSON-RPC
+ _request_ 无关,**但** `roots/list`、
+ `sampling/createMessage` 和 `elicitation/create` 请求**不得(MUST NOT)**
+ 在独立 stream 上发送。

向后兼容性

影响评估

预计此变更对现有实现的影响很小或没有影响:

  1. 保留常见使用模式 - 完全支持在工具执行、资源读取和提示词处理期间进行采样/信息征询
  2. 没有已知实现受到影响 - GitHub 调研只发现一个采用此模式的实现,而这个唯一实现由 SEP 作者所有

禁止的模式

以下模式此前从未被明确记录或推荐,现在被明确禁止:

# ❌ PROHIBITED: Standalone server push
async def background_task():
    while True:
        await asyncio.sleep(60)
        # Try to initiate sampling without any client request context
        await session.create_message(...)  # NOT ALLOWED

仍受支持的模式

以下规范模式仍然完全受支持:

# ✅ SUPPORTED: Sampling during tool execution
@mcp.tool()
async def analyze_data(data: str, ctx: Context) -> str:
    # Request LLM analysis while processing the tool call
    result = await ctx.session.create_message(
        messages=[SamplingMessage(role="user", content=...)]
    )
    return result.content.text

实现指南

面向服务器实现者

如果服务器符合以下条件,无需更改:

  • 仅在工具处理程序内使用服务器到客户端请求
  • 仅在资源/提示词处理程序内使用服务器到客户端请求
  • 在处理客户端请求的过程中同步使用服务器到客户端请求

如果服务器存在以下行为,则需要更改:

  • 尝试在独立 HTTP GET stream 上发起服务器到客户端请求
  • 尝试发送独立于客户端操作的服务器到客户端请求
  • 存在尝试调用服务器到客户端请求的后台任务

对于“需要更改”的情况,需要实现其他设计。

建议实现者将在初始化后立即发起的未经请求的服务器到客户端请求(通常是 URL 信息征询), 改为在确实需要客户端提供该信息的客户端到服务器请求作用域内延迟执行。

超时注意事项

当 MCP 服务器在客户端请求内发起一个“嵌套”请求时,父请求的持续时间会延长,以包含用户的响应时间。

实现者**必须(MUST)**确保:

  1. 传输超时(例如 HTTP Request Timeout)足以容纳“人在回路”延迟,而该延迟可能没有上限。
  2. 基础设施(例如负载均衡器)强制执行的短超时,可能导致连接在用户响应前终止。对于 Streamable HTTP, **应该(SHOULD)使用传输层 SSE keepalive 机制保持连接并重置计时器;还可以(MAY)**使用 ping 请求执行协议层响应性检查。

面向客户端实现者

无需更改 - 客户端应该已经能够在自身出站请求的上下文中处理采样/信息征询请求。如果当前支持带外请求,实现还有可能得到简化。

客户端收到没有关联出站请求的服务器到客户端请求时,**应该(SHOULD)**以 -32602(Invalid Params)error 响应。

面向传输实现者

未来的传输实现可以依赖以下保证:

  • 采样/信息征询请求只会在客户端发起请求的作用域内发生
  • 传输无需在独立 channel 上支持任意的服务器发起请求/响应流
  • 请求关联和生命周期管理得到简化

时间线

(本 SEP 旨在作为本次变更的公开通知,先于未来不兼容此用法的协议版本发布。)

已考虑的替代方案

1. 软弃用

使用 SHOULD NOT 规范词劝阻但不禁止此模式。

拒绝原因: 此行为从未被有意支持,而保留歧义会阻碍传输简化。

2. 保留当前歧义

保持现有 SHOULD 规范词不变。

拒绝原因: 这会阻碍未来的传输实现,并使实现者无法确定此模式是否受支持。

3. 创建能力标志

为希望采用此行为的服务器添加 sampling.standalone 或类似能力。

拒绝原因: 这会为一个没有已知需求的用例增加复杂度,并与“嵌套”设计原则相矛盾。

参考资料

  • 当前采样文档:/specification/draft/client/sampling.mdx
  • 当前信息征询文档:/specification/draft/client/elicitation.mdx
  • 传输规范:/specification/draft/basic/transports.mdx
  • 客户端概念文档中的用户交互模型讨论