封面图
背景
大模型应用需要面对训练数据或是模型本身的局限性。
一个直观的例子是:用户想要应用根据最近的天气预报来去制定旅行计划。但天气属于实时数据,通常不包含在模型的训练数据中。此时,大模型本身无法给出可靠答案,应用需要依赖某个天气预报服务来完成任务。
另外一个更典型的例子是计数问题。大模型本身不擅长计数,这是由于其工作机制决定的。模型在底层会将文本拆分为词元(token)进行概率预测,而不是逐字符处理,因此在涉及字符频率、精确数量等问题时,输出往往不稳定:
但与此同时,大模型很擅长生成解决计数问题的代码:
在这种情况下,应用只需要调用本地的 Python 解释器执行这段代码,就可以得到准确结果。如今市面上的大多数 AI 对话产品,在处理复杂计算、数据分析等问题时,都会在幕后调用计算器、代码解释器或其他工具。大模型本身只负责理解问题并给出“该做什么”,而不真正参与计算过程。
按照传统方式开发大模型应用,开发者需要为每一个工具(远端服务或是本地能力)单独编写一套交互逻辑,包括但不限于:接口协议、身份认证、参数校验、错误处理以及权限控制。这种模式在系统规模较小时尚可接受,但随着工具数量增加,其缺陷会迅速放大——
- 新增一个工具,就要改一轮应用代码
- 工具接口一旦变更,应用需要同步调整
- 不同应用之间几乎无法复用同一套集成逻辑
这种高度耦合、缺乏标准化的方式,并不具备良好的拓展性,也逐渐成为限制大模型应用进一步演进的瓶颈。
正是在这样的背景下,Model Context Protocol(MCP)被提出,用来系统性地解决“大模型应用如何与外部系统沟通”的问题。
MCP 概述
MCP 遵循客户端–服务端架构,其核心目标是为大模型使用外部工具提供一套统一、标准化的交互框架。在这一架构中,大模型应用并不直接与具体工具或服务交互,而是通过 MCP 这一协议层完成能力发现与调用。
MCP 客户端(Client)
MCP 客户端通常嵌入在大模型应用内部,负责将模型产生的工具调用意图转化为符合协议规范的请求。同时,它会接收并解析来自 MCP 服务端的响应结果,再将结果注入回模型上下文或应用逻辑中。MCP 服务端(Server)
MCP 服务端通常由具体工具或能力的提供方实现,用于对外暴露其可用能力。这些能力以标准化的形式描述,包括工具列表、调用参数、返回结构以及必要的说明信息,而不关心客户端或模型的具体实现。
客户端与服务端之间通过标准化的 JSON-RPC 消息格式进行交互。这种设计使协议本身不依赖于具体的底层传输方式,既可以运行在 HTTP、TCP、WebSocket 之上,也可以通过本地 pipe 等方式进行通信。
实例搭建
我们可以用 Cloudflare 提供的 MCP 服务模板迅速搭建一个服务实例。打开终端,执行以下命令:
1 | npm create cloudflare@latest -- my-mcp-server --template=cloudflare/ai/demos/remote-mcp-authless |
程序成功启动后,会在终端中输出服务监听的端口号:
接下来启动 MCP Inspector。该工具可以作为一个临时的 MCP 客户端,用于测试和调试 MCP 服务端提供的能力。打开一个新的终端窗口并执行:
1 | npx @modelcontextprotocol/inspector@latest |
启动成功后,会进入 MCP Inspector 的控制台界面。此时需要在左侧配置连接参数,将端口号替换为前面 MCP 服务启动时打印的端口号即可完成连接:
至此,实例搭建完成。
流量分析
我们可以使用 Chrome 浏览器的开发者工具(F12,切换到 Network 标签页)对 MCP 的通信过程进行流量分析。过滤条件选择 Fetch/XHR,以便只关注由 JavaScript 发起的网络请求。
在当前示例中,MCP 采用的是 HTTP 请求 + SSE(Server-Sent Events)推流 的传输模式。因此,在任何 MCP 消息发送之前,客户端会首先与服务端建立一条 SSE 长连接:
1 | >> |
该请求的响应不会立即结束,而是保持打开状态,用于后续持续接收服务端推送的事件。
查看 SSE 响应流,可以看到服务端向客户端推送的第一条事件是 endpoint:
1 | event: endpoint |
这是一条传输层控制事件,用于告知客户端后续上行请求应当访问的路径,并同时分配本次会话所使用的 sessionId。需要注意的是,这一过程并不属于 MCP 协议本身,而是 MCP over SSE 的实现约定。
在此之后,客户端发送的所有 MCP 请求,其基本形式均为:
1 | POST /sse/message?sessionId=d281d68617db3d18923a008d7a19737a3ee81f2ce2b2cbf426861b30db4cd6d3 |
在请求被成功接收的情况下,服务端会立即返回:
1 | 202 Accepted |
该响应仅表示请求已被接收并进入处理流程,而不包含任何业务结果。当服务端完成实际处理后,会将结果通过之前建立的 SSE 通道推送给客户端:
1 | event: message |
这种“请求与响应分离”的通信模式与传统 HTTP 接口有明显不同,但它带来的好处也很直观:客户端可以在同一时间并发发起多个工具调用请求,而无需等待某个请求返回后才能继续发送下一个。
MCP 握手
在 MCP 会话建立阶段,客户端与服务端会完成一次明确的初始化流程。该流程由两条 JSON-RPC 消息构成,通常被称为“两阶段握手”。
第一阶段,客户端发送 initialize 请求:
1 | { |
该请求主要完成三项工作:
- MCP 协议版本声明:通过
protocolVersion指明客户端期望使用的协议版本 - 客户端能力声明:通过
capabilities告知服务端客户端能够理解和处理的协议特性 - 客户端身份信息:通过
clientInfo提供用于调试和日志的客户端信息
服务端在收到该请求后,会通过 SSE 推送一条 message 事件作为响应:
1 | event: message |
该响应同样完成了三件事:
- 确定最终使用的协议版本:若双方版本不同,以服务端返回的版本为准
- 服务端能力声明:
tools.listChanged表示工具列表在会话期间可能发生变化 - 服务端身份信息:用于标识当前 MCP 服务
在客户端成功处理完上述响应后,会发送第二阶段的通知:
1 | { |
该消息用于显式告知服务端:客户端已完成初始化,可以进入正常通信阶段。该通知不要求服务端返回任何内容。
工具发现
在 MCP Inspector 中点击 List Tools 后,客户端会发起如下请求以获取可用工具列表:
1 | { |
服务端随后通过 SSE 推送工具清单:
1 | { |
其中 result.tools 描述了每个工具的名称及其参数结构规范(JSON Schema)。客户端或模型会依据这些信息决定调用哪个工具,并生成合法的调用参数。
工具调用
在控制台中选择 add 工具,填写参数并点击运行后,客户端会发送如下请求:
1 | { |
params 字段中包含了工具调用所需的全部信息,包括工具名称和调用参数。服务端在完成计算后,会将结果通过 SSE 推送回客户端:
1 | { |
需要注意的是,MCP 并不强制规定工具返回结果的结构。上述 content 形式是一种常见的实现约定,用于表示可直接展示给用户或模型的文本内容。MCP Inspector 会对该结构进行 UI 层解包,仅展示其中的文本部分。
结语
MCP 的价值并不在于引入了新的能力,而在于把大模型与外部能力之间的交互方式标准化。通过清晰的分层设计,模型只需要表达“要做什么”,而具体的执行、传输和会话管理都被放在了协议和实现层中完成。