MCP协议深度解读:功能对比与最佳实践
MCP 协议说明与接入设计
这次来聊聊 MCP(Model Context Protocol)——这个在 AI 和工具平台之间越来越热门的协议。它到底是个什么玩意儿?怎么接入?认证授权怎么做?工具中心类平台怎么实现最靠谱?下面一一拆解。
- API 域名:
https://api.example.com - MCP 地址:
https://api.example.com/mcp - API Key:
tc_xxxxxxxx - 用户 ID:
user_id - Session ID:
mcp_sess_xxxxx
1. MCP 是什么
MCP 全称是 Model Context Protocol。它要解决什么问题呢?说白了就是:当外面的 AI 客户端想调用平台里的工具时,大家得有个统一的“接头暗号”。具体来说——
- 客户端怎么发现服务端有什么工具;
- 客户端怎么知道每个工具需要什么参数;
- 客户端怎么调用工具;
- 服务端怎么控制权限,只暴露该给用户看的工具;
- 服务端怎么把执行结果平平安安返回给客户端。
在工具中心类平台里,MCP 就是用来让外部 AI 客户端调用平台里各类工具的,比如文本处理、文件解析、视频解析、数据查询、内部自动化工具,不一而足。
MCP 本身不是某个简单的 REST 接口,而是一套基于 JSON-RPC 的协议规范。常见的核心方法有三个:
initializetools/listtools/call
另外,很多平台还会额外提供一个非标准的辅助接口:
GET /mcp/info
2. MCP 与普通 HTTP 工具调用的区别
很多工具平台同时支持两种调用方式。听起来有点绕?我们拿实际的例子来对比看看。
2.1 普通 HTTP 工具调用
典型的调用方式:
POST /tools/run
X-API-Key: tc_xxxxxxxx
Content-Type: application/json
{"tool_name": "text_summary","input": {"text": "待处理文本"}}
这种方式直观、简单,适合脚本、后端服务、CI/CD 或普通业务系统。说实话,大部分场景下够用了。
2.2 MCP 工具调用
MCP 则更偏向 AI 客户端的协议化调用。客户端先和服务端建立协议会话,然后通过 JSON-RPC 来发现和调用工具。大致流程是:
initialize → tools/list → tools/call
与普通 HTTP 相比,MCP 更关注这些事:工具发现、工具 schema、客户端能力协商、会话上下文、Agent 生态兼容。
3. MCP 常见传输方式
MCP 可以在不同的传输方式上运行。常见的有:
| 传输方式 | 说明 | 常见场景 |
|---|---|---|
| Stdio | 客户端启动本地进程,通过标准输入输出通信 | 本地工具、本地桌面集成 |
| SSE + HTTP | 使用 SSE 建立服务端推送通道,再通过 HTTP 发送消息 | 早期远程 MCP 实现 |
| Streamable HTTP | 新版远程 MCP 推荐方式,通过 HTTP 请求和会话头维持上下文 | 云端工具服务、Web 服务 |
| WebSocket | 通过双向长连接传输 JSON-RPC 消息 | 实时双向通信场景 |
| Stateless HTTP | 每个请求独立携带 API Key,无持久会话 | 简化实现、调试或兼容模式 |
对于工具中心类平台,一般推荐的是:Streamable HTTP + 服务端 Session。原因很直白——它适合远程服务,适合 Nginx / API Gateway 袋里,适合 Docker / Kubernetes 部署,能通过 Redis 维护 session,易于横向扩展,运维复杂度也远低于 WebSocket。
4. /mcp/info 是什么
GET /mcp/info 严格来说不是 MCP 标准的 JSON-RPC 方法,而是平台额外提供的辅助说明接口。它的作用主要是返回:
- MCP 服务名称;
- MCP 服务地址;
- 支持的传输方式;
- 认证方式;
- 客户端配置示例;
- 协议版本;
- 能力说明。
举个例子,访问 GET /mcp/info 可能会返回这样一个 JSON:
{"name": "tool-center-mcp","endpoint": "https://api.example.com/mcp","auth": {"type": "api_key","header": "X-API-Key"},"transports": ["streamable-http"],"protocolVersion": "2024-11-05"}
4.1 /mcp/info 是否需要认证
答案取决于它返回什么内容。如果只返回通用的服务说明,可以公开访问。但如果返回的信息涉及用户相关数据,比如当前用户、API Key、已订阅工具列表、账号权限等,那就必须校验 X-API-Key 了。
推荐的做法是:/mcp/info 默认只返回通用说明;带有 API Key 时,可以额外返回该 Key 能访问的摘要信息。这样既照顾了公开查询的需求,也兼顾了安全。
5. MCP 三个核心方法
MCP 协议流程主要围绕三个核心方法展开,分别是 initialize、tools/list 和 tools/call。
5.1 initialize
initialize 是 MCP 会话的敲门砖。客户端通过它告诉服务端——自己是哪家的客户端、什么版本、支持哪些协议版本、有哪些能力。说白了就是“我来了,这是我的名片”。
请求示例:
{"jsonrpc": "2.0","id": 1,"method": "initialize","params": {"protocolVersion": "2024-11-05","capabilities": {},"clientInfo": {"name": "example-client","version": "1.0.0"}}}
服务端返回自己的能力和信息:
{"jsonrpc": "2.0","id": 1,"result": {"protocolVersion": "2024-11-05","capabilities": {"tools": {}},"serverInfo": {"name": "tool-center","version": "1.0.0"}}}
initialize 的作用不是调用工具,而是建立协议上下文,让双方互相认识一下。
5.2 tools/list
tools/list 用来获取当前会话下用户可以访问的工具列表。请求十分简单:
{"jsonrpc": "2.0","id": 2,"method": "tools/list","params": {}}
服务端要根据当前 session 对应的用户和 API Key 去查权限,只返回该用户已订阅并且当前可用的工具。这能确保用户看不到不该看的东西。
响应示例:
{"jsonrpc": "2.0","id": 2,"result": {"tools": [{"name": "text_summary","description": "对文本内容进行摘要","inputSchema": {"type": "object","properties": {"text": {"type": "string","description": "需要摘要的文本"}},"required": ["text"]}}]}}
注意:tools/list 无论如何都不能返回用户未订阅的工具。
5.3 tools/call
tools/call 是真正干活的接口——调用某个具体的工具。
请求示例:
{"jsonrpc": "2.0","id": 3,"method": "tools/call","params": {"name": "text_summary","arguments": {"text": "这是一段需要摘要的文本。"}}}
服务端的处理逻辑需要步步谨慎:
1. 根据 session 获取 user_id / api_key_id;
2. 检查 API Key 是否仍然有效;
3. 检查用户是否订阅了 params.name 对应的工具;
4. 检查工具是否 active;
5. 校验 arguments 是否符合工具 schema;
6. 执行工具;
7. 返回结果。
响应示例:
{"jsonrpc": "2.0","id": 3,"result": {"content": [{"type": "text","text": "摘要结果..."}]}}
如果用户未订阅某个工具,应返回权限错误:
{"jsonrpc": "2.0","id": 3,"error": {"code": -32003,"message": "当前用户未订阅该工具"}}
6. 推荐执行顺序
严格来说,/mcp/info 不属于 MCP 会话本身,它是辅助信息接口。整体推荐顺序如下:
可选:GET /mcp/info
↓
连接或请求 /mcp,携带 X-API-Key
↓
initialize
↓
tools/list
↓
tools/call
更完整的展开是这样的:
1. 用户在控制台创建 API Key;
2. 用户将 API Key 配置到 MCP 客户端;
3. 客户端访问 /mcp/info 或直接连接 /mcp;
4. 服务端验证 X-API-Key;
5. 服务端创建或恢复 MCP session;
6. 客户端发送 initialize;
7. 服务端返回协议能力;
8. 客户端发送 tools/list;
9. 服务端返回当前用户已订阅的工具;
10. 客户端发送 tools/call;
11. 服务端校验权限并执行工具。
7. MCP Session 是什么
MCP session 可以理解为一次客户端与 MCP 服务端之间的协议会话。它保存的信息包括:
- 当前连接属于哪个用户;
- 当前连接使用哪个 API Key;
- 是否已初始化;
- 客户端信息;
- 协议版本;
- 会话创建时间;
- 最后活跃时间。
一个典型的 session 结构:
{"session_id": "mcp_sess_xxxxx","user_id": 10001,"api_key_id": 20001,"api_key_prefix": "tc_abcd","initialized": true,"client_info": {"name": "example-client","version": "1.0.0"},"protocol_version": "2024-11-05","created_at": "2026-01-01T00:00:00Z","last_seen_at": "2026-01-01T00:10:00Z"}
需要特别点明的是:session 不是浏览器登录 session,也不是 JWT。它是 MCP 协议层的服务端上下文,由服务端来维护。
8. Session 由谁维护
MCP session 的核心状态应由 服务端 来维护。客户端通常只需要保存 session_id,或者保持连接本身,或者保存 Mcp-Session-Id,或者保存请求 ID 用于匹配响应。
服务端需要保存的内容更多:user_id、api_key_id、是否已认证、是否已初始化、客户端信息、权限上下文、会话 TTL、长连接对象(如果是 SSE / WebSocket)。
有个原则非常重要:不能把用户身份和权限完全交给客户端保存,因为客户端不可信。
9. 不同传输方式下的 Session 维护
9.1 SSE + HTTP
常见的流程是客户端通过 GET 建立 SSE 连接,并通过 POST 发送消息,服务端通过 SSE event 返回结果。
| 内容 | 维护方 |
|---|---|
| session 状态 | 服务端 |
| session_id | 客户端保存并携带 |
| SSE 连接对象 | 服务端保存 |
| 用户身份与权限 | 服务端 |
9.2 Streamable HTTP
第一次请求携带 X-API-Key,服务端返回 Mcp-Session-Id,后续请求携带这个头即可。
| 内容 | 维护方 |
|---|---|
| session 数据 | 服务端 |
Mcp-Session-Id | 客户端保存并携带 |
| 用户身份与权限 | 服务端 |
| 请求/响应 | 每次 HTTP 请求 |
9.3 WebSocket
WebSocket 连接本身就是会话的载体。
| 内容 | 维护方 |
|---|---|
| WebSocket 连接 | 双方保持 |
| session 状态 | 服务端 |
| 用户身份与权限 | 服务端 |
| 请求 ID | 客户端生成,双方匹配 |
9.4 Stateless HTTP
如果不实现 session,也可以每次请求都携带 API Key。优点是简单、无需 session 存储、请求天然无状态。但缺点也很明显:不容易维护 initialized 状态、不容易记录客户端信息、不完全符合会话型 MCP 语义、对复杂客户端的兼容性较差。
10. 认证与授权
工具中心类平台通常有两套凭证,各管各的:
| 场景 | 凭证 | 请求头 |
|---|---|---|
| 管理控制台 | JWT | Authorization: Bearer |
| 工具调用 / MCP | API Key | X-API-Key: tc_xxxxxxxx |
原则很清晰:管理台用 JWT,工具调用和 MCP 用 API Key;不要把 JWT 交给 MCP 客户端,也不要把 API Key 当成前端登录态使用。
11. API Key 应该在哪一步验证
11.1 /mcp/info
如果只返回通用服务说明,可以不验证。如果返回用户相关信息,必须验证 API Key。
11.2 建立 /mcp 会话时
必须验证 X-API-Key。服务端应检查:API Key 是否存在、是否 active、是否过期、所属用户是否 active、是否允许创建 MCP session。验证成功后创建 session。
11.3 initialize
如果 session 已在连接入口创建,initialize 可复用 session 上下文。它主要负责协议协商,不应承担主要权限判断。
11.4 tools/list
必须基于当前 session 的用户身份进行授权过滤。逻辑是:根据 session.user_id 查询 active subscriptions,只返回已订阅且 active 的工具。
11.5 tools/call
必须再次进行实时授权校验。即使 tools/list 已经过滤过,tools/call 仍然不能信任客户端。因为——用户可能手写 tools/call、可能绕过 tools/list、订阅可能在 session 存活期间被取消、API Key 可能在 session 存活期间被吊销、工具可能临时下线。
服务端应检查:session 是否存在、API Key 是否仍 active、是否过期、用户是否仍 active、是否订阅了对应工具、工具是否 active、arguments 是否符合 schema。
12. 推荐的 Session 存储方案
12.1 单实例开发环境
可以用进程内内存,比如 Map。简单、性能高、易调试。缺点是服务重启 session 丢失、多实例无法共享、不适合生产横向扩容。
12.2 生产环境
推荐使用 Redis,键值如 mcp:session:mcp_sess_xxxxx -> JSON。推荐 TTL 设为 30 分钟到 2 小时,每次请求刷新 TTL。Redis 中的 value 示例:
{"session_id": "mcp_sess_xxxxx","user_id": 10001,"api_key_id": 20001,"api_key_prefix": "tc_abcd","initialized": true,"client_info": {"name": "example-client","version": "1.0.0"},"protocol_version": "2024-11-05","created_at": "2026-01-01T00:00:00Z","last_seen_at": "2026-01-01T00:10:00Z"}
13. 推荐实现:Streamable HTTP + Redis Session
推荐的整体架构:
MCP Client → HTTPS → API Gateway / Nginx → MCP Server → Redis: MCP Session → Database: User / API Key / Subscription / Service → Tool Worker
推荐流程:
GET /mcp/info → 返回 MCP 服务说明
POST /mcp initialize + X-API-Key → 验证 API Key → 创建 Redis session → 返回 Mcp-Session-Id
POST /mcp tools/list + Mcp-Session-Id → 读取 Redis session → 查询用户已订阅服务 → 返回工具列表
POST /mcp tools/call + Mcp-Session-Id → 读取 Redis session → 实时校验 API Key / 订阅 / 工具状态 → 执行工具 → 返回结果
14. 客户端配置示例
{"mcpServers": {"tool-center": {"url": "https://api.example.com/mcp","headers": {"X-API-Key": "tc_xxxxxxxx"}}}}
几点说明:
url指向 MCP 入口;X-API-Key是用户在控制台创建的 API Key;- 用户必须先订阅工具;
tools/list只返回已订阅工具;tools/call调用未订阅工具应返回权限错误。
15. Nginx 袋里注意事项
如果使用远程 MCP,需要确保袋里支持长连接或流式响应。示例配置:
location /mcp {
proxy_pass http://backend_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
chunked_transfer_encoding off;
}
关键点:
- SSE / Streamable HTTP 通常需要关闭 buffering;
- 长连接需要较长的
proxy_read_timeout; - 如果不使用 WebSocket,
Upgrade头不是必须,但保留通常不影响; - 如果使用多实例,session 必须放 Redis,不能只放本地内存。
16. 常见错误与处理
16.1 API Key 无效
建议返回:
{"jsonrpc": "2.0","id": 1,"error": {"code": -32001,"message": "API Key 无效或已过期"}}
HTTP 状态码用 401 Unauthorized。
16.2 未订阅工具
建议返回:
{"jsonrpc": "2.0","id": 3,"error": {"code": -32003,"message": "当前用户未订阅该工具"}}
HTTP 状态码用 403 Forbidden。
16.3 工具不存在
建议返回:
{"jsonrpc": "2.0","id": 3,"error": {"code": -32602,"message": "工具不存在或不可用"}}
16.4 参数错误
建议返回:
{"jsonrpc": "2.0","id": 3,"error": {"code": -32602,"message": "工具参数不合法"}}
17. 安全建议
-
API Key 只展示一次:创建成功时展示完整 Key;后续只展示前缀;不在前端持久化完整 Key。
-
MCP 不使用 JWT:JWT 用于管理控制台,MCP / 工具调用使用 API Key。
-
tools/list 按订阅过滤:不暴露未订阅工具,不暴露内部工具。
-
tools/call 必须实时校验权限:不能因为 session 已认证就跳过权限检查。
-
Session 设置 TTL:推荐 30 分钟到 2 小时,每次请求刷新 TTL。
-
API Key 吊销应尽快生效:
tools/call时检查 API Key 状态;可在吊销 Key 时清理相关 MCP session。 -
记录审计日志:记录调用时间、API Key 前缀、用户 ID、工具名称、成功/失败状态。不记录完整 API Key。
18. 推荐后端伪代码
18.1 初始化 session
async def handle_initialize(request):
api_key = request.headers.get("X-API-Key")
key_info = verify_api_key(api_key)
if not key_info:
return jsonrpc_error(request.id, -32001, "API Key 无效或已过期")
session_id = create_session_id()
session = {
"session_id": session_id,
"user_id": key_info.user_id,
"api_key_id": key_info.id,
"initialized": True,
"client_info": request.params.get("clientInfo"),
"protocol_version": request.params.get("protocolVersion"),
}
await redis.setex(f"mcp:session:{session_id}", 7200, json.dumps(session))
return response_with_header(
jsonrpc_result(request.id, {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "tool-center", "version": "1.0.0"},
}),
headers={"Mcp-Session-Id": session_id},
)
18.2 获取工具列表
async def handle_tools_list(request):
session = await get_session(request.headers.get("Mcp-Session-Id"))
if not session:
return jsonrpc_error(request.id, -32002, "MCP session 不存在或已过期")
subscriptions = get_active_subscriptions(session["user_id"])
tools = build_tools_from_subscriptions(subscriptions)
return jsonrpc_result(request.id, {"tools": tools})
18.3 调用工具
async def handle_tools_call(request):
session = await get_session(request.headers.get("Mcp-Session-Id"))
if not session:
return jsonrpc_error(request.id, -32002, "MCP session 不存在或已过期")
tool_name = request.params["name"]
arguments = request.params.get("arguments", {})
if not is_api_key_active(session["api_key_id"]):
return jsonrpc_error(request.id, -32001, "API Key 已失效")
if not has_active_subscription(session["user_id"], tool_name):
return jsonrpc_error(request.id, -32003, "当前用户未订阅该工具")
validate_tool_arguments(tool_name, arguments)
result = await run_tool(tool_name, arguments)
return jsonrpc_result(request.id, {"content": [{"type": "text", "text": result}]})
19. 最佳实践总结
推荐设计:
传输方式:Streamable HTTP
认证方式:X-API-Key
Session 存储:Redis
Session 标识:Mcp-Session-Id
工具权限:基于用户订阅实时判断
推荐流程:
GET /mcp/info —— 可选,用于查看服务说明
POST /mcp initialize + X-API-Key —— 验证 API Key,创建 session,返回 Mcp-Session-Id
POST /mcp tools/list + Mcp-Session-Id —— 使用 session.user_id,返回已订阅工具
POST /mcp tools/call + Mcp-Session-Id —— 使用 session.user_id / api_key_id,再次校验 API Key 和订阅,执行工具
核心原则:
身份认证可复用 session;关键操作授权必须实时校验。
20. 一句话说明
MCP 是 AI 客户端与工具服务之间的标准协议。对于远程工具平台,推荐使用 Streamable HTTP,通过 X-API-Key 完成首次认证,通过服务端 session 维持上下文,通过 tools/list 暴露已订阅工具,通过 tools/call 调用工具,并在每次关键操作时实时校验 API Key、订阅和工具状态。
