MCP 协议拆解:从 JSON-RPC 信封到 Agent 全链路

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<br/>(文件系统)"] S2["MCP Server B<br/>(数据库)"] end C1 <-->|"stdio / HTTP"| S1 C2 <-->|"stdio / HTTP"| S2

Host 是用户面对的 AI 应用。它管 UI、管对话历史、管 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 格式封装。这是"血管系统"------理解它就理解了消息在网络上到底长什么样。

JSON-RPC 只有三种信封:

Request (请求)------有 id,期望对方回一个 Response:

json 复制代码
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "read_file", "arguments": { "path": "config.json" } } }

Response (响应)------id 和 Request 配对,说明是回复哪个请求:

json 复制代码
{ "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "文件内容..." }] } }

Notification (通知)------没有 id,不期望回复:

json 复制代码
{ "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 帮你做了所有序列化/反序列化。生产环境不需要手动 parse,但理解信封格式能帮你在 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 告诉 Server "我支持 roots 和 sampling",Server 告诉 Client "我有 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_filelist_directorysearch_files------LLM 一看名字就知道这个工具干什么。不用 fileManagerhandleData 这种模糊的命名。

描述用两句话公式。 第一句说使用场景,第二句划定边界:

perl 复制代码
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 是官方提供的图形化调试工具,用法:

bash 复制代码
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 实操(Day 13)和自定义 Server 开发就是在这个骨架上填肉了。

相关推荐
机器之心1 小时前
当Token飙到天文数字,高通用「计算连续体」重搭智能体新基建
人工智能·openai
weixin_468466851 小时前
液态神经网络新手入门与实战指南
人工智能·深度学习·神经网络·ai·机器视觉·液态神经网络
机器之心1 小时前
一夜之间,ChatGPT与Codex合并了
人工智能·openai
机器之心1 小时前
老黄的Cosmos 3刚发一天,就被一家中国公司反超了
人工智能·openai
标书畅畅行1 小时前
钛投标标书查重系统技术架构与功能实现解析
大数据·人工智能
Stick_ZYZ1 小时前
从“能调用工具”到“能稳定执行任务”:Agent 工程化的下一步
java·人工智能·后端·spring·ai
宸一2 小时前
Day 4:用后端思维拆解Agent核心架构——三元组、工具调用、错误处理
人工智能
KaMeidebaby2 小时前
卡梅德生物技术快报|蛋白翻译后修饰:YAP/TAZ 分子调控机制与靶向干预技术
前端·人工智能·物联网·百度·新浪微博
阿里云大数据AI技术2 小时前
DataWorks Data Agent:从增强到自主,数据智能体的范式跃迁
人工智能·agent