MCP(Model Context Protocol)模型上下文协议 番外篇 2025-03-26 更新
今年3月底。Anthropic对 MCP协议 进行了一次重大更新,具体改动参考:
Key Changesspec.modelcontextprotocol.io/specification/2025-03-26/changelog/https://link.zhihu.com/?target=https%3A//spec.modelcontextprotocol.io/specification/2025-03-26/changelog/
主要代码改动https://link.zhihu.com/?target=https%3A//github.com/modelcontextprotocol/specification/compare/2024-11-05...2025-03-26
主要变更内容有:
1. 新增基于OAuth 2.1的完整授权框架
2. 使用更灵活的Streamable HTTP传输协议替代原有的HTTP+SSE传输方案
3. 新增JSON-RPC批处理支持
4. 引入完善的工具注解系统,可更清晰地描述工具行为(如只读/破坏性操作等)
由于原文说的很多,但是其实大部分都是引用的常见的OAuth和HTTP的基本知识,真正MCP相关内容并不多。下面我分章节简单分析下具体改动的重点和相关个人分析:
一、授权框架
在Roadmap里面躺了很久的授权功能总算有了。现在MCP总算可以在传输层提供授权能力了,允许MCP客户端代表资源所有者向受保护的MCP服务器发起请求。
1. 协议要求
- 可选实现:MCP实现可选择是否支持授权功能
- HTTP传输:若使用HTTP传输,应遵循本规范
- STDIO传输:应从环境变量获取凭证,不适用本规范
- 其他传输:必须遵循对应协议的安全最佳实践
可以看到,MCP开始把本地Stdio和HTTP传输彻底分开了,之后的第二部分HTTP stream传输也更加深化了这一点。待会儿可以细说。
2. OAuth 2.1基础授权
3. 服务器元数据发现
发现机制
- 必须路径:
GET /.well-known/oauth-authorization-server
- 版本协商:建议客户端在请求头添加
MCP-Protocol-Version: <版本号>
- 若发现失败,必须回退到预定义默认路径(
/authorize
、/token
、/register
) - 基础URL计算规则:从MCP服务器URL中剥离路径(如
https://api.example.com/v1/mcp
→ 基础URL为https://api.example.com
基础URL规则(没有元数据时的fallback)
端点类型 | 默认路径 |
---|---|
授权端点 | /authorize |
令牌端点 | /token |
注册端点 | /register |
4. 访问令牌使用规范
每次请求必须携带Authorization头
Authorization: Bearer <access-token>
安全要求
- 禁止在URL查询参数传递令牌(也就是说GET的时候不用带)
- 每次请求必须携带Authorization头(也就是说POST的时候需要带)
- 无效令牌必须返回HTTP 401
5. 个人总结
原文真的写了一大堆,但很多都是开发人员本身就应该会的,我这里大致列了一张表:
特性 | MCP特有 | OAuth/HTTP标准 |
---|---|---|
元数据发现失败回退路径 | ✅ | ❌(RFC8414无此规定) |
基础URL剥离路径规则 | ✅ | ❌ |
动态注册的强推荐性 | ✅ | ❌(OAuth中为可选) |
MCP-Protocol-Version头 | ✅ | ❌ |
PKCE流程 | ❌ | ✅(OAuth 2.1要求) |
Bearer Token用法 | ❌ | ✅(RFC6750定义) |
所以可以看到,MCP只是在服务发现、注册机制、版本管理上引入一些自定义规则,而授权流程本身严格遵循OAuth 2.1。其设计目标是通过标准化回退和自动化注册,确保在分布式模型交互场景中的可用性。
二、可流式HTTP传输协议规范(Streamable HTTP)
是的,你没看错,他前脚刚定义完,后脚又大改了。新的传输规范替代2024-11-05协议版本的HTTP+SSE传输方案。(所以我的实战篇压根就没弄SSE,当时就觉得这玩意定的像个半成品,绝对不是因为懒)
1. 协议概述
本传输方案中,服务端作为独立进程运行,支持多客户端连接。基于HTTP POST/GET请求实现,可选用服务器推送事件(SSE)实现多消息流式传输,既支持基础MCP服务,也可实现支持流式传输和服务器主动通知的高级功能。
服务端必须提供一个同时支持POST/GET方法的统一HTTP端点(称为MCP端点),例如:https://example.com/mcp
2. 客户端消息发送规范
请求方式
- 所有客户端发往服务端的JSON-RPC消息必须通过独立HTTP POST请求发送至MCP端点
- 请求头必须包含:
Accept: application/json, text/event-stream
请求体格式
- 单个JSON-RPC请求/通知/响应
- 批量请求/通知数组
- 批量响应数组
处理规则
- 当请求体仅包含通知或响应时:
- 拒绝时返回4xx状态码(如400),响应体可包含无ID的JSON-RPC错误对象
- 服务端接受则返回
202 Accepted
(无响应体)
- 当请求体包含任何请求时:
- 服务端必须返回以下两种形式之一:
Content-Type: application/json
(返回单JSON对象)Content-Type: text/event-stream
(启动SSE流)
SSE流管理
- 服务端应在流中返回每个请求的响应(可批量)
- 可在响应前发送关联的请求/通知(可批量)
- 所有响应发送完成前不应主动关闭流(会话过期除外)
- 连接中断不视为取消请求,显式取消应发送
MCP CancelledNotification
(说实话,这一段我一开始没看懂,我都断了,还不能视为取消请求,还得显示发送Notification,这啥意思。后来结合上面的改动综述,我大概猜测有些工具的行为是改动或者破坏性操作,这样的操作确实不应该被连接中断打断,因为client发完请求就够了)
3. 服务端消息监听规范
连接初始化
- 客户端可通过HTTP GET发起SSE流(无需预先POST数据)
- 必须包含:
Accept: text/event-stream
- 服务端必须返回
text/event-stream
或405 Method Not Allowed
流式传输规则
- 服务端可发送非关联性请求/通知(可批量)
- 禁止发送响应(恢复中断的流除外)
- 任意一方可随时关闭流
4. 高级功能
多连接管理
- 客户端可同时维护多个SSE流
- 服务端禁止跨流广播相同消息
断线恢复
- 服务端可为SSE事件添加全局唯一ID
- 重连时客户端应携带
Last-Event-ID
头 - 服务端仅需恢复原流消息(非全量重发)
会话管理
- 初始化阶段服务端可通过
Mcp-Session-Id
头建立会话 - 会话ID要求:ASCII可见字符、全局唯一、加密安全
- 后续请求必须携带该ID,缺失时返回400错误
- 显式终止会话应发送
DELETE
请求
5. 个人总结
我觉得这一版的定义确实比之前成熟了很多:统一端点设计、动态传输模式、增强型SSE管理、有状态会话、可靠恢复机制等,在兼容性、灵活性和可靠性上显著超越之前的HTTP+SSE方案,尤其适合需要双向实时通信+强状态管理的复杂应用场景(如云IDE、协作编辑、IoT控制等)
但是问题来了,之前的版本咋办了,于是翻到末尾看到写的磕磕巴巴的一段:
向后兼容方案
服务端适配方案
- 同时维护新旧协议端点
- 或合并端点(需注意复杂度)
客户端适配方案
1. 尝试POST初始化请求到MCP端点:
- 成功 → 使用新协议
- 返回4xx错误 → 改用GET检测SSE端点
2. 收到SSE端点事件 → 切换至旧协议通信
(说实话,长期以后有谁会这么干啊,你还不如直接说SSE就凉了得了)
三、其他更新
其实重点就上面两个,剩下都是些边角料:
1. JSON-RPC批处理
JSON-RPC 的 批处理(Batch) 是一种允许客户端一次性发送多个请求对象的机制,旨在提高通信效率。以下是其核心特点和工作原理:
核心概念
1. 批量发送请求
-
- 客户端可以将多个 请求对象(Request) 组合成一个 数组 一次性发送给服务器,例如:
--> [{"jsonrpc":"2.0", "method":"sum", "params":[1,2,4], "id":"1"},{"jsonrpc":"2.0", "method":"notify_hello", "params":[7]}, // 这是一个通知(无id){"jsonrpc":"2.0", "method":"subtract", "params":[42,23], "id":"2"}]
2. 服务器处理规则
- 服务器会处理所有请求(可能并行或乱序),待全部处理完成后返回一个 响应数组。
- 每个请求(非通知)对应一个响应对象,但 通知(Notification,无
id
的请求)不会生成响应。 - 响应顺序可能与请求顺序不一致,客户端需通过
id
字段匹配请求与响应。
响应规则
1. 正常批处理响应
-
- 返回一个包含所有非通知请求响应的数组,例如:
<-- [{"jsonrpc":"2.0", "result":7, "id":"1"},{"jsonrpc":"2.0", "result":19, "id":"2"}]
2. 全通知批处理
- 如果所有请求都是通知(无
id
),服务器 不返回任何内容(包括空数组)。
3. 错误处理
- 整体错误:若批处理本身无效(如非 JSON、空数组、结构错误),返回单个错误响应,例如:
<-- {"jsonrpc":"2.0", "error":{"code":-32600, "message":"Invalid Request"}, "id":null}
- 部分错误:若批处理中某些请求无效(如方法不存在、参数错误),每个错误请求生成独立错误响应,例如:
<-- [{"jsonrpc":"2.0", "error":{"code":-32601, "message":"Method not found"}, "id":"5"}]
关键优势
- 高效性:减少多次网络往返的开销,适合需要批量操作的场景。
- 灵活性:支持混合发送普通请求和通知,服务器可并行处理。
- 容错性:单个请求失败不影响其他请求的处理。
2. 工具注释(tool annotations)
通过引入分层设计的ToolAnnotations
机制,显著优化了工具行为的描述能力与系统可维护性。其核心优势及创新点如下:
职责分离与结构清晰化
- 将工具属性明确划分为操作属性(如输入约束)与显示属性(如用户界面描述),通过继承
DisplayAnnotations
实现显示逻辑的独立封装。 - 解耦功能与展示逻辑,避免模型提示(
description
)与用户可见信息的混淆,提升开发规范性与代码可读性。
可扩展性与灵活性增强
- 通过可选属性设计,支持工具按需扩展功能描述,同时保持核心接口简洁。
- 允许不同工具差异化配置(如权限控制、交互提示),适应复杂场景需求,降低协议升级的兼容性风险。
显示属性的标准化复用
- 将通用显示配置(如标签、图标)抽象为
DisplayAnnotations
接口,供多工具继承复用。 - 统一前端交互体验,减少重复代码,同时支持全局样式调整,提升开发效率。
意图导向的元数据设计
- 明确区分面向模型的
description
(功能语义解释)与面向用户的显示属性(如多语言文案),精准传递信息。 - 避免模型因“展示文案”干扰理解,同时让终端用户获得更友好的交互提示,兼顾系统性能与用户体验。
3. 个人总结
JSON-RPC Batch 本质是对现有协议的直接复用,属于技术延续性设计;
Tool Annotations 聚焦前端开发体验优化(如统一交互描述),虽未突破工具定义范式,但通过分层元数据提升了配置可维护性。
四、总结:OAuth与Streamable HTTP驱动的下一代云工具集成方案
MCP 通过整合OAuth 身份验证和Streamable HTTP 传输协议,构建了一个安全、高效、可扩展的AI云工具通信框架,为未来的云原生应用和分布式系统提供了强大的基础设施支持。
1. OAuth 安全集成
- 标准化身份验证:基于OAuth 2.0,确保客户端和服务端的安全认证,支持细粒度权限控制。
- 无缝对接企业级SSO:可直接集成现有身份提供商(如Azure AD、Github、Google Auth),降低接入成本。
2. Streamable HTTP 的高效通信
- 统一端点,减少协议碎片化:单一MCP端点支持请求/响应、流式推送、服务端主动通知,简化对接流程。
- 智能传输模式:自动选择短连接(快速请求)或SSE长流(实时数据推送),优化网络资源使用。
MCP通过OAuth安全层 + Streamable HTTP传输层的组合,解决了之前AI工具在远程场景中的认证碎片化、协议模糊、实时性不足三大痛点,为AI的云原生时代提供了更灵活、更可靠、更易扩展的通信标准。
但是我有一点不太理解的是,为啥不直接定义一个 OpenAPI 呢。这样大家甚至可以直接脚本生成一对server和client,大大降低后续开发和版本更迭的难度。
对于MCP的基础知识,可以查看我的专栏: