抓包解析MCP协议:基于JSON-RPC的MCP host与MCP server的交互
本文通过抓包剖析模型上下文协议的通信机制,我们将从核心概念入手,最终聚焦于最“硬核”的部分:MCP Host、MCP Server 和 MCP Tools 之间是如何通过JSON-RPC 2.0协议进行字节级通信的。
一、 核心概念解析
在深入通信细节之前,我们必须先理解舞台上的各个角色。
- 大模型
· 角色: 整个交互过程的“大脑”或“决策者”。例如 GPT、Llama 等大型语言模型。
· 职责: 理解用户的自然语言指令,决定需要调用哪些工具来获取信息或执行任务,并最终整合信息生成对用户友好的回复。注意:大模型本身并不直接与 MCP Server 通信。 - MCP Server
· 角色: 资源和能力的提供者。它是一个独立的进程,管理着特定的资源或提供一组特定的功能。
· 职责:
· 向 MCP Host 注册自己可用的 Tools 和 Resources。
· 监听来自 MCP Host 的请求。
· 执行具体的 Tool 调用或访问指定的 Resource,并将结果返回给 MCP Host。
· 示例: 一个“数据库 Server”可以提供 run_query Tool;一个“文件系统 Server”可以提供 read_file Resource 和 write_file Tool。 - MCP Tools & Resources
· MCP Tools: 可执行的函数。它们由 MCP Server 提供,有明确的输入参数,可以被调用并执行一个动作(如发送邮件、写入数据库)。
· MCP Resources: 只读的数据源。它们也由 MCP Server 提供,描述了一个可供读取的数据流或静态数据,大模型可以“订阅”这些资源以获取上下文信息(如实时股票价格、日志文件流)。 - MCP Host
· 角色: 通信的中枢和代理。它是与大模型交互的应用程序(如 AI 聊天界面、AI Agent 框架),同时负责管理一个或多个 MCP Server。
· 职责:
· 启动和管理 MCP Server: 加载 Server 配置并启动这些外部进程。
· 建立通信桥梁: 与 MCP Server 建立双向通信(通常是 STDIN/STDOUT 或 WebSocket)。
· 协议转换: 接收大模型的“意图”,将其转换为标准的 JSON-RPC 请求发送给对应的 MCP Server;同时将 Server 的 JSON-RPC 响应转换回大模型能理解的格式。
· 提供上下文: 将 Tools 和 Resources 的描述信息作为上下文提供给大模型。 - JSON-RPC 2.0 协议
· 角色: 通信的“普通话”或标准格式。它是一种轻量级的远程过程调用协议,使用 JSON 作为数据格式。
· 特点: 简单、无状态、与传输层无关。MCP 完全基于此协议构建,确保了不同实现之间的互操作性。
· 核心报文结构:
· 请求: {“jsonrpc”: “2.0”, “id”: 1, “method”: “method_name”, “params”: {…}}
· 成功响应: {“jsonrpc”: “2.0”, “id”: 1, “result”: …}
· 错误响应: {“jsonrpc”: “2.0”, “id”: 1, “error”: {“code”: -32601, “message”: “Method not found”}}
二、 通信流程图
一次完整的交互可以分为三个主要阶段:初始化、工具调用、资源订阅。下图清晰地展示了整个流程中的数据流向:
三、 字节级通信流程详解
现在,让我们打开“抓包工具”,看看在 阶段一(初始化) 和 阶段三(工具调用) 中,MCP Host 和 MCP Server 之间实际传输的 JSON-RPC 报文是什么样子的。
通信基础: MCP 通常使用 标准输入和标准输出 与 Server 进程通信。这意味着每一条 JSON-RPC 消息都是一行 JSON 文本,以换行符 \n 分隔。
- 初始化阶段
· 步骤 1: MCP Host 启动 MCP Server 进程
· Host 通过命令行或配置启动 Server 进程,并建立好 STDIN/STDOUT 管道。
· 步骤 2: Server 发送 initialize 请求
· Server 首先向 Host 发送初始化请求,协商协议版本。
· Server -> Host 发送的字节流:
json {"jsonrpc":"2.0","id":"init_1","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"my_mcp_host","version":"1.0.0"}}} \n
· Host -> Server 回复的字节流:
json {"jsonrpc":"2.0","id":"init_1","result":{"protocolVersion":"2024-11-05","capabilities":{}}} \n
· 步骤 3: Server 通知 Host 可用的 Tools 和 Resources
· 初始化成功后,Server 会主动发送通知,告知 Host 自己有哪些能力。
· Server -> Host 发送的字节流(通知 Tools):
json {"jsonrpc":"2.0","method":"tools/list","params":{"tools":[{"name":"get_weather","description":"获取指定城市的天气","inputSchema":{"type":"object","properties":{"city":{"type":"string","description":"城市名称"}},"required":["city"]}}]}} \n
· Server -> Host 发送的字节流(通知 Resources):
json {"jsonrpc":"2.0","method":"resources/list","params":{"resources":[{"uri":"file:///logs/app.log","name":"应用日志","description":"实时应用日志流","mimeType":"text/plain"}]}} \n
· 注意: 这是 method 不带 id 的 通知,Host 不会对此进行回复。
- 工具调用阶段(当用户提问“查询北京的天气”)
· 步骤 1: MCP Host 将工具调用请求封装为 JSON-RPC callTool 请求
· Host 收到大模型的指令后,会构造一个 callTool 请求。
· Host -> Server 发送的字节流:
json {"jsonrpc":"2.0","id":"call_123","method":"callTool","params":{"name":"get_weather","arguments":{"city":"北京"}}} \n
· 步骤 2: MCP Server 执行并返回结果
· Server 收到请求,解析出要调用 get_weather 工具,参数是 {“city": “北京”}。然后它执行内部逻辑(比如调用一个天气 API),最后将结果封装返回。
· Server -> Host 发送的字节流(成功响应):
json {"jsonrpc":"2.0","id":"call_123","result":{"content":[{"type":"text","text":"北京当前天气晴朗,气温25摄氏度。"}]}} \n
· 如果调用出错,可能返回:
json {"jsonrpc":"2.0","id":"call_123","error":{"code":-32000,"message":"Weather API unavailable","data":{"retryAfter":30}}} \n
四、 总结
通过以上的分解,我们可以清晰地看到其实MCP的协议只是规范了Host与Server间的交互过程,至于Host如何与各模型间的交换,并不是MCP协议里规定的,MCP协议只规定了两部分内容:
- 每个MCP Server有哪些tools与资源可以用。
- Host如何调用这些函数与资源。
至于Host与各模型间如何交换与使用什么协议,各模型的规范都不一样,不在本文的描述范围。
MCP协议的价值或者说作用在于如下描述:
- MCP 的核心价值在于通过标准化协议将大模型的“思考”与外部世界的“能力”解耦。
- MCP Host是关键的中介,它负责协议转换和进程管理。
- JSON-RPC 2.0 是通信的基石,其简洁的请求-响应模型非常适合这种场景。每一条消息都是一行完整的、以换行符结尾的 JSON 文本。
- 整个通信流程是结构化的、可预测的。从 initialize 握手,到 tools/list 广播能力,再到具体的 callTool 执行,每一步都遵循明确的规范。
这种设计使得开发者可以轻松地为大模型扩展任何能力(只需按照协议规范实现一个 MCP Server),而 AI 应用开发者也可以无缝地集成这些能力(只需实现一个 MCP Host),从而极大地丰富了大模型的应用生态。