MCP 系列(协议篇):深入理解 MCP 协议机制

0 阅读9分钟

📖 引言

在上一篇基础篇中,我们解决了“为什么要用 MCP”的问题。
这一篇,我们继续回答“MCP 到底是怎么通信的”。

如果你希望 AI 不只是聊天,而是真的能稳定地调用数据库、业务系统或第三方 API,那么协议层就是必须吃透的一环。

MCP 的核心在于两大基础协议:

  1. 消息协议:JSON-RPC 2.0(定义消息格式)
  2. 传输协议: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 协议的关键步骤。

初始化流程:

  1. Client 发送初始化请求

    • Client 通过 initialize 方法发送请求,包含 Client 的 capabilities(能力声明)
    • 声明 Client 支持的功能,如是否支持 tools、resources、prompts 等
  2. Server 返回初始化响应

    • Server 处理初始化请求,返回 Server 的 capabilities
    • 包含 Server 支持的功能列表和版本信息
  3. 能力协商

    • 双方根据各自的 capabilities 协商最终支持的功能
    • 只有双方都支持的功能才能使用
  4. 初始化完成

    • 初始化完成后,Client 才能调用 list_tools()list_resources() 等方法
    • 未初始化就调用其他方法会导致错误

初始化时序图:

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 消息来实现通信。

基本通信过程:

04.png

详细描述:

  1. 启动子进程(MCP Server)

    • MCP Client 以子进程形式启动 MCP Server,通过命令行指定 Server 的可执行文件及其参数
  2. 消息交换

    • MCP Client 通过 stdin 向 MCP Server 写入 JSON-RPC 消息
    • MCP Server 处理请求后,通过 stdout 返回 JSON-RPC 消息,也可通过 stderr 输出日志
  3. 生命周期管理

    • 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 来对应

基本通信过程:

05.png

详细描述:

  1. 连接建立:Client 首先请求建立 SSE 连接,Server"同意",然后生成并推送唯一的 Session ID

  2. 请求发送:Client 通过 HTTP POST 发送 JSON-RPC 2.0 请求(请求中会带有 Session ID 和 Request ID 信息)

  3. 请求接收确认:Server 接收请求后立即返回 202(Accepted)状态码,表示已接受请求

  4. 异步处理:Server 应用框架会自动处理请求,根据请求中的参数,决定调用某个工具或资源

  5. 结果推送:处理完成后,Server 通过 SSE 通道推送 JSON-RPC 2.0 响应,其中带有对应的 Request ID

  6. 结果匹配:Client 的 SSE 连接侦听接收到数据流后,会根据 Request ID 将接收到的响应与之前的请求匹配

  7. 重复处理:循环步骤 2-6 这个过程(这里面包含一个 MCP 的初始化过程)

  8. 连接断开:在 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 单向推送,缺乏灵活性

基本通信过程:

06.png

主要变化:

  • ✅ 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)])

三种模式对比总结:

特性STDIOSSE RemoteStreamable HTTP
连接方式本地子进程HTTP + SSE 双通道统一 HTTP 端点
部署场景本地/单机远程(需长连接)远程(支持无状态)
连接可靠性中等(SSE 断开需重建)高(支持自动恢复)
扩展性中等高(无状态设计)
适用场景开发/简单部署实时推送需求生产环境/云部署

📝 总结

通过本文,我们深入了解了 MCP 的两大基础协议:

  1. 消息协议(JSON-RPC 2.0):定义了标准化的消息格式,确保不同系统间的互操作性
  2. 传输协议(STDIO/SSE/Streamable HTTP):提供了多种传输方式,适应不同的部署场景

如果你现在要做技术选型,可以先按这个原则:

  • 本地开发调试优先选 STDIO
  • 远程且需要持续推送可用 SSE
  • 生产部署与可扩展性优先选 Streamable HTTP

下期预告:在下一篇文章中,我们将进入实战环节,包括:

  • 完整的代码示例(Server 和 Client)
  • 开发最佳实践
  • 常见问题与解决方案

📚 参考资源