MCP(Model Context Protocol)协议自 2024 年 11 月由 Anthropic 首次发布以来,已经历多次重大迭代。2025-03-26 版本是迄今为止变动最大的一次更新,它从认证安全、传输架构、工具元数据三个维度对协议进行了全面升级。如果你正在构建或维护 MCP Server,这次更新值得你认真对待。
本文将深入解读这些变更的技术细节和设计动机,并提供可运行的代码示例帮助你快速落地。
核心要点
- OAuth 2.1 强制升级:废弃隐式授权流,强制 PKCE + HTTPS,支持动态客户端注册
- Streamable HTTP 取代 SSE:单端点设计替代双通道架构,支持双向通信和断线恢复
- Tool Annotations:为每个工具声明行为元数据(只读/破坏性/幂等/开放域),驱动智能权限控制
- JSON-RPC 批处理:协议级强制支持,大幅降低网络开销
- 会话管理:
Mcp-Session-Id头实现有状态连接和断点续传 - 生态爆发:Claude、ChatGPT、VS Code、Cursor 等主流平台全面支持
想要发现更多 MCP Server?访问 MCP 导航页面 快速浏览生态全貌。
MCP 规范演进时间线
MCP 协议自诞生以来经历了四个重要版本:
| 版本 | 发布日期 | 核心变更 |
|---|---|---|
| 2024-11-05 | 2024年11月 | 初始版本,定义 Tools/Resources/Prompts 三大原语,HTTP+SSE 传输 |
| 2025-03-26 | 2025年3月 | OAuth 2.1、Streamable HTTP、Tool Annotations、JSON-RPC 批处理 |
| 2025-06-18 | 2025年6月 | Elicitation(服务端请求用户输入)、安全最佳实践文档、结构化输出 |
| 2025-11-25 | 2025年11月 | 进一步安全增强、协议稳定性改进 |
其中 2025-03-26 版本是最具里程碑意义的一次迭代,它将 MCP 从"本地开发工具协议"推向了"企业级生产就绪协议"的新阶段。
如果你还不熟悉 MCP 的基础概念,建议先阅读 MCP 协议深度解析 了解核心架构。
OAuth 2.1 认证授权框架
为什么 MCP 需要认证?
在 MCP 的初始版本中,Server 大多通过 stdio 在本地运行,本地进程天然拥有当前用户的权限,无需额外认证。然而,当 MCP Server 走向远程部署,认证就成了不可回避的问题:
- 权限隔离:不同用户访问不同数据,远程 Server 必须识别调用者身份
- 工具安全:具有破坏性的工具(如数据库删除)不能被未授权的 AI Agent 随意调用
- 合规要求:企业环境中对 API 访问的审计追溯是基本要求
OAuth 2.0 到 2.1 的关键升级
MCP 2025-03-26 版本直接采用了 OAuth 2.1 标准,相比旧版 OAuth 2.0 有三个核心变化:
| 变更项 | OAuth 2.0 | OAuth 2.1 |
|---|---|---|
| 隐式授权流 | 支持 | 完全废弃 |
| PKCE | 可选 | 强制要求 |
| HTTPS | 推荐 | 强制要求 |
**PKCE(Proof Key for Code Exchange)**是这次升级的核心。它通过密码学挑战-应答机制,彻底消除了授权码在传输过程中被截获的中间人攻击风险。
OAuth 认证流程
以下是 MCP 中 OAuth 2.1 + PKCE 的完整授权流程:
动态客户端注册
MCP 规范要求支持 RFC 7591 动态客户端注册,这意味着 MCP Client 不需要预先在 Server 端手动配置——连接时自动注册即可获得凭证。这对 AI 工具生态尤为重要:
POST /register HTTP/1.1
Content-Type: application/json
{
"client_name": "My AI Assistant",
"redirect_uris": ["http://localhost:3000/callback"],
"grant_types": ["authorization_code"],
"token_endpoint_auth_method": "none"
}
响应中返回 client_id,后续授权流程使用该 ID 进行身份标识。
Node.js 实现:带 OAuth 认证的 MCP Server
下面演示如何用 Node.js 构建一个符合 2025-03-26 规范的 MCP Server,集成 OAuth 2.1 认证。如果你在调试过程中需要检查 JWT Token 的结构,可以使用 JWT 生成与解析工具。
import express from 'express';
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
const app = express();
app.use(express.json());
// --- OAuth 2.1 端点 ---
// 授权服务器元数据发现
app.get('/.well-known/oauth-authorization-server', (req, res) => {
res.json({
issuer: 'https://mcp.example.com',
authorization_endpoint: 'https://mcp.example.com/authorize',
token_endpoint: 'https://mcp.example.com/token',
registration_endpoint: 'https://mcp.example.com/register',
response_types_supported: ['code'],
code_challenge_methods_supported: ['S256'],
grant_types_supported: ['authorization_code', 'refresh_token']
});
});
// 动态客户端注册 (RFC 7591)
const clients = new Map();
app.post('/register', (req, res) => {
const clientId = crypto.randomUUID();
clients.set(clientId, {
client_name: req.body.client_name,
redirect_uris: req.body.redirect_uris,
created_at: Date.now()
});
res.status(201).json({ client_id: clientId });
});
// Token 端点 (验证 PKCE)
app.post('/token', (req, res) => {
const { code, code_verifier, client_id } = req.body;
const stored = authCodes.get(code);
if (!stored) return res.status(400).json({ error: 'invalid_grant' });
// 验证 PKCE: SHA256(code_verifier) === code_challenge
const challenge = crypto
.createHash('sha256')
.update(code_verifier)
.digest('base64url');
if (challenge !== stored.code_challenge) {
return res.status(400).json({ error: 'invalid_grant' });
}
const accessToken = jwt.sign(
{ sub: stored.user_id, client_id, scope: stored.scope },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = crypto.randomUUID();
res.json({
access_token: accessToken,
token_type: 'Bearer',
expires_in: 900,
refresh_token: refreshToken
});
});
// --- MCP Streamable HTTP 端点 ---
app.post('/mcp', authenticateToken, (req, res) => {
// 处理 JSON-RPC 请求(含批处理)
const body = req.body;
if (Array.isArray(body)) {
// 批处理模式
const results = body.map(msg => handleJsonRpc(msg, req.user));
res.json(results.filter(r => r !== null));
} else {
const result = handleJsonRpc(body, req.user);
result ? res.json(result) : res.status(202).end();
}
});
function authenticateToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'unauthorized' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(403).json({ error: 'invalid_token' });
}
}
Python 实现:OAuth PKCE 客户端
import hashlib
import base64
import os
import httpx
class MCPOAuthClient:
def __init__(self, server_url: str):
self.server_url = server_url
self.client_id = None
self.access_token = None
async def discover(self):
"""发现授权服务器元数据"""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{self.server_url}/.well-known/oauth-authorization-server"
)
return resp.json()
async def register(self):
"""动态客户端注册"""
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{self.server_url}/register",
json={
"client_name": "Python MCP Client",
"redirect_uris": ["http://localhost:8080/callback"],
"grant_types": ["authorization_code"]
}
)
self.client_id = resp.json()["client_id"]
def generate_pkce(self) -> tuple[str, str]:
"""生成 PKCE code_verifier 和 code_challenge"""
code_verifier = base64.urlsafe_b64encode(
os.urandom(32)
).decode('utf-8').rstrip('=')
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).decode('utf-8').rstrip('=')
return code_verifier, code_challenge
async def exchange_token(self, code: str, code_verifier: str):
"""使用授权码 + code_verifier 换取 Access Token"""
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{self.server_url}/token",
data={
"grant_type": "authorization_code",
"code": code,
"code_verifier": code_verifier,
"client_id": self.client_id
}
)
tokens = resp.json()
self.access_token = tokens["access_token"]
return tokens
async def call_tool(self, tool_name: str, arguments: dict):
"""调用 MCP 工具"""
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{self.server_url}/mcp",
headers={"Authorization": f"Bearer {self.access_token}"},
json={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments
}
}
)
return resp.json()
Streamable HTTP:传输层架构革新
从双端点到单端点
旧版 MCP(2024-11-05)采用 HTTP+SSE 双通道方案:GET /sse 建立 Server→Client 的推送通道,POST /message 接收 Client→Server 的 JSON-RPC 请求。这种设计存在明显的工程痛点——如果你曾经实现过这套方案(参考 用 Go 从零实现 SSE 传输层),你一定对双连接管理的复杂性深有体会。
2025-03-26 版本引入 Streamable HTTP 彻底重构了传输层:
| 对比维度 | 旧版 HTTP+SSE | Streamable HTTP |
|---|---|---|
| 端点数量 | 2个(GET /sse + POST /message) | 1个(POST /mcp) |
| 通信方向 | 单向推送(SSE)+ 请求-响应(POST) | 双向通信 |
| 断线恢复 | 需重建完整会话 | 支持 Last-Event-ID 断点续传 |
| 简单请求 | 被强制走流式传输 | 可直接返回 JSON 响应 |
| 连接建立时间 | 约 320ms | 约 180ms(降低 44%) |
核心工作机制
Streamable HTTP 的智能之处在于协议协商——Client 通过 Accept 头声明自己的能力,Server 据此选择响应方式:
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
Mcp-Session-Id: sess_abc123
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "long_running_task", "arguments": {} }
}
Server 可以选择两种响应策略:
策略 1:简单 JSON 响应(短任务)
HTTP/1.1 200 OK
Content-Type: application/json
{"jsonrpc": "2.0", "id": 1, "result": {"content": [{"type": "text", "text": "done"}]}}
策略 2:SSE 流式响应(长任务)
HTTP/1.1 200 OK
Content-Type: text/event-stream
event: message
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":50,"message":"处理中..."}}
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"完成"}]}}
会话管理与断线恢复
新增的 Mcp-Session-Id HTTP 头为长任务场景带来了关键的可靠性保障。当网络断开后重新连接时,Client 携带之前的 Session ID 和 Last-Event-ID,Server 可以从断点恢复而无需重新初始化:
// 客户端断线重连逻辑
async function reconnect(sessionId, lastEventId) {
const response = await fetch('/mcp', {
method: 'GET',
headers: {
'Accept': 'text/event-stream',
'Mcp-Session-Id': sessionId,
'Last-Event-ID': lastEventId
}
});
// Server 从 lastEventId 之后继续推送事件
const reader = response.body.getReader();
// ... 处理 SSE 事件流
}
关于高并发场景下的连接管理策略,可以参考 高并发 MCP Gateway 架构设计。
Tool Annotations:工具行为元数据
为什么需要 Tool Annotations?
在 MCP 初始版本中,工具只有 name、description 和 inputSchema 三个字段。Client(或 LLM)只能通过文本描述来"猜测"一个工具会做什么——这在涉及破坏性操作时非常危险。
2025-03-26 版本引入 Tool Annotations 机制,允许 Server 为每个工具声明结构化的行为元数据:
interface ToolAnnotations {
title?: string; // 语义化标题(UI展示用)
readOnlyHint?: boolean; // 是否只读(默认 false)
destructiveHint?: boolean; // 是否有破坏性(默认 true)
idempotentHint?: boolean; // 是否幂等(默认 false)
openWorldHint?: boolean; // 是否与外部交互(默认 true)
}
四个 Hint 字段详解
| Hint 字段 | 默认值 | 含义 | 典型场景 |
|---|---|---|---|
readOnlyHint |
false | 工具不会修改任何外部状态 | 数据库查询、文件读取 |
destructiveHint |
true | 工具可能造成不可逆的破坏 | 删除记录、重置配置 |
idempotentHint |
false | 相同参数多次调用效果一致 | 更新操作(PUT语义) |
openWorldHint |
true | 工具会与外部世界交互 | 发送邮件、调用第三方API |
重要提示:所有 Hint 字段都是"建议性"的,Client 不应将其作为安全控制的唯一依据。实际的权限校验必须由 Server 端的 RBAC 引擎执行。
实际应用示例
{
"tools": [
{
"name": "query_database",
"description": "Execute a read-only SQL query",
"inputSchema": {
"type": "object",
"properties": {
"sql": { "type": "string" }
}
},
"annotations": {
"title": "Database Query",
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}
},
{
"name": "delete_user",
"description": "Permanently delete a user account",
"inputSchema": {
"type": "object",
"properties": {
"user_id": { "type": "string" }
}
},
"annotations": {
"title": "Delete User Account",
"readOnlyHint": false,
"destructiveHint": true,
"idempotentHint": false,
"openWorldHint": false
}
},
{
"name": "send_email",
"description": "Send an email to the specified recipient",
"inputSchema": {
"type": "object",
"properties": {
"to": { "type": "string" },
"subject": { "type": "string" },
"body": { "type": "string" }
}
},
"annotations": {
"title": "Send Email",
"readOnlyHint": false,
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true
}
}
]
}
Client 可以基于这些 Annotations 实现智能化的 UI 交互——例如,对 destructiveHint: true 的工具自动弹出二次确认对话框,对 readOnlyHint: true 的工具跳过确认直接执行。这与 Function Calling 和 Tool Use 的安全理念一脉相承。
JSON-RPC 批处理与进度通知
协议级批处理支持
2025-03-26 版本明确规定:所有 MCP 实现必须支持 JSON-RPC 2.0 批处理规范。这意味着 Client 可以在单个 HTTP 请求中发送多个 JSON-RPC 调用,大幅减少网络开销。
[
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "analyze_text", "arguments": {"text": "hello"}}},
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "translate", "arguments": {"text": "hello", "target": "zh"}}},
{"jsonrpc": "2.0", "method": "notifications/initialized"}
]
批处理的性能优势非常显著:
| 指标 | 单请求模式 | 批处理模式 | 优化幅度 |
|---|---|---|---|
| TCP 握手次数(100个请求) | 100 | 1 | 99% |
| Header 总开销 | ~150KB | ~2KB | 98.7% |
| 3G 网络总耗时 | 12.3s | 1.8s | 85.4% |
增强的进度通知
新版进度通知增加了 message 字段,支持语义化的状态描述:
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "task_123",
"progress": 65,
"total": 100,
"message": "数据清洗中:已处理 12000/20000 条记录"
}
}
这让用户界面可以展示更有意义的进度信息,而不仅仅是一个百分比数字。
生态支持与 MCP Registry
主流平台支持现状
截至 2026 年,MCP 协议已获得全面的生态支持:
| 平台 | 支持状态 | 协议版本 | 备注 |
|---|---|---|---|
| Claude Desktop | ✅ 原生支持 | 2025-03-26+ | Anthropic 官方客户端,支持最全面 |
| ChatGPT | ✅ 已支持 | 2025-03-26+ | 通过 Plugin/Actions 集成 |
| VS Code / Copilot | ✅ 已支持 | 2025-03-26+ | GitHub Copilot Agent 模式 |
| Cursor | ✅ 已支持 | 2025-03-26+ | 深度集成,支持自定义 MCP Server |
| Windsurf | ✅ 已支持 | 2025-03-26+ | Codeium 的 AI IDE |
你可以在 AI 工具导航 中发现更多支持 MCP 的 AI 客户端和应用。
GitHub MCP Registry
2025 年 9 月,GitHub 上线了官方 MCP Registry 预览版——这是 MCP Server 的"应用商店",为生态系统提供了一个中心化的发现与分发平台:
- 权威索引:公开 MCP Server 的单一可信数据源
- 社区驱动:由 Anthropic、GitHub、Microsoft 等核心贡献者维护
- 客户端集成:VS Code 等工具可以直接从 Registry 安装 MCP Server
- 质量保证:提供 Server 的信誉评分和安全审计信息
对于 Server 开发者而言,将你的 MCP Server 发布到 Registry 可以显著提升可发现性。你可以通过 JSON 格式化工具 来检查和美化你的 Registry 配置文件。
从旧版迁移到新规范
迁移检查清单
如果你正在维护基于 2024-11-05 版本的 MCP Server,以下是迁移到 2025-03-26 版本的完整路线:
第 1 步:传输层升级(优先级最高)
// ❌ 旧版:双端点 HTTP+SSE
app.get('/sse', handleSSE);
app.post('/message', handleMessage);
// ✅ 新版:单端点 Streamable HTTP
app.post('/mcp', handleStreamableHTTP);
app.get('/mcp', handleSSEStream); // 可选:用于服务端主动推送
第 2 步:实现 OAuth 2.1 认证
// 新增元数据发现端点
app.get('/.well-known/oauth-authorization-server', (req, res) => {
res.json({ /* 授权服务器配置 */ });
});
// 新增动态客户端注册
app.post('/register', handleDynamicRegistration);
// 所有 MCP 端点添加认证中间件
app.post('/mcp', authenticateOAuth, handleMCP);
第 3 步:添加 Tool Annotations
// ❌ 旧版:仅有基础描述
{ name: 'delete_file', description: 'Delete a file' }
// ✅ 新版:包含行为元数据
{
name: 'delete_file',
description: 'Delete a file',
annotations: {
title: 'Delete File',
readOnlyHint: false,
destructiveHint: true,
idempotentHint: false,
openWorldHint: false
}
}
第 4 步:支持 JSON-RPC 批处理和会话管理
app.post('/mcp', (req, res) => {
// 处理 Mcp-Session-Id
const sessionId = req.headers['mcp-session-id'] || generateSessionId();
res.setHeader('Mcp-Session-Id', sessionId);
// 支持批处理
if (Array.isArray(req.body)) {
const results = req.body.map(msg => processMessage(msg, sessionId));
return res.json(results.filter(Boolean));
}
// 单消息处理
const result = processMessage(req.body, sessionId);
result ? res.json(result) : res.status(202).end();
});
向后兼容性建议
MCP 规范建议 Server 在过渡期间同时支持新旧传输方式,通过请求头进行版本协商。旧版 Client 发送的请求会自动被路由到兼容模式。关于 MCP 协议的高阶实战技巧,可以参考 MCP 协议高阶实战指南。
总结
MCP 2025-03-26 版本标志着协议从"可用"迈向"可靠"的关键一步。OAuth 2.1 解决了远程部署的认证难题,Streamable HTTP 简化了传输架构,Tool Annotations 让工具调用更加安全可控。对于正在构建 AI 应用的开发者而言,及时跟进这些规范变更,将帮助你构建更安全、更高效、更易维护的 MCP 集成方案。
如果你正在寻找可以直接使用的 MCP Server,或者想要发布自己的 Server,建议访问 MCP Server 导航 浏览完整的生态。
本文所属专栏:MCP 协议精通之路 — 从入门到企业级实战的完整学习路径。