MCP.so
登录
返回博客

MCP 是如何工作的?逐步拆解协议流程(2026)

2026年10月15日MCP.so TeamMCP.so Team

MCP 是如何工作的?逐步拆解 2026-07-28 版协议:discover、tools/list、tools/call、elicitation、通知、传输与鉴权,附真实 JSON 报文。

MCP 的工作方式是:AI 应用(Host)为每个连接的服务器创建一个客户端,由客户端与服务器交换 JSON-RPC 2.0 消息——列出服务器的工具,让模型挑选一个,再发送 tools/call,把结果交回给模型。从当前规范版本 2026-07-28 起,MCP 是无状态的:没有握手,每个请求都在 _meta 中携带自己的协议版本和客户端能力,因此任何一个服务器实例都能处理任何一个请求。

本文跟随一次工具调用,逐条消息看它在线路上发生了什么。如果你想先了解全貌,请阅读 什么是 MCP?;服务器那一侧的介绍见 什么是 MCP 服务器?。

三个角色:Host、Client、Server

  • Host:用户直接使用的应用——Claude Code、Cursor、VS Code、ChatGPT。它掌管对话、模型和授权确认弹窗。
  • Client:Host 为某一个服务器创建的连接器。根据 架构规范,每个客户端只与一个服务器通信;接了五个服务器的 Host 就运行五个客户端。(详见我们的 MCP 客户端指南。)
  • Server:暴露工具、资源和提示词的程序——可以是本地进程,比如 Filesystem 服务器,也可以是远程服务,比如托管的 Sentry MCP 服务器。

规范中写明了一条关键设计原则:服务器看不到完整对话,也不能"窥视"其他服务器。每个服务器能收到什么,由 Host 决定。

第 0 步:建立传输通道

在任何 MCP 消息流动之前,客户端需要一条通道。规范定义了 两种标准传输方式:

stdioStreamable HTTP
服务器运行在哪里客户端启动的子进程任意位置,通过一个 URL 访问,如 https://example.com/mcp
消息分帧stdin/stdout 上按换行分隔的 JSON-RPC每条消息一个 HTTP POST;回复为 JSON 或仅属于该请求的 SSE 流
凭证来自环境变量OAuth 2.1 Bearer Token(见第 7 步)
取消请求发送 notifications/cancelled关闭该请求的响应流

旧的 HTTP+SSE 传输(2024-11-05)已被正式标记为弃用——迁移方法见 SSE 与 Streamable HTTP。

第 1 步(可选):server/discover

客户端可以先问服务器支持什么。服务器必须实现 server/discover,但客户端不一定要调用。下面这组来自官方 架构指南:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "server/discover",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "example-client",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": { "elicitation": {} }
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"],
    "capabilities": { "tools": { "listChanged": true }, "resources": {} },
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "example-server",
        "version": "1.0.0"
      }
    },
    "ttlMs": 3600000,
    "cacheScope": "public"
  }
}

这个 _meta 块就是无状态设计的核心。每个请求都包含:

  • io.modelcontextprotocol/protocolVersion(必填)——本次请求使用的协议版本。
  • io.modelcontextprotocol/clientCapabilities(必填)——客户端在本次请求中能做什么,例如 elicitation。
  • io.modelcontextprotocol/clientInfo(建议填写)——客户端名称和版本。

如果服务器不支持请求的版本,会返回 UnsupportedProtocolVersionError(错误码 -32022)并附上 supported 列表,客户端从中选一个版本重试。缺少必填字段的请求会收到 -32602(Invalid params)。

第 2 步:列出工具

接着客户端请求服务器的工具清单:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": { "_meta": { "...": "same three fields as above" } }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "complete",
    "tools": [
      {
        "name": "weather_current",
        "title": "Weather Information",
        "description": "Get current weather information for any location worldwide",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "City name, address, or coordinates"
            },
            "units": {
              "type": "string",
              "enum": ["metric", "imperial", "kelvin"],
              "default": "metric"
            }
          },
          "required": ["location"]
        }
      }
    ],
    "ttlMs": 300000,
    "cacheScope": "public"
  }
}

2026-07-28 版本带来了三点变化:

  • 列表结果现在必须带 ttlMs 和 cacheScope——这里表示客户端可以缓存这份列表五分钟。
  • 列表不得随连接而变化。它可以随调用者的授权而变化(例如按 OAuth scope 过滤工具)。
  • 服务器应以确定的顺序返回工具,这样模型的 prompt 缓存更容易命中。

大型列表用 cursor 分页。聚合了很多服务器的 Host 往往会按需加载工具——见 MCP 工具发现。

第 3 步:模型选择工具

这一步其实不属于 MCP。Host 把每个工具的 name、description 和 inputSchema 转换成自家模型的 function calling 格式,连同用户消息一起发给模型。当模型返回一个工具调用(weather_current,参数 location: "San Francisco")时,Host 应该先让用户看到即将执行什么——规范规定应当始终有一个能拒绝工具调用的人在环中。

第 4 步:tools/call

客户端发出调用。在 Streamable HTTP 上,传输层还会把关键字段镜像到请求头里,方便网关在不解析 JSON 的情况下完成路由:Mcp-Method 在所有请求上都是必需的,Mcp-Name 在 tools/call、resources/read 和 prompts/get 上必需。下面是 Streamable HTTP 规范 中的示例:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "location": "Seattle, WA" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

如果 MCP-Protocol-Version 请求头与 body 中 _meta 的值不一致,服务器会以 400 和 HeaderMismatch 错误(-32020)拒绝。成功的回复如下:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "text",
        "text": "Current weather in Seattle, WA: 54°F, light rain..."
      }
    ]
  }
}

现在每个结果都带有 resultType。"complete" 表示这就是最终答案。工具在业务层面失败时仍然返回一个结果,只是带 isError: true,这样模型可以读取错误信息并换个办法再试;只有协议层面的失败(例如工具不存在)才会以 JSON-RPC 错误返回。Host 把 content(如果工具声明了 outputSchema,还有 structuredContent)放进对话,模型据此写出回答。

第 5 步:服务器需要更多输入时(MRTR)

有时工具离不开用户才能完成——确认一笔购买、选择一个账号。在 2026-07-28 之前,服务器会反过来给客户端发一个 JSON-RPC 请求。现在服务器从不主动发起请求,而是使用 多轮往返请求(MRTR):

  1. 客户端发送 tools/call(id 1)。
  2. 服务器回复 resultType: "input_required",其中带有 inputRequests(比如一个 elicitation/create 表单)和一个不透明的 requestState。
  3. 客户端把表单展示给用户。
  4. 客户端以新请求(id 2)重试原来的调用,传入 params.inputResponses(用户的回答,使用相同的 key),并原样回传 params.requestState。
  5. 服务器根据这次重试恢复状态,返回 resultType: "complete"。
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "github_login": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Please provide your GitHub username",
          "requestedSchema": {
            "type": "object",
            "properties": { "name": { "type": "string" } },
            "required": ["name"]
          }
        }
      }
    },
    "requestState": "AEAD-protected blob"
  }
}

由于两次调用之间服务器不保存任何状态,重试落到负载均衡后面的哪个实例都没关系。规范要求服务器把 requestState 视为攻击者可控的输入,并保护其完整性(HMAC 或 AEAD)。Elicitation 有两种模式:form 用于普通数据,URL 用于任何敏感信息(如凭证),这类信息绝不能经过客户端。

另外两项客户端功能——sampling 和 roots——同样可以借助 MRTR 传递,但它们(连同协议日志功能)在 2026-07-28 中已被弃用。新服务器应直接调用 LLM API,并通过工具参数接收路径。

第 6 步:通知

MCP 有两类通知,走的是不同的路径:

  • **请求级通知。**在请求的 _meta 中加上 progressToken,服务器就可以在最终结果之前,在该请求自己的响应流上推送 notifications/progress(progress、total、message)。
  • **变更通知。**要获知工具、提示词或资源的变化,客户端需要发起一个长连接的 subscriptions/listen 请求,并选择要订阅的类型。它取代了 resources/subscribe 和旧的 HTTP GET 流。
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "subscriptions/listen",
  "params": {
    "_meta": { "...": "standard fields" },
    "notifications": {
      "toolsListChanged": true,
      "resourceSubscriptions": ["file:///project/config.json"]
    }
  }
}

服务器首先发送 notifications/subscriptions/acknowledged,列出它同意推送的内容;之后 notifications/tools/list_changed 或 notifications/resources/updated 陆续到达,每条都带有 io.modelcontextprotocol/subscriptionId 标记。收到 list_changed 后,客户端重新调用 tools/list。

要取消一个请求:stdio 客户端发送带请求 ID 的 notifications/cancelled;Streamable HTTP 客户端直接关闭该请求的流。实现方应为每个请求设置超时。

第 7 步:鉴权

鉴权位于传输层,在 JSON-RPC 消息之外。授权规范 规定:

  • stdio 服务器不应使用 OAuth 流程,而是从环境中读取凭证(通常是客户端配置里的 env 块)。
  • HTTP 服务器作为 OAuth 2.1 资源服务器。未认证的请求会收到 401,其 WWW-Authenticate 头指向服务器的受保护资源元数据。客户端由此发现授权服务器、完成注册(现在优先使用 Client ID Metadata Documents,Dynamic Client Registration 已弃用)、执行 PKCE,之后在每个请求上携带 Authorization: Bearer。

完整流程及可运行示例见我们的 MCP 鉴权指南。

那 initialize 呢?

网上大多数讲解——以及今天仍在运行的许多服务器——描述的都是 2025-11-25 及更早版本的旧版流程:客户端先发 initialize 请求(带 protocolVersion、capabilities、clientInfo),服务器回复自己的能力,客户端再发 notifications/initialized;此后连接就是一个会话(在 HTTP 上,服务器下发 Mcp-Session-Id 请求头,后续请求必须回传)。2026-07-28 版本移除了握手、会话 ID、ping 以及流的断点续传。

两个时代通过 版本规范 中的回退规则实现互通。在 stdio 上,同时支持两代的客户端先用 server/discover 探测,若收到的不是新版响应就回退到 initialize;在 HTTP 上,它先发送新版请求,如果收到的 400 不含新版错误体,就回退。

SDK 会帮你处理这些,但要留意默认值:在 TypeScript SDK v2 中,new Client(...) 默认仍执行旧版握手,除非你传入 versionNegotiation: { mode: 'auto' }——这样它会用 server/discover 探测,并自动回退。服务端的 serveStdio 辅助函数则两代都能应答(协议版本指南)。

亲眼看看

读 JSON 是一回事,实时观察更直观。用 MCP Inspector 连接任意服务器(例如 npx @modelcontextprotocol/inspector 搭配 GitHub MCP 服务器),每一次 tools/list 和 tools/call 交换都能看得清清楚楚。想自己写一个服务器,可以从 构建 MCP 服务器 开始。

下一步

现在你已经了解完整的往返流程:discover、list、call、input-required 重试、通知、鉴权。想看它实际运行,可以浏览 MCP 服务器目录,把一个服务器接入你的客户端。

常见问题

用简单的话说,MCP 是怎么工作的?

AI 应用为每个服务器运行一个 MCP 客户端。客户端询问服务器有哪些工具,模型选择其中一个,客户端发送 tools/call 请求,再把结果交回模型。所有消息都使用 JSON-RPC 2.0。

MCP 还有 initialize 握手吗?

当前的 2026-07-28 版本已经没有了。每个请求都在 _meta 中携带协议版本和客户端能力,客户端也可以先调用 server/discover。2025-11-25 及更早版本的服务器仍使用 initialize,所以 SDK 会在需要时回退。

MCP 使用什么传输方式?

有两种标准传输。本地服务器使用 stdio,即在子进程的 stdin 和 stdout 上传输按换行分隔的 JSON-RPC。远程服务器使用 Streamable HTTP:每条消息一个 POST,回复为 JSON 或仅属于该请求的 SSE 流。旧的 HTTP+SSE 传输已弃用。

MCP 服务器如何向用户索取输入?

通过 elicitation,以多轮往返请求(MRTR)的方式传递。服务器对 tools/call 返回一个 input_required 结果,说明它需要什么。客户端收集回答后,带上 inputResponses 和原样的 requestState 重试原请求。