0%

MCP协议:如何构建大模型访问REST服务的统一通道

本文对应同名视频,建议配合视频观看效果更佳。视频从真实抓包出发,完整演示了握手、工具调用与 Swagger 自动映射三大核心流程。

就在 2026 年 7 月 28 号,MCP 发布了协议诞生以来最大的一次修订——握手机制和 Mcp-Session-Id 被正式砍掉,官方原话是 largest revision of the protocol since launch

但讽刺的是,Anthropic 今年 6 月才刚公布,MCP 的安装量已经 9700 万。这么大的存量,现网里绝大多数还跑着旧版机制。

有一份第三方基准测试的数字:让大模型直接照着 API 文档自己拼接调用后端接口,复杂任务成功率只有 92.31% ;换成 MCP 协议做中间层之后,成功率到 100% ,调用次数和耗时都少了近 20%。

今天从真实抓包出发,把这套刚被官方判”过时”、但现网还在大规模跑的 MCP 机制,拆给你看。

视频讲解

为什么需要 MCP

大模型调用外部接口,本来只有两条路,都不太舒服。

第一条路:让大模型直接读 API 文档、自己拼请求。但它本质上是个基于概率的文本预测引擎,没有严谨契约约束的时候,参数拼错、长上下文注意力分散引发的幻觉,出错率很难压下去——92.31% 的成功率就是这条路的现实写照。

第二条路:退回到各厂商私有的 Function Calling 格式——准是准了,但每换一种大模型,网关就得重写一遍适配层,陷入生态孤岛。

MCP 协议做的事,是在这两条路之间架一层标准化的桥——大模型只需要认一套协议,剩下的翻译和代理工作,交给网关。


握手:大模型和网关怎么”认识”彼此

JSON-RPC 2.0:MCP 的底层语言

MCP 协议底层就是 JSON-RPC 2.0——一种”动作导向”的轻量级协议,不像 REST 那样纠结 HTTP 动词和路由设计,所有交互都浓缩进一个 JSON 对象,认准四个字段就够:

字段 含义
jsonrpc 固定值 "2.0",标明协议版本
method 要执行什么动作,如 initializetools/call
params 传递给 method 的参数
id 请求唯一标识,用于异步场景下配对请求与响应

为什么 id 字段必须有

id 这个字段的设计不是偶然的。MCP 底层走的是 SSE 长连接,彻底打破了 HTTP 那种按顺序一问一答的模式——大模型可以连续无阻塞地发出请求1、请求2,但网关处理不同后端接口的耗时不一样,完全可能先推回响应2、再推响应1。客户端就是靠这个 id 字段,在乱序的异步流里把请求和响应精准对上号。

握手流程:initialize → Session-Id → initialized

第一步,大模型客户端发一个 initialize 请求:

1
2
3
curl -i -X POST http://10.1.9.21:11999/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{...},"id":1}'

网关返回响应,头部会带一个 Mcp-Session-Id,后面所有请求都要带着这个凭证:

1
Mcp-Session-Id: ajnoOA90SgAAAAAA

响应包体里还有个 capabilities 字段,宣告网关支持哪些能力:

1
2
3
4
5
6
7
{
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
}
}

握手拿到 Session-Id 后,这个会话其实只是”半激活“状态——只有等大模型发完 notifications/initialized 这条确认通知,会话才正式生效,在这之前 tools 类的业务请求会被网关直接拒绝。之后如果长时间没有请求,网关也会按超时时间把会话淘汰掉,回收资源。

版本号为什么用日期

MCP 协议的版本号不是常见的 x.y.z,而是用日期,比如 2024-11-052025-03-26。设计本意是协议两端能快速、去中心化地演进,用日期做时间线交集比对,省得在次要版本号定义权上扯皮。

但现实是:从 2024 年 11 月发布到现在,一年多总共才出了 5 个正式版本,节奏并不算快。所以现网里大量中间件和网关,包括本文抓包用的矩尺 AI 网关,停留在更早的版本上——这也是为什么理解握手这套”旧”机制,现在依然有实际价值。


工具调用:一句 JSON 是怎么变成 REST 请求的

握手完成后,进入工具调用流程,这才是 MCP 协议的核心价值所在。

第一步:tools/list 发现工具

大模型先要知道网关背后挂了哪些工具,发起 tools/list 查询:

1
{"jsonrpc":"2.0","method":"tools/list","id":2}

如果后端接口太多,网关会分页返回,避免一次性把大模型的上下文撑爆:

1
2
3
4
{
"tools": [...],
"nextCursor": "page2_token"
}

第二步:tools/call 发起调用

假设列表里有个叫 list_vs 的工具,能查虚拟服务信息。大模型基于用户的自然语言意图,推理出要调用这个工具,传入参数 name: mcp

1
2
3
4
5
6
7
8
9
10
11
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_vs",
"arguments": {
"name": "mcp"
}
},
"id": 3
}

第三步:网关翻译成 REST 请求

这条 JSON-RPC 请求到了网关这里,会被翻译成一个真正的 REST 请求——网关把 arguments 里的参数提取出来,重组成 URL 查询参数:

1
GET /slb/virtual_service/?name=mcp HTTP/1.1

后端服务处理完,返回标准的 200 和 JSON 结果,网关再把它包装回 JSON-RPC 的 result 字段,通过同一个会话推回给大模型。

一句自然语言,就这样变成了一次精确的 REST 调用,再原路带着结果回来。


谁来维护工具清单:Swagger → MCP 的自动转换

后端接口天天在变,如果这份工具列表要靠人工维护,运维成本高,还容易滞后出错——接口一改参数,大模型感知不到,调用就会出错。

网关的做法是直接读后端现成的 Swagger 文档,自动生成映射

tools/list 的数据结构

工具列表返回的结构很规整——一个 tools 数组,加一个分页用的 nextCursor。每个工具描述包含以下字段:

字段 用途
name 工具名,供大模型调用时引用
description / title 给大模型读的提示词,说明这个工具做什么
inputSchema 遵循 JSON Schema 规范,定义工具能接受什么参数
outputSchema 定义工具返回哪些字段,方便大模型预判后续处理

Swagger parameters → inputSchema

inputSchema 的源头是 Swagger 文档里的 parameters 数组。比如 list_vs 工具,在 Swagger 里对应的定义是:GET 方法、/slb/virtual_service/ 路径,参数里声明了一个叫 name 的查询参数。

网关要做的,是把这种扁平的参数列表,重新组装成 JSON Schema 要求的带 properties 的树状结构,同时把参数类型也对齐转换过去:

1
2
3
4
5
6
7
8
9
10
// Swagger parameters(扁平)
[{"name": "name", "in": "query", "type": "string"}]

// inputSchema(树状 JSON Schema)
{
"type": "object",
"properties": {
"name": {"type": "string"}
}
}

命名细节:中划线 → 下划线

顺带提一句命名细节——Swagger 里 operationIdlist-vs,带中划线;网关会把它转成 list_vs,下划线,这是照顾大模型的命名偏好做的转换。

Swagger responses → outputSchema

Swagger 用 responses 字段,按不同 HTTP 状态码分别声明返回格式;网关会取出 200 成功状态码对应的结构,逆向重组成 outputSchema,让大模型不只知道怎么调用,还能提前预判返回结果里有哪些字段,方便规划后续处理逻辑。

$ref 展开:防止大模型推理幻觉

复杂后端服务里,很多参数和返回结构会被多个接口反复复用,统一定义在 Swagger 的 definitions 里,用 $ref 指针互相引用,有的结构内部还嵌套引用着另一个结构。

为了不让大模型在理解深层嵌套关系时产生推理幻觉,网关通常要把这些分散的引用,在转换时完全展开铺平,直接写进最终的工具描述里。

这也导致了一个直观的现象——网关转换后生成的工具列表,体积往往比原始的 Swagger 文档大得多,具体膨胀多少,取决于网关配置的引用展开层数。


总结

回到开头那组数字——92.31% 到 100%,靠的就是这一整条链路:

1
2
3
4
握手建立信任
→ Mcp-Session-Id 追踪身份
→ JSON-RPC ↔ REST 互相翻译
→ Swagger 自动维护契约,无需人工同步

这也是这一年 Agent 能大规模接入真实业务系统的底层原因之一。

就在 9 天前,2026 年 7 月 28 号,MCP 发布了协议诞生以来最大的一次修订——握手和 Session-Id 被正式砍掉,改成了无状态架构。讽刺的是,这才是”用日期做版本号是为了快速迭代”这句话,第一次真正兑现。

MCP 协议里的另外两大能力——resources 资源读取、prompts 提示词模板,这次没展开讲,下期接着聊。

关于本期内容的讨论 前往 B 站评论区