MCP协议拆解:JSON-RPC到Agent全链路权威指南
摘要
MCP协议采用三层架构,Host、Client、Server各司其职。底层通信基于JSON-RPC2 0信封格式,经历初
MCP 协议深度解析:从 JSON-RPC 通信层到 Agent 完整链路
这篇内容来源于 Agent 工程化学习(Phase 04)的阶段性复盘。核心目标是将对 MCP 协议的认知,从“能用 SDK 跑通”升级到“理解每条消息的意图、每层抽象的设计理由”。所有观点来自实际编码练习与课程任务,并非规范文档的翻译。

读者对象:已能成功运行 MCP Server,但对内部运作机制仍感模糊的开发者。
MCP 要解决的核心痛点
AI Agent 需要调用外部能力(读文件、查数据库、发请求),每个 Host 应用都重复实现一套工具接口。同一种工具在不同应用中需要写两遍——就像 USB 标准诞生前,每款外设都要配专属接口。
MCP(Model Context Protocol)正是这个统一接口标准。任何 Host(CatDesk、Cursor、Claude Desktop)都能连接任意 MCP Server。一次编写 Server,处处可用。
它的地位与 HTTP 类似:HTTP 统一了浏览器与服务器的通信格式,MCP 统一了 AI 应用与工具提供方的通信格式。
三层架构:Host、Client、Server
MCP 将参与方划分为三层,各层职责明确:
flowchart LR
subgraph Host["Host (CatDesk / Cursor)"]
UI["UI + 对话管理"]
LLM["LLM 调用"]
C1["MCP Client A"]
C2["MCP Client B"]
end
subgraph Servers["独立进程"]
S1["MCP Server A
(文件系统)"]
S2["MCP Server B
(数据库)"]
end
C1 <-->|"stdio / HTTP"| S1
C2 <-->|"stdio / HTTP"| S2
Host 是用户直接面对的 AI 应用。它负责界面渲染、对话历史管理、LLM 调用等,内部包含一个或多个 Client 实例。
Client 扮演协议适配层角色。每个 Client 对应一个 Server 连接,承担三项职责:管理连接生命周期、转换工具格式(MCP Schema ↔ AI SDK Schema)、将调用请求路由到正确的 Server。
Server 是能力提供方。它以独立进程运行,通过传输层与 Client 通信,暴露 Tools、Resources、Prompts 三类能力。
将 Client 单独抽离的根本原因是关注点分离。Host 只需关心“可用的工具清单”,Server 只需关心“收到请求后的执行逻辑”,中间的连接管理、格式转换、传输层适配全部封装在 Client 中。这样一来,更换 LLM 框架只需调整 Client 的翻译逻辑,更换传输方式只需修改 Client 的底层实现,上下两层均无需改动。
类比浏览器:前端代码只管发 fetch 收数据,TCP 连接池、TLS 握手、HTTP/2 多路复用等底层工作,全由浏览器的网络栈(Client 层)自动处理。
底层通信格式:JSON-RPC 2.0
MCP 的所有消息,无论通过 stdio 还是 HTTP 传输,均采用 JSON-RPC 2.0 格式封装。这相当于 MCP 的“血管系统”——理解它,就能看懂消息在网络中的真实形态。
JSON-RPC 只有三种信封类型:
Request(请求)——包含 id,期望对方返回 Response:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "read_file", "arguments": { "path": "config.json" } } }
Response(响应)——id 与 Request 配对,标明是对哪个请求的回复:
{ "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "文件内容..." }] } }
Notification(通知)——不含 id,无需回复:
{ "jsonrpc": "2.0", "method": "notifications/initialized" }
几个关键认知点:
id 是异步配对机制。Client 同时发出 3 个请求(id=1,2,3),Server 可以乱序返回,Client 依靠 id 匹配“这条回复对应哪个请求”。Notification 无需 id,因为没人需要回复它。
错误码遵循标准规范。-32601 Method not found、-32602 Invalid params 均由 JSON-RPC 规范定义,Server 返回错误时应使用这些码,不得自行编造。
SDK 自动处理所有序列化与反序列化。生产环境无需手动解析,但理解信封格式能帮你在 Inspector 中看懂消息流,出问题时更快定位。
三阶段生命周期
一个 MCP 连接从建立到关闭,经历三个明确阶段:
sequenceDiagram
participant C as Client
participant S as Server
rect rgb(230,245,255)
Note over C,S: Phase 1: Initialize(能力协商)
C->>S: initialize { protocolVersion, capabilities, clientInfo }
S-->>C: result { protocolVersion, capabilities, serverInfo }
C->>S: notifications/initialized
end
rect rgb(230,255,230)
Note over C,S: Phase 2: Operation(正常工作)
C->>S: tools/list
S-->>C: result { tools: [...] }
C->>S: tools/call { name, arguments }
S-->>C: result { content: [...] }
C->>S: resources/read { uri }
S-->>C: result { contents: [...] }
end
rect rgb(255,240,230)
Note over C,S: Phase 3: Shutdown(关闭)
Note over C,S: 传输层信号:EOF / HTTP disconnect
end
Phase 1 核心是能力协商。Client 声明“我支持 roots 和 sampling”,Server 声明“我有 tools 和 resources”。后续操作不得超出对方声明的能力——例如 Client 未声明 sampling,Server 就不能调用 sampling/createMessage。这是硬性约束。
initialized 为何是 Notification 而非 Request?因为 Server 无需确认,Client 只是单向通知“我已就绪,可进入工作阶段”。一个简单的“我就绪”信号。
Phase 3 没有结构化的 shutdown 方法。关闭由传输层信号完成——stdio 场景关闭 stdin/stdout,HTTP 场景 DELETE session。简洁且足够。
六大原语
MCP 定义了六种能力原语,分为两组:
Server 暴露给 Client(3 个):
- Tools:Agent 主动调用的操作,可能产生副作用(写文件、发请求)。LLM 在推理过程中自主决定是否调用。
- Resources:只读数据,通过 URI 寻址(如
file://workspace/info)。通常在对话开始前预加载给 LLM 作为背景知识,也可在推理过程中按需读取。 - Prompts:预定义的提示词模板。Host 展示给用户选择,用户触发后注入对话。
Client 暴露给 Server(3 个):
- Roots:告知 Server 可访问的工作区路径。
- Sampling:Server 请求 Client 侧的 LLM 生成内容(反向调用)。
- Elicitation:Server 向用户提问获取输入。
日常开发最常接触的是前三个。其中 Tool 是核心——Agent 的“动手能力”全靠 Tool 支撑。
Resource 与 Tool 的本质区别
一个易混淆点:Resource 的 URI(如 file://workspace/info)看似文件路径,实为 Server 自行定义的虚拟地址。当 Client 调用 readResource("file://workspace/info") 时,Server 并非去磁盘找文件,而是执行注册时绑定的 async 函数——可能是读目录拼接 JSON、查数据库、调 API,一切皆可。
Tool 与 Resource 的本质差异在于触发者及是否有副作用:
- Resource:Host 在对话开始前预加载,为 LLM 提供背景知识。无副作用,类似 GET 请求。
- Tool:LLM 在推理过程中自主决定调用。可能有副作用(写文件、删记录),类似 POST 请求。
Tool Schema 设计原则
Tool 的 Schema 直接影响 LLM 能否在正确时机选中正确工具。以下四条原则可参考:
命名采用 snake_case + 动宾结构。 read_file、list_directory、search_files——LLM 一眼就能识别工具用途。避免 fileManager、handleData 这类模糊命名。
描述采用两句话公式。 第一句说明使用场景,第二句划定边界:
Use when you need to read the contents of a file at a given path.
Do not use for directories or binary files.
第二句“不要用来做什么”至关重要——LLM 常在相似工具间犹豫,边界描述能快速排除错误选项。
粒度选择 Atomic 而非 Monolithic。 read_file + write_file 优于 file_manager(action: "read"|"write"|"delete")。原因在于 LLM 更容易直接选对精确工具,而非先选中大工具再决定 action 参数。
inputSchema 每个字段必须包含 description。 LLM 需要知道每个参数的含义、格式与约束。用 required 明确必选参数,用 enum 限制取值范围。清晰度直接影响工具调用成功率。
完整调用链路:一条消息的旅程
将各层串联,观察一次完整调用:
sequenceDiagram
participant User as 用户
participant Host as Host (CatDesk)
participant LLM as LLM (DeepSeek)
participant Client as MCP Client
participant Server as MCP Server
User->>Host: "帮我读一下 config.json"
Note over Host,LLM: Host 把工具菜单 + 用户消息一起发给 LLM
Host->>LLM: messages + tools 列表
Note over LLM: LLM 自主判断需要调用 read_file
LLM-->>Host: tool_call: read_file(path="config.json")
Note over Host,Client: Host 把 LLM 的决策交给 Client 执行
Host->>Client: 转发 tool_call
Note over Client,Server: Client 翻译成 JSON-RPC 格式发给 Server
Client->>Server: {"method":"tools/call","params":{...}}
Server-->>Client: {"result":{"content":[...]}}
Note over Host,Client: Client 提取结果返回
Client-->>Host: 文件内容
Note over Host,LLM: Host 把工具结果塞回 LLM context
Host->>LLM: tool_result 放入 context
Note over LLM: LLM 基于结果组织自然语言回答
LLM-->>Host: "config.json 的内容是..."
Host-->>User: 展示回答
几个易误解点:
LLM 不是“建议你使用工具”,而是直接输出调用指令。整个流程是自动化的 Agent Loop,用户几乎无感知。
Host 拿到工具结果后不直接展示给用户,而是先塞回 LLM。让 LLM 基于结果组织回答——它可能总结,可能继续调用另一个工具,直到认为任务完成才输出文本。
一次对话可能循环多轮。用户说“帮我找所有 .ts 文件然后读 package.json”,LLM 会先调 search_files,看到结果后再调 read_file,两轮工具调用完成后给出最终回答。
如何观测每一层
调试 MCP 应用时,不同层需要不同观测工具:
| 观测层 | 观测方式 | 可获取信息 |
|---|---|---|
| LLM 决策(Host 层) | AI SDK 的 onStepFinish 回调 | 每轮选择了哪些工具、传了哪些参数、finishReason 是 tool-calls 还是 stop |
| Client ↔ Server 通信 | Client 的 verbose 模式或 JSON-RPC 追踪 | 工具调用耗时、返回内容预览、错误信息 |
| JSON-RPC 原始信封 | traceJsonRpc 模式或 MCP Inspector | 每条消息的完整 JSON:id、method、params、result |
MCP Inspector 是官方提供的图形化调试工具,使用非常直接:
npx @modelcontextprotocol/inspector npx tsx src/04-tools-mcp/03-file-server/index.ts .
它让你手动扮演 Client 角色:点击 tools/list 查看已注册的工具,点击 tools/call 填写参数测试执行。Inspector 能展示 Client↔Server 段的所有 JSON-RPC 消息,但看不到 LLM 决策——那部分需要在 Host 代码中添加 onStepFinish 回调。
编写 MCP Server 的标准流程
五步即可完成:
1. 声明身份——创建 McpServer 实例,赋予名称和版本号。
2. 注册能力——通过 registerTool / registerResource / registerPrompt 挂载能力。每个 Tool 需定义 name、description、inputSchema、handler。
3. 连接传输层——new StdioServerTransport() + server.connect(transport)。SDK 自动处理 initialize 握手。
4. Inspector 调试——无需编写 Client 代码即可验证 Server 是否正常。手动测试每个 Tool 的输入输出。
5. 接入 Host——将 Server 配置到 CatDesk / Cursor 的 MCP 配置中,Agent 即可使用。
生产环境中,90% 的代码集中在两件事:定义 Schema(告知 LLM 能做什么)和实现 Handler(真正执行逻辑)。握手、路由、序列化全部由 SDK 处理。
总结
MCP 的核心设计哲学清晰明了:Server 声明自己能做什么(Schema),Client 负责连接与翻译,Host 让 LLM 自主决定调用什么。各层各司其职,依靠 JSON-RPC 信封在中间传递。
掌握这个层次后,后续的 Inspector 实操与自定义 Server 开发,不过是在此骨架上填充血肉。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。