📖 引言
在上一篇基础篇中,我们解决了“为什么要用 MCP”的问题。
这一篇,我们继续回答“MCP 到底是怎么通信的”。
如果你希望 AI 不只是聊天,而是真的能稳定地调用数据库、业务系统或第三方 API,那么协议层就是必须吃透的一环。
MCP 的核心在于两大基础协议:
- 消息协议:JSON-RPC 2.0(定义消息格式)
- 传输协议:STDIO、SSE、Streamable HTTP(定义传输方式)
本文你将获得三件事:
- 理解 MCP 初始化握手为什么是“第一步”
- 看懂三种传输方式的差异与适用场景
- 拿到可对照的最小代码片段,便于快速落地
🔌 MCP 两大基础协议介绍
2.1 消息协议:JSON-RPC 2.0
在 MCP 中规定了唯一的标准消息格式,就是 JSON-RPC 2.0。
JSON-RPC 2.0 是一种轻量级的、用于远程过程调用(RPC)的消息交换协议,使用 JSON 作为数据格式。
💡 注意:它不是一个底层通信协议,只是一个应用层的消息格式标准。
这种消息协议的好处包括:
- ✅ 与语言无关:几乎所有现代编程语言都支持 JSON
- ✅ 简单易用:结构简单,天然可读,易于调试
- ✅ 轻量灵活:可以适配各种传输方式
2.1.1 MCP 初始化流程(Handshake)
在 MCP Client 与 Server 建立连接后,必须进行初始化握手(Handshake)才能开始正常通信。这是 MCP 协议的关键步骤。
初始化流程:
-
Client 发送初始化请求
- Client 通过
initialize方法发送请求,包含 Client 的 capabilities(能力声明) - 声明 Client 支持的功能,如是否支持 tools、resources、prompts 等
- Client 通过
-
Server 返回初始化响应
- Server 处理初始化请求,返回 Server 的 capabilities
- 包含 Server 支持的功能列表和版本信息
-
能力协商
- 双方根据各自的 capabilities 协商最终支持的功能
- 只有双方都支持的功能才能使用
-
初始化完成
- 初始化完成后,Client 才能调用
list_tools()、list_resources()等方法 - 未初始化就调用其他方法会导致错误
- 初始化完成后,Client 才能调用
初始化时序图:
Client Server
| |
|---- initialize(capabilities, protocolVersion) --->|
| |
|<--- result(serverInfo, capabilities, version) ----|
| |
|---- initialized / 后续 list_tools、call_tool ---->|
| |
代码示例:
# Client 端初始化
async with stdio_client(server_params) as (read_stream, write_stream):
session = ClientSession(read_stream, write_stream)
async with session:
# 必须首先调用 initialize()
capabilities = await session.initialize()
# 查看 Server 支持的能力
print(f"Server capabilities: {capabilities.capabilities}")
# 输出示例:
# {
# "tools": {},
# "resources": {},
# "prompts": {}
# }
# 初始化完成后,才能调用其他方法
tools = await session.list_tools()
resources = await session.list_resources()
初始化的重要性:
- ✅ 确保 Client 和 Server 版本兼容
- ✅ 明确双方支持的功能,避免调用不支持的方法
- ✅ 建立会话上下文,为后续通信做准备
2.1.2 一眼看懂 JSON-RPC 请求结构
如果你第一次接触 MCP,可以先记住这个最小请求结构(以 initialize 为例):
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
},
"clientInfo": {
"name": "demo-client",
"version": "1.0.0"
}
}
}
你可以把它理解成一句话:“我是谁、我支持什么、我想按哪个协议版本和你通信。”
2.2 传输协议
MCP 支持三种传输协议,分别适用于不同的使用场景:
2.2.1 STDIO 模式
STDIO(Standard Input/Output) 是一种基于标准输入(stdin)和标准输出(stdout)的本地通信方式。
MCP Client 启动一个子进程(MCP Server)并通过 stdin 和 stdout 交换 JSON-RPC 消息来实现通信。
基本通信过程:
详细描述:
-
启动子进程(MCP Server)
- MCP Client 以子进程形式启动 MCP Server,通过命令行指定 Server 的可执行文件及其参数
-
消息交换
- MCP Client 通过 stdin 向 MCP Server 写入 JSON-RPC 消息
- MCP Server 处理请求后,通过 stdout 返回 JSON-RPC 消息,也可通过 stderr 输出日志
-
生命周期管理
- MCP Client 控制子进程(MCP Server)的启动和关闭
- 通信结束后,MCP Client 关闭 stdin,终止 MCP Server
适用场景:本地开发、单机部署、简单集成场景。
最小代码(STDIO Client):
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio
server_params = StdioServerParameters(
command="python",
args=["mysqlMCPServer.py"],
)
async def main():
async with stdio_client(server_params) as (read_stream, write_stream):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
tools = await session.list_tools()
print(tools)
if __name__ == "__main__":
asyncio.run(main())
2.2.2 基于 SSE 的 Remote 模式(旧版远程主流)
SSE(Server-Sent Events,服务器发送事件) 是一种基于 HTTP 协议的单向通信技术,允许 Server 主动实时向 Client 推送消息,Client 只需建立一次连接即可持续接收消息。
特点:
- 单向(仅 Server → Client)
- 基于 HTTP 协议,一般借助一次 HTTP Get 请求建立连接
- 适合实时消息推送场景(如进度更新、实时数据流等)
由于 SSE 是一种单向通信的模式,所以它需要配合 HTTP Post 来实现 Client 与 Server 的双向通信。
严格地说,这是一种 HTTP Post(Client → Server)+ HTTP SSE(Server → Client) 的伪双工通信模式。
这种传输模式下:
- 一个 HTTP Post 通道,用于 Client 发送请求(如调用 MCP Server 中的 Tools 并传递参数),此时 Server 会立即返回
- 一个 HTTP SSE 通道,用于 Server 推送数据(如返回调用结果或更新进度)
- 两个通道通过 session_id 来关联,而请求与响应则通过消息中的 id 来对应
基本通信过程:
详细描述:
-
连接建立:Client 首先请求建立 SSE 连接,Server"同意",然后生成并推送唯一的 Session ID
-
请求发送:Client 通过 HTTP POST 发送 JSON-RPC 2.0 请求(请求中会带有 Session ID 和 Request ID 信息)
-
请求接收确认:Server 接收请求后立即返回 202(Accepted)状态码,表示已接受请求
-
异步处理:Server 应用框架会自动处理请求,根据请求中的参数,决定调用某个工具或资源
-
结果推送:处理完成后,Server 通过 SSE 通道推送 JSON-RPC 2.0 响应,其中带有对应的 Request ID
-
结果匹配:Client 的 SSE 连接侦听接收到数据流后,会根据 Request ID 将接收到的响应与之前的请求匹配
-
重复处理:循环步骤 2-6 这个过程(这里面包含一个 MCP 的初始化过程)
-
连接断开:在 Client 完成所有请求后,可以选择断开 SSE 连接,会话结束
💡 简单总结:通过 HTTP Post 发送请求,但通过 SSE 的长连接异步获得 Server 的响应结果。
适用场景:远程部署、实时推送需求。
最小代码(SSE Server):
from fastapi import FastAPI, Request
from mcp.server.sse import SseServerTransport
from starlette.routing import Mount
from mysqlMCPServer import mcp
app = FastAPI()
sse = SseServerTransport("/messages/")
app.router.routes.append(Mount("/messages", app=sse.handle_post_message))
@app.get("/sse")
async def handle_sse(request: Request):
async with sse.connect_sse(request.scope, request.receive, request._send) as (read_stream, write_stream):
await mcp.run(read_stream, write_stream, mcp.create_initialization_options())
2.2.3 Streamable HTTP 模式
在 MCP 新标准(2025-03-26 版)中,MCP 引入了新的 Streamable HTTP 远程传输机制来代替之前的 HTTP+SSE 的远程传输模式,STDIO 的本地模式不变。
该新标准还在 OAuth 2.1 的授权框架、JSON-RPC 批处理、增强工具注解等方面进行增加和调整,且在 2025 年 5 月 8 日发布的 MCP SDK 1.8.0 版本中正式支持了 Streamable HTTP。
HTTP+SSE 方式存在的问题:
- ❌ 需要维护两个独立的连接端点
- ❌ 有较高的连接可靠性要求,一旦 SSE 连接断开,Client 无法自动恢复,需要重新建立新连接,导致上下文丢失
- ❌ Server 必须为每个 Client 维持一个高可用长连接,对可用性和伸缩性提出挑战
- ❌ 强制所有 Server 向 Client 的消息都经由 SSE 单向推送,缺乏灵活性
基本通信过程:
主要变化:
- ✅ Server 只需一个统一的 HTTP 端点(例如
/mcp或/messages)用于通信 - ✅ Client 可以完全无状态的方式与 Server 进行交互,即 RESTful HTTP Post 方式
- ✅ 必要时 Client 也可以在单次请求中获得 SSE 方式响应(如需要进度通知的长时间运行任务,可以借助 SSE 不断推送进度)
- ✅ Client 也可以通过 HTTP Get 请求来打开一个长连接的 SSE 流,这种方式与当前的 HTTP+SSE 模式类似
- ✅ 增强的 Session 管理:Server 会在初始化时返回 Mcp-Session-Id,后续 Client 在每次请求中需要携带该 MCP-Session-Id
- Mcp-Session-Id 用来关联一次会话的多次交互
- Server 可以用 Session-Id 来终止会话,要求 Client 开启新会话
- Client 也可以用 HTTP Delete 请求来终止会话
Streamable HTTP 的优势:
- ✅ 允许无状态的 Server 存在,不依赖长连接,有更好的部署灵活性与扩展能力
- ✅ 对 Server 中间件的兼容性更好,只需要支持 HTTP 即可,无需做 SSE 处理
- ✅ 允许根据自身需要开启 SSE 响应或长连接,保留了现有规范 SSE 模式的优势
适用场景:生产环境、云部署、需要高可扩展性的场景。
最小代码(Streamable HTTP Server):
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from starlette.applications import Starlette
from starlette.routing import Mount
from mysqlMCPServer import mcp
session_manager = StreamableHTTPSessionManager(
app=mcp,
event_store=None,
json_response=None,
stateless=True,
)
async def handle_streamable_http(scope, receive, send):
await session_manager.handle_request(scope, receive, send)
starlette_app = Starlette(routes=[Mount("/mcp", app=handle_streamable_http)])
三种模式对比总结:
| 特性 | STDIO | SSE Remote | Streamable HTTP |
|---|---|---|---|
| 连接方式 | 本地子进程 | HTTP + SSE 双通道 | 统一 HTTP 端点 |
| 部署场景 | 本地/单机 | 远程(需长连接) | 远程(支持无状态) |
| 连接可靠性 | 高 | 中等(SSE 断开需重建) | 高(支持自动恢复) |
| 扩展性 | 低 | 中等 | 高(无状态设计) |
| 适用场景 | 开发/简单部署 | 实时推送需求 | 生产环境/云部署 |
📝 总结
通过本文,我们深入了解了 MCP 的两大基础协议:
- 消息协议(JSON-RPC 2.0):定义了标准化的消息格式,确保不同系统间的互操作性
- 传输协议(STDIO/SSE/Streamable HTTP):提供了多种传输方式,适应不同的部署场景
如果你现在要做技术选型,可以先按这个原则:
- 本地开发调试优先选 STDIO
- 远程且需要持续推送可用 SSE
- 生产部署与可扩展性优先选 Streamable HTTP
下期预告:在下一篇文章中,我们将进入实战环节,包括:
- 完整的代码示例(Server 和 Client)
- 开发最佳实践
- 常见问题与解决方案
📚 参考资源
- MCP 文档手册:modelcontextprotocol.io/introductio…
- MCP 中文文档:mcp-docs.cn/introductio…
- Python SDK 的 GitHub 地址:github.com/modelcontex…