SEP-2575:使 MCP 无状态化
社区译文;来源: MCP 29affa6b
| 字段 | 值 |
|---|---|
| SEP | 2575 |
| 标题 | 使 MCP 无状态化 |
| 状态 | Final |
| 类型 | 标准跟踪 |
| 创建日期 | 2025-06-18 |
| 作者 | Jonathan Hefner (@jonathanhefner), Mark Roth (@markdroth), Shaun Smith (@evalstate), Harvey Tuch (@htuch), Kurtis Van Gent (@kurtisvg) |
| 发起人 | Kurtis Van Gent (@kurtisvg) |
| PR | #2575 |
摘要
真正的无状态协议中,每个请求都自包含并可独立理解;这种协议凭借内在的简洁性、可扩展性和可靠性,具有很强的吸引力。当前的模型上下文协议(MCP)默认并非无状态。规范要求执行初始化握手,在客户端与服务器之间建立 session 状态,并在连接期间持续保留该状态。
这种内在的有状态性使 MCP 难以大规模运行。例如,将 MCP 服务器置于标准负载均衡器之后颇具挑战,因为客户端的 session 与保存其状态的特定服务器实例相耦合。
本提案提出一系列变更,使无状态 MCP 成为默认模式,并对协议复杂度和状态采用“按需承担”模型。在此模型下,我们默认提供简单的无状态功能,只有在实际需要相应功能时,才引入有状态、长连接带来的开销。
具体而言,本 SEP 提议移除建立状态的初始化握手,并以彼此独立的无状态替代方案取而代之。这一初始步骤使每个请求都能被独立处理,从而简化服务器端逻辑,并为稳健、可扩展的部署铺平道路。
动机
模型上下文协议(MCP)规范当前强制要求有状态的初始化握手。这项设计选择给可扩展性、可靠性和实现简洁性带来了重大挑战。本 SEP 的动机正是解决这些不足。
有状态性的问题
核心问题在于,服务器必须保留先前请求的 session 状态,才能理解后续请求。这与现代云原生系统的设计背道而驰;现代系统倾向采用无状态服务,以获得更强的韧性和可扩展性。
- 阻碍扩展: 最关键的问题是有状态 MCP 难以进行负载均衡。简单的无状态负载均衡器(例如 L4/L7 轮询)无法使用,因为它会将客户端请求路由到不同的后端服务器,而这些服务器都不具备正确的 session 状态。运维人员被迫实现粘性 session 等复杂且脆弱的方案,将客户端绑定到特定服务器。这会增加基础设施复杂度,可能导致负载分布不均,也使服务难以进行水平扩展。
- 韧性和容错能力差: 在有状态模型中,如果处理客户端 session 的特定服务器实例发生故障,该 session 状态就会丢失。客户端必须检测连接故障、重新建立连接(很可能经负载均衡器连接到新的服务器实例),并再次执行完整的初始化握手。这个过程会造成中断且效率低下,还会增加“可恢复性”相关逻辑的复杂度。
- 实现复杂度增加: 当前模型给开发者带来了沉重负担。
- 服务器端: 开发者必须实现逻辑,为每个客户端创建、管理并最终垃圾回收 session 状态。这是 bug 和内存泄漏的常见来源。
- 客户端: 开发者必须编写复杂代码来管理持久连接,并处理不可避免的网络故障和重连,包括断开连接后重新同步状态的逻辑。
设计原则
本提案为协议复杂度建立“按需承担”模型,并按优先顺序遵循以下原则:
- 优先无状态: 只要可能,请求就必须自包含,提供服务器处理请求所需的全部信息,而不依赖先前请求的状态。
- 优先使用状态引用: 如果完全无状态的交互并不实际,应在每个请求中传递状态引用。
- 将有状态性视为最后手段: 只有在不存在更简单的替代方案来解决关键用例时,才应接受有状态逻辑和长生命周期流式连接的复杂度。
传输一致性
这些无状态原则必须一致地应用于所有传输。保持 stdio 与 http 实现同步,可确保统一的开发者体验,使开发者只需学习一次核心协议语义,便能应用于所有场景。这种一致性简化了传输无关库和工具的创建,并防止不同传输以根本不同的方式运行而导致协议碎片化。单一、连贯的协议模型对健康的生态系统至关重要。
规范
概述
本规范从根本上重构 MCP 交互模型,使其优先采用无状态模式。当前,MCP 要求在交换任何资源之前执行强制的三步初始化握手。该握手会协商并建立几项关键信息:
- MCP 协议版本
- 服务器能力和
serverInfo - 客户端能力和
clientInfo
初始化握手这一要求会强制建立状态,并预期该状态在客户端与服务器后续通信期间持续存在。此外,将这些协商捆绑到单一初始化阶段,会在它们之间形成隐含关联,尤其是把能力交换与强制连接生命周期关联起来。
本提案将移除初始化握手,并把其功能“拆分”为彼此独立的无状态组件。我们将提供定义更清晰的新机制,使客户端和服务器无需经过强制创建状态的周期即可交换这些信息。
注意: Session 管理(包括传输层和应用层)由 SEP-2322 和 SEP-2567 分别处理。本 SEP 仅关注移除初始化握手,并为版本协商、发现和能力提供无状态替代方案。
协议版本
为使请求自包含,以往在握手期间协商的元数据现在必须随每个请求提供。
HTTP
对于 HTTP 传输,协议版本 MUST 作为 HTTP header 传递。Header 值 MUST 与请求 payload 的 _meta 字段中提供的值匹配;否则服务器 MUST 返回 400 Bad Request(参见 SEP-2243)。
MCP-Protocol-Version: 2025-06-18- 用途:告知服务器客户端在此特定请求中使用的 MCP 规范版本。
- 要求:此 header 为强制要求。服务器应拒绝缺少版本或版本不受支持的请求。
- 此 header MUST 与下文规定的 Request 中提供的值匹配。
每请求版本
protocol-version MUST 直接嵌入请求 payload 的 _meta 字段。对于 HTTP,此 _meta MUST 与关联的 HTTP header 匹配,否则服务器应返回 400 Bad Request。
以下 diff 展示了对 RequestMetaObject 的必要变更:
export interface RequestMetaObject extends MetaObject {
progressToken?: ProgressToken;
+ /**
+ * The MCP Protocol Version being used for this request.
+ */
+ "io.modelcontextprotocol/protocolVersion": string;
// Additional per-request fields (clientInfo, clientCapabilities, logLevel)
// are introduced in the Per-Request Client Capabilities section below.
}不受支持的协议版本
如果服务器收到使用其未实现协议版本的请求(无论服务器不认识该版本,还是服务器选择不支持某个已知版本,例如实验版或草案版),服务器 MUST 返回 JSON-RPC error response。对于 HTTP,response status code MUST 为 400 Bad Request。Error MUST 符合以下结构:
export const UNSUPPORTED_PROTOCOL_VERSION = -32022;
export interface UnsupportedProtocolVersionError extends Omit<
JSONRPCErrorResponse,
"error"
> {
error: Error & {
code: typeof UNSUPPORTED_PROTOCOL_VERSION;
data: {
/**
* An array of protocol version strings that the server supports.
*/
supported: string[];
/**
* The protocol version that was requested by the client.
*/
requested: string;
};
};
}版本协商流程
移除初始化握手后,版本协商会在请求过程中直接发生:
- 客户端在
MCP-Protocol-Versionheader 和io.modelcontextprotocol/protocolVersion_meta字段中发送其首选协议版本。 - 如果服务器支持该版本,则正常处理请求。
- 如果服务器不支持所请求的版本,则返回
UnsupportedProtocolVersionError,其中包含服务器的supported版本列表。 - 客户端从列表中选择双方都支持的版本并重试。
或者,客户端 MAY 先调用 server/discover,了解服务器支持的版本后再发送其他请求。
服务器能力发现
为了使客户端能够适配不同的服务器实现,本规范引入了发现 RPC。这为服务器公布其支持的协议版本和能力提供了标准机制。
服务器 MUST 实现 server/discover。客户端 MAY 调用该方法,但不是强制要求——客户端可以在不先调用发现端点的情况下自由调用任何 RPC。如果客户端调用了不受支持的 RPC,服务器 MUST 返回 Method not found JSON-RPC error(-32601)。对于 HTTP,response status code MUST 为 404 Not Found。
server/discover RPC
- 用途:允许客户端查询服务器支持的协议版本、能力和其他元数据。
Request Schema:
export interface DiscoverRequest extends Request {
method: "server/discover";
params?: {};
}Response Schema:
export interface DiscoverResult extends Result {
/**
* A list of MCP Protocol Version strings that this server supports.
* The client should choose a version from this list for use in
* subsequent requests.
*/
supportedVersions: string[];
/**
* An object detailing the capabilities of the server.
*/
capabilities: ServerCapabilities;
/**
* Information about the server software implementation.
*/
serverInfo: Implementation;
/**
* Natural language instructions describing how to use the server and
* its features. This can be used by clients to improve an LLM's
* understanding of available tools (e.g., by including it in a system prompt).
*/
instructions?: string;
}每请求客户端能力
为彻底解除与初始握手的耦合,客户端能力不再只在初始化时协商一次。相反,客户端在每个请求中都 MUST 指定其能力。这可确保服务器始终充分了解客户端针对该次特定事务能够处理哪些可选功能。空的能力对象表示客户端不支持任何可选能力——服务器 MUST NOT 根据先前请求推断能力。
每请求元数据 Schema
每个请求的 _meta 都携带一小组先前位于初始化握手中的字段。完整的 RequestMetaObject 结构如下:
export interface RequestMetaObject extends MetaObject {
progressToken?: ProgressToken;
/**
* The MCP Protocol Version being used for this request.
*/
"io.modelcontextprotocol/protocolVersion": string;
/**
* Identifies the client software.
*/
"io.modelcontextprotocol/clientInfo": Implementation;
/**
* Capabilities of the client for this specific request.
*/
"io.modelcontextprotocol/clientCapabilities": ClientCapabilities;
/**
* The desired log level for this request.
*/
"io.modelcontextprotocol/logLevel"?: LoggingLevel;
}字段语义:
"io.modelcontextprotocol/protocolVersion":string——MCP 协议版本。必填。 协商详情参见上文“协议版本”一节。"io.modelcontextprotocol/clientInfo":Implementation——标识客户端软件。必填。Implementationschema 要求提供name和version;其他字段可选。"io.modelcontextprotocol/clientCapabilities":ClientCapabilities——客户端针对该请求的能力。必填。"io.modelcontextprotocol/logLevel":LoggingLevel——该请求所需的日志级别。可选。 如未提供,服务器 MUST NOT 为该请求发送任何日志通知。客户端通过显式设置级别来选择接收日志消息。此字段取代logging/setLevelRPC。
Roots 被有意排除在每请求 _meta 字段之外。需要客户端 roots 的服务器 MUST 通过 MRTR ListRootsRequest 机制请求(参见 SEP-2322),这样可以避免在每个请求中放入可能很大的 root 列表,并遵循“按需承担”原则。
缺少任何必填字段的请求均为格式错误;服务器 MUST 使用 INVALID_PARAMS 拒绝该请求(对于 HTTP,还应使用 400 Bad Request)。
响应流
这些声明的能力决定服务器可以在响应流中包含哪些内容。SEP-2322(MRTR)定义了服务器到客户端的交互如何通过 IncompleteResult 内嵌于响应中;本 SEP 规定,这些交互由 RequestMetaObject 中声明的每请求 clientCapabilities 管理。
对于 HTTP,任何请求的响应都 MAY 以 SSE 流(Content-Type: text/event-stream)而非单个 JSON 对象的形式交付。只有通知(例如 notifications/progress、notifications/message)作为独立消息在该流上传输,最后再传输最终结果。服务器到客户端的交互(sampling、elicitation、listRoots)不会作为独立请求发送——它们作为输入请求嵌入特定请求路径(例如 CallTool、GetPrompt、ListResources)返回的 IncompleteResult 中。客户端满足这些输入请求后,重试原始请求。
请求取消
客户端取消进行中请求的方式取决于传输:
- HTTP。 关闭 SSE 响应流 MUST 被服务器视为取消该请求。由于每个请求都有自己的响应流,因此传输层断开连接的含义明确。
- STDIO。 客户端 MUST 发送引用该 request ID 的
notifications/cancelled通知。STDIO 只有一个共享通道,因此不存在可供关闭的每请求流。
服务器 SHOULD 在实际可行时尽快停止已取消请求的工作,并且 MUST NOT 再为其发送任何消息。
移除可恢复流
由于连接断开现在会隐式取消请求,因此移除了可恢复的 SSE 流(通过 Last-Event-ID 重连)。它们与默认无状态范式相矛盾:恢复流会要求服务器在连接故障后继续保留每请求状态。
需要持久性或可恢复性的工作负载 MUST 改用 tasks primitive;该原语提供了连接断开后获取结果的显式机制。
缺少必需能力
服务器 MUST NOT 依赖客户端未声明的能力。如果处理请求需要客户端未在其 clientCapabilities 中声明的能力,服务器 MUST 返回 JSON-RPC error,并指明缺少的能力。对于 HTTP,response status code MUST 为 400 Bad Request。
export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
export interface MissingRequiredClientCapabilityError extends Omit<
JSONRPCErrorResponse,
"error"
> {
error: Error & {
code: typeof MISSING_REQUIRED_CLIENT_CAPABILITY;
data: {
/**
* The capabilities the server requires from the client
* to process this request.
*/
requiredCapabilities: ClientCapabilities;
};
};
}subscriptions/listen RPC
本 SEP 引入新的 subscriptions/listen RPC,取代先前的 HTTP GET 端点,并确保 HTTP 与 STDIO 的行为一致。客户端使用该 RPC 打开长生命周期通道,用于接收特定请求上下文之外的通知。
此协议版本移除了 Streamable HTTP 用于服务器到客户端消息的 HTTP GET 端点。所有通信均使用 POST。
根据 SEP-2260,此通道仅传输通知(不传输请求);服务器发起的请求使用 MRTR(参见上文“响应流”),并限定在特定客户端请求的作用域内。
Request Schema
export interface SubscriptionsListenRequest extends Request {
method: "subscriptions/listen";
params: {
_meta: {
"io.modelcontextprotocol/protocolVersion": string;
"io.modelcontextprotocol/clientInfo": Implementation;
"io.modelcontextprotocol/clientCapabilities": ClientCapabilities;
// ... other meta fields
};
/**
* The notifications the client wants to receive on this stream.
* Each notification type is opt-in; the server **MUST NOT** send
* notification types the client has not explicitly requested here.
*/
notifications: {
/**
* If true, receive notifications/tools/list_changed.
*/
toolsListChanged?: boolean;
/**
* If true, receive notifications/prompts/list_changed.
*/
promptsListChanged?: boolean;
/**
* If true, receive notifications/resources/list_changed.
*/
resourcesListChanged?: boolean;
/**
* Subscribe to notifications/resources/updated for specific
* resource URIs. Replaces the resources/subscribe RPC.
*/
resourceSubscriptions?: string[];
};
};
}notifications 字段为必填,客户端 MUST 显式选择接收每一种所需的通知类型。如果 notifications 内的某个字段被省略(或设为 false),服务器 MUST NOT 发送该类型的通知。
确认通知
服务器首先发送此通知,以确认订阅已建立。订阅具有长生命周期,没有自然的“完成结果”;它会在以下情况下结束:
- 客户端显式取消订阅(在 HTTP 中关闭 SSE 流,或在 STDIO 中发送
notifications/cancelled); - 底层连接关闭(HTTP timeout、TCP 断开连接、STDIO 进程退出);或
- 服务器将其拆除(例如 shutdown),此时服务器 MUST 关闭 SSE 流(HTTP),或发送引用订阅 request ID 的
notifications/cancelled(STDIO)。
export interface SubscriptionsAcknowledgedNotification extends Notification {
method: "notifications/subscriptions/acknowledged";
params: {
/**
* The notification subscriptions the server has agreed to honor.
* Only includes notification types the server actually supports.
* If the client requested an unsupported notification type
* (e.g., promptsListChanged when the server has no prompts),
* it is omitted from this set.
*/
notifications: {
toolsListChanged?: boolean;
promptsListChanged?: boolean;
resourcesListChanged?: boolean;
resourceSubscriptions?: string[];
};
};
}多个并发订阅
客户端 MAY 同时拥有多个活动订阅(例如,一个监听工具列表变更,另一个监听资源更新)。每个订阅都由其 SubscriptionsListenRequest 的 JSON-RPC request ID 标识。
为了让 STDIO 客户端能够在单个共享通道上解复用属于不同订阅的通知,作为活动订阅一部分交付的每个通知都 MUST 在 _meta 中包含订阅的 request ID:
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": "<original listen request id>"
}
}
}同一关联模式也适用于需要与特定请求相关联的其他服务器到客户端通知,例如 notifications/progress(它使用发起请求的 ID)。
停止订阅
- HTTP。 关闭 SSE 响应流会停止订阅。
- STDIO。 客户端发送引用 listen request ID 的
notifications/cancelled。服务器 MUST 停止为该订阅发送通知。
传输行为
HTTP。 客户端通过 POST 发送 SubscriptionsListenRequest。服务器响应为开放的 SSE 流(Content-Type: text/event-stream),该流上的第一条 JSON-RPC 消息 MUST 是 SubscriptionsAcknowledgedNotification。
STDIO。 客户端可以随时发送 SubscriptionsListenRequest。服务器 MUST 通过发送 SubscriptionsAcknowledgedNotification 进行确认。后续通知在双向 STDIO 通道上传输,每条通知均按上述方式附带订阅的 request ID。如果连接终止(例如服务器崩溃并重启),客户端 MUST 重新发送 SubscriptionsListenRequest 以重新建立订阅。
已弃用和已移除的 RPC
为简化协议并与转向每请求能力保持一致,移除以下 RPC method 和通知:
initialize/notifications/initialized:移除初始化握手。版本协商通过每请求MCP-Protocol-Versionheader 和_meta字段处理。能力发现由server/discover处理。logging/setLevel:已移除。日志级别现在通过每请求'io.modelcontextprotocol/logLevel'_meta字段指定。没有替代 RPC。roots/list:作为顶层服务器到客户端 RPC 移除。需要客户端 roots 的服务器 MUST 通过 MRTRListRootsRequest机制请求(参见 SEP-2322)。notifications/roots/list_changed:已移除。Roots 通过 MRTR 按需获取,因此不再需要变更通知。resources/subscribe/resources/unsubscribe:移除这些 method。资源订阅本质上是有状态的——服务器必须记住每个客户端订阅了哪些资源。作为替代,客户端在subscriptions/listen请求的notifications参数中声明希望接收更新的资源。服务器在 listen 流上为匹配的资源发送notifications/resources/updated。ping:在两个方向上均被移除。服务器到客户端的 ping 被移除,因为服务器不能再独立发送请求。客户端到服务器的 ping 也被移除,因为任何普通 RPC 调用都足以证明服务器存活,而传输层机制(HTTP keep-alive、SSE comment、STDIO 进程状态)能更恰当地处理连接健康检查。
理由
默认无状态优先
本 SEP 的主要设计决策是移除强制初始化握手,使无状态交互成为协议的默认模型。这项选择源于“按需承担”原则,以及使 MCP 与现代云原生架构保持一致的愿望。让最简单的交互模型成为默认模式,可以降低入门门槛,并减少最常见用例的实现复杂度。这会直接支持简单直接的水平扩展并提高韧性,因为任何请求都可以由任意服务器实例处理。
考虑过的替代方案:可选握手
我们考虑过保留现有的有状态握手,但将其设为可选。在此模型中,客户端可以选择执行握手以建立持久 session,也可以跳过握手并发送自包含请求。
拒绝原因:
支持两套并行交互模型会大幅增加协议及每种实现的复杂度。服务器和客户端需要构建、测试和维护两条独立逻辑路径,从而扩大可能出现 bug 的表面积。这也违反了以一种清晰、明确方式执行核心功能的设计原则。通过彻底切换,我们可以确保整个生态系统向前发展,并受益于更简单、更具可扩展性且更稳健的基础。
显式 Session 管理
本提案最初包含专用的 sessions/create 和 sessions/delete RPC,用于管理逻辑 session 的生命周期。
Session 管理现在由 SEP-2567 单独处理;该 SEP 提议彻底移除 session,改用显式状态句柄。这与核心维护者作出的 sessions 与 sessionless 决策一致。
关注点分离
本提案的一项核心原则,是把整体式初始化握手“拆分”为一组彼此独立、用途单一的 RPC。原始握手把协议协商和能力发现混合在一个复杂交互中。新设计明确分离这些关注点:
- 发现:仅由
server/discover处理。 - 能力:通过
_meta字段或subscriptions/listenRPC,按每个请求处理。
这样做是为了创建更模块化、更灵活且更易理解的协议。每个组件现在都具有单一且明确定义的职责。客户端只需使用协议中自己需要的部分,从而遵循“按需承担”原则。
考虑过的替代方案:整体式握手
我们本可以保留单一的整体式握手 RPC,只需向其中添加更多参数和复杂逻辑,以支持无状态优先模型。
拒绝原因:
单个包揽一切的 RPC 难以实现、测试和演进。它迫使所有客户端(即使是最简单的客户端)都了解协议中最复杂的功能。通过分离这些关注点,我们使协议更易于学习和正确实现,同时也使其更灵活,更便于未来扩展。
向后兼容性
虽然本提案试图保留现有功能和用例,但它引入了一项根本性的向后不兼容变更。因此,它将需要新的协议版本。
支持多个版本
虽然本 SEP 移除了 initialize 握手,但希望同时支持新旧客户端的服务器 MAY 这样做。此类服务器可以继续实现旧的 initialize RPC 以处理遗留客户端,同时为更新后的客户端公开新的无状态 RPC(server/discover 等)。
服务器和客户端都应能够恰当地处理版本变化。下面列出两个示例场景,其中 vPrev 表示 SEP 之前的版本,vAfter 表示 SEP 之后的版本。
客户端(支持 vPrev)→ 服务器(vPrev、vPost)
- 客户端发送初始化请求
- 服务器支持 vPrev,因此按规范返回初始化结果
- 客户端和服务器按照
vPrev通信。
客户端(支持 vPrev、vPost)→ 服务器(vPrev)
对于 HTTP,客户端可以尝试任意 vPost 请求(例如带有 MCP Protocol Version header 的 tools/list)。服务器返回 400 Bad Request(或 Unsupported protocol version);客户端在后续请求中回退到 vPrev(并执行初始化)。
对于 STDIO,客户端不能依靠每请求 error 来检测服务器版本。同时支持不要求初始化的 vPost 以及要求 initialize 的遗留版本的客户端,SHOULD 先使用 server/discover 探测,以确定应使用哪个版本:
- 客户端发送
server/discover,并将 MCP Protocol Version_meta字段设为其首选 vPost。 - 如果服务器支持 vPost(或客户端同样支持的任意 vPost 风格版本),客户端将发现的版本用于后续请求。
- 如果服务器返回
Unsupported protocol version或Method not found,客户端回退到其支持的遗留版本,并执行initialize握手。
仅支持 vPost 风格版本的客户端无需探测——它只需使用首选版本,并正常处理 Unsupported protocol version error。
安全影响
在没有 session 握手的情况下,每个请求都必须独立进行身份认证和授权。实现 MUST 确保移除初始化阶段不会绕过身份认证。
除每请求身份认证外,本提案不会引入其他安全问题。
参考实现
// TODO
常见问题
什么是协议层无状态?
Wikipedia 对无状态协议的定义如下:
无状态协议是一种通信协议,接收方不得保留先前请求的 session 状态。发送方将相关 session 状态传递给接收方,使每个请求都可以被独立理解,即无需引用接收方保留的先前请求的 session 状态。
这并不意味着不能在无状态协议之上构建有状态应用。HTTP 就是无状态协议的一个示例,当今大部分 Web 都构建于其上。但这确实意味着状态不能存在于_协议本身_中,而应在请求中指定该状态(如果无法这样做,则指定状态引用,供服务器或客户端跟踪)。
这会使 MCP 成为完全无状态的协议吗?
不会完全如此(所以说是“默认”无状态)。这取决于如何理解“请求”:上文提到的 SSE 流(包括客户端发起和服务器发起的流)通常在一个流上下文中包含多个请求。不过,这些流被限制在单个 HTTP 请求内,且可以选择不使用,这意味着其复杂度受到约束,且只需在实际情况要求时选择承担。
为什么 STDIO 也必须是无状态的?
MCP 所使用的传输应该仅仅是实现细节。如果协议的一个版本支持的功能无法清晰映射到该协议的另一个版本上,那么它们实际上就是两种不同的协议。
这样,开发者可以轻松地把服务从一种传输切换到另一种传输,而无需大幅改变应用行为,也能更轻松地在不同传输之间正确建立代理。否则,这些不同实现之间将持续存在功能差距和割裂,进而导致困惑和不兼容。
server/discover 与 MCP Server Card 有何关系?
server/discover RPC 与 MCP Server Card 提案存在重叠;后者定义了用于 HTTP 发现的 .well-known/mcp.json 文档。两种机制被有意保留:Server Card 很适合 HTTP(无需身份认证、可缓存、可索引),而 server/discover 提供了统一的 RPC 接口,可在 HTTP 和 STDIO 传输上以一致方式工作。两者的内容应在适用之处保持一致。
开放问题
哪些内容应放在 _meta 中,哪些应作为顶层协议字段?
本 SEP 将几个先前通过握手协商的值(protocolVersion、clientInfo、roots、logLevel、clientCapabilities)放入 io.modelcontextprotocol/ namespace 下的每请求 _meta 字段。这符合规范的许可:由 schema 中的定义保留“特定用途元数据”。
不过,这可能随着时间推移导致 _meta 过载——我们应在什么时候重新添加顶层字段?一种可能的区分方式是:必需的协议层字段(例如 protocolVersion)可能更适合作为顶层字段,而可选值或扩展提供的值则保留在 _meta 中。这个问题需要在本 SEP 定稿前进行更广泛的讨论。
clientInfo 是否应成为 ClientCapabilities 的一部分?
目前,clientInfo(Implementation 类型)和 clientCapabilities(ClientCapabilities 类型)是彼此独立的字段。在每请求模型中,使用单个字段容纳全部客户端元数据可以减少开销。但是,clientInfo 与能力的用途不同:前者用于身份/UI,后者用于功能协商。clientInfo 应合并到 ClientCapabilities 中,继续作为独立的每请求 _meta 字段,还是通过完全不同的机制处理(例如仅通过 subscriptions/listen 发送)?
SEP 达到 Final 后的变更
本 SEP 作为已接受内容的历史记录保留。以下列表跟踪本 SEP 达到 Final 状态后对规范所做的变更。权威的最新要求请参阅当前规范。
- 客户端身份改为可选请求元数据。
#3002 将
io.modelcontextprotocol/clientInfo改为可选字段。除非经过专门配置而不这样做,否则客户端 SHOULD 在每个请求中包含该字段。 - 服务器身份移至可选结果元数据。
#3002 在结果
_meta中引入io.modelcontextprotocol/serverInfo,并移除顶层DiscoverResult.serverInfo字段,以避免重复表示。除非经过专门配置而不这样做,否则服务器 SHOULD 在每个结果中包含此元数据。 - 订阅增加了优雅完成结果。
#2953 为服务器发起的优雅关闭定义了
subscriptions/listen结果,取代了本 SEP 中“订阅没有自然完成结果”的表述。服务器 SHOULD 在关闭流之前发送此结果。