Agent 工程体系 · 第 32/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。

MCP 架构深解:Host、Client、Server、能力协商和生命周期

MCP(Model Context Protocol)是一个用于连接 LLM 应用与外部数据源、工具和工作流的开放协议。它并不是“让模型直接访问 API”的函数调用格式,而是一套规定了组件边界、消息模型、能力声明、传输绑定、请求生命周期和安全责任的互操作协议。

在 MCP 中,最容易混淆的不是 tools/listtools/call 的字段,而是下面几个问题:

  • Host 和 Client 到底是不是同一个东西?
  • 一个 Client 能否连接多个 Server?
  • Server 是否能看到完整对话?
  • “能力协商”是一次握手,还是每次请求都要携带?
  • MCP 的“会话”是 TCP、HTTP、进程,还是业务上下文?
  • initialize 还是否存在?
  • 一个工具调用失败时,应该返回 JSON-RPC error,还是返回 isError: true
  • Server 能否主动调用 Client?
  • Streamable HTTP 是否仍然需要粘性会话?

这些问题必须放在具体协议版本中回答。本文以 MCP 2026-07-28 规范为现代基线,同时解释截至 2025-11-25 及更早版本的兼容生命周期。2026-07-28 版本已经移除了协议层面的 initialize / initialized 握手和 Mcp-Session-Id,因此把旧版本的会话模型直接套到新版本上,会得到错误的架构判断。(modelcontextprotocol.io)


1. 先建立正确的架构模型

MCP 的核心关系可以表示为:

graph LR
    U[用户] --> H[Host]
    H --> L[LLM / Agent Runtime]

    H --> C1[Client 1]
    H --> C2[Client 2]
    H --> C3[Client 3]

    C1 <--> S1[Server A]
    C2 <--> S2[Server B]
    C3 <--> S3[Server C]

    S1 --> E1[数据库 / 文件系统 / API]
    S2 --> E2[代码执行环境]
    S3 --> E3[企业业务系统]

其中:

  • Host:承载 LLM 应用并负责协调的宿主进程。
  • Client:Host 内部创建的 MCP 协议连接器。
  • Server:对外暴露资源、提示模板和工具的 MCP 服务端。
  • Transport:承载 JSON-RPC 消息的传输绑定,例如 stdio 或 Streamable HTTP。
  • Capability:一方声明自己支持哪些协议功能。
  • Primitive:MCP 中可被发现或调用的功能类型,例如 Tools、Resources 和 Prompts。

规范规定,一个 Host 可以管理多个 Client;每个 Client 与恰好一个 Server通信。多个 Server 的组合由 Host 完成,而不是由 Server 之间直接互相发现或调用。(modelcontextprotocol.io)

因此,下面两种说法都不准确:

“MCP Client 就是整个聊天应用。”

更准确的说法是:

聊天应用或 Agent Runtime 通常是 Host;其中每个指向一个 MCP Server 的协议连接器,才是一个 Client。

同样,下面这种结构也不是 MCP 的基本架构:

一个 Client -> 多个 Server

如果一个 Host 需要连接三个 Server,通常应当创建三个 Client:

Host
├── Client A -> Server A
├── Client B -> Server B
└── Client C -> Server C

这样设计的主要价值是隔离:

  1. 每个 Server 只接触发给它的请求。
  2. 一个 Server 不会自动看到另一个 Server 的上下文。
  3. 完整对话历史和跨 Server 编排逻辑保留在 Host。
  4. Host 可以为不同 Server 配置不同权限、凭据和用户确认策略。

2. Host:不是转发器,而是安全与语义边界

2.1 Host 的定义

Host 是启动或承载 AI 应用的容器和协调者。它通常负责:

  • 创建、配置和销毁多个 Client;
  • 决定哪些 Client 可以连接哪些 Server;
  • 聚合来自多个 Server 的工具、资源和提示;
  • 将可用能力转换为模型能够理解的上下文;
  • 处理用户授权、确认和拒绝;
  • 将模型的工具选择转换为具体的 tools/call 请求;
  • 管理跨工具、跨 Server 的 Agent 流程;
  • 处理模型采样和用户输入请求;
  • 隔离不同 Server 之间的数据和权限。

MCP 规范明确把安全策略、用户授权、AI/LLM 集成和上下文聚合放在 Host 一侧。Server 的职责则应尽量聚焦于自身提供的能力。(modelcontextprotocol.io)

2.2 Host 为什么必须存在

假设一个 Agent 同时连接:

  • 文件系统 Server;
  • GitHub Server;
  • 数据库 Server。

用户说:

“检查项目中的依赖漏洞,并创建一个 GitHub Issue。”

这个请求至少涉及:

  1. 文件系统读取;
  2. 依赖分析;
  3. GitHub API 写入;
  4. 可能的用户确认;
  5. 结果汇总。

如果让 Server 直接互相调用,会产生几个问题:

  • GitHub Server 是否能读取文件系统 Server 的数据?
  • 数据库 Server 是否能访问完整对话?
  • 哪个组件决定“创建 Issue”是否需要用户批准?
  • 不同 Server 的权限是否会被意外拼接?
  • 失败后由谁负责重试和补偿?

Host 的存在把这些问题集中到一个可审计的位置:

sequenceDiagram
    participant U as 用户
    participant H as Host
    participant L as LLM
    participant F as 文件 Server
    participant G as GitHub Server

    U->>H: 检查漏洞并创建 Issue
    H->>L: 提供任务、工具目录和上下文
    L->>H: 选择文件分析工具
    H->>F: tools/call
    F-->>H: 漏洞分析结果
    H->>L: 注入分析结果
    L->>H: 选择 GitHub 创建工具
    H->>U: 请求确认写入操作
    U-->>H: 同意
    H->>G: tools/call
    G-->>H: Issue 创建结果
    H-->>U: 汇总结果

这里的关键不是“Host 帮模型转发 JSON”,而是:

Host 决定哪些信息可以跨边界流动,以及哪些具有副作用的操作必须经过用户授权。

规范要求 Host 在暴露用户数据和调用工具前建立明确的同意与控制流程;工具应被视为可能执行任意代码的能力,工具描述和注解本身也不能在不可信来源下直接当作安全事实。(modelcontextprotocol.io)


3. Client:一条面向单个 Server 的协议连接

3.1 Client 的定义

Client 是由 Host 创建、负责与一个 Server 通信的连接器。它通常实现:

  • MCP 消息编码和解码;
  • 请求 ID 分配与响应匹配;
  • 传输建立和关闭;
  • Server 能力发现;
  • 工具、资源、提示的列表获取;
  • 通知接收;
  • 取消、超时和重连;
  • 请求级元数据注入;
  • Server 返回结果的解析。

MCP Client 不是模型本身,也不是工具实现。它位于 Host 和 Server 之间:

LLM / Agent Runtime
        |
        | Host 内部调用
        v
MCP Client
        |
        | JSON-RPC over transport
        v
MCP Server

一个 Client 与一个 Server 的一对一关系,解决了两个隔离问题:

  • 身份隔离:不同 Server 可以使用不同凭据和授权范围。
  • 上下文隔离:Client 不应把一个 Server 的全部输入自动发送给另一个 Server。

3.2 Client 不等于连接生命周期

Client 对象可以比底层连接活得更久,也可以在一次连接断开后重新建立传输。反过来,一个长时间存在的 stdio 进程也不等于一个对话会话。

需要区分四种生命周期:

层次 例子 是否等价
进程生命周期 一个被 Host 启动的 Server 子进程 不等于会话
传输生命周期 一条 stdio 管道或 HTTP 请求流 不等于对话
协议请求生命周期 一个 tools/call 请求 通常独立
业务状态生命周期 一个任务 ID、游标、文件锁 由应用显式管理

现代 MCP 规范要求请求自描述,Server 不应依赖此前请求来推断协议版本、Client 身份或能力。需要跨多个请求保存的状态,必须通过显式标识符传递。(modelcontextprotocol.io)

因此,下列实现是错误的:

# 错误思路:假定上一次请求设置过用户
server.current_user = user_from_previous_request

# 下一次请求直接使用
def handle_tool_call(arguments):
    return operate_as(server.current_user, arguments)

正确做法是把业务状态显式编码到请求参数中:

{
  "name": "continue_task",
  "arguments": {
    "taskId": "task_123",
    "page": 2
  }
}

这样,即使请求被负载均衡到另一台 Server 实例,也能根据 taskId 恢复业务状态。


4. Server:能力提供者,而不是 Agent 编排器

4.1 Server 的定义

Server 是向 Client 提供上下文和能力的服务端。它可以暴露:

  • Tools:模型或 Host 可以调用的函数;
  • Resources:可读取的上下文和数据;
  • Prompts:提示模板或工作流入口;
  • 以及规范或扩展定义的其他能力。

Server 可以是:

  • Host 启动的本地子进程;
  • 通过网络访问的远程服务;
  • 企业内部网关后的服务;
  • 由 SDK 封装的业务模块。

MCP 只规范 Server 与 Client 之间的协议,不规定 Server 内部必须使用什么语言、数据库或框架。官方 SDK 当前提供 TypeScript、Python、C#、Go、Rust 等 Tier 1 SDK,以及 Java、Ruby、Swift、PHP、Kotlin 等其他 SDK 层级。不同 SDK 遵循各自语言习惯,但都围绕 Server、Client 和标准传输提供实现。(modelcontextprotocol.io)

4.2 Server 不应该知道什么

一个合规的 Server 通常不应默认知道:

  • 用户的完整聊天记录;
  • Host 连接的其他 Server;
  • 模型使用了哪些内部思维或中间状态;
  • 用户没有授权的数据;
  • 与当前请求无关的其他任务上下文。

这不是“Server 永远不能接收这些数据”,而是:

Server 只能接收 Host 明确放进当前请求的数据。

例如,Host 可以向工具传入:

{
  "name": "search_code",
  "arguments": {
    "repository": "org/project",
    "query": "timeout",
    "scope": "src/"
  }
}

但不应因为调用了 search_code,就把完整聊天记录、其他 Server 的返回值和所有用户文件一起发送过去。


5. JSON-RPC:MCP 的消息基础

MCP 消息使用 JSON-RPC 2.0 编码。规范要求 Client 和 Server 之间的消息遵循 JSON-RPC 结构。核心消息类型有三种:

  1. Request:需要响应的请求;
  2. Response:成功结果或错误;
  3. Notification:不需要响应的单向通知。(modelcontextprotocol.io)

5.1 Request

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "Hangzhou"
    }
  }
}

请求 ID 必须满足两个条件:

  • 必须是字符串或整数;
  • 不能与尚未收到响应的其他请求 ID 重复。

因此,Client 可以并发发出多个请求:

id=41 -> tools/list
id=42 -> tools/call
id=43 -> resources/read

只要响应携带相同 ID,Client 就能正确匹配:

response id=43 -> resources/read
response id=41 -> tools/list
response id=42 -> tools/call

响应顺序不必等于请求顺序。

5.2 Result Response

成功响应具有:

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "text",
        "text": "杭州当前天气晴朗"
      }
    ],
    "isError": false
  }
}

在 2026-07-28 规范中,resultType 用于区分结果形态:

  • complete:请求已经完成;
  • input_required:请求尚未完成,需要 Client 提供额外输入;
  • 扩展可以增加其他结果类型,但必须通过能力声明进行支持协商。

旧版本没有 resultType 时,兼容 Client 应将缺失值按 "complete" 处理。(modelcontextprotocol.io)

5.3 JSON-RPC Error 与工具业务错误

这是生产实现中最常见的错误分类混淆。

协议或请求层错误

例如:

  • JSON 格式损坏;
  • method 不存在;
  • 参数结构不合法;
  • 不支持的协议版本;
  • 请求缺少必要的 Client 能力。

这种情况使用 JSON-RPC error:

{
  "jsonrpc": "2.0",
  "id": 42,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {
      "field": "location",
      "reason": "must be a string"
    }
  }
}

工具执行层错误

工具本身被正确识别、参数也符合 Schema,但外部业务操作失败,例如:

  • GitHub 返回权限不足;
  • 数据库连接超时;
  • 查询结果为空;
  • 删除操作被业务规则拒绝。

这通常仍然是一个合法的工具结果,只是结果中的 isErrortrue

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "resultType": "complete",
    "isError": true,
    "content": [
      {
        "type": "text",
        "text": "GitHub API 返回 403:当前令牌没有创建 Issue 的权限"
      }
    ]
  }
}

判断逻辑可以写成:

JSON-RPC error
    => 请求没有按 MCP 方法完成,通常属于协议、路由或参数层失败

result.isError = true
    => MCP 调用本身完成,但工具业务执行失败

如果把所有业务异常都包装成 JSON-RPC error,Host 很难区分“工具不存在”和“工具执行失败”;如果把参数解析失败都包装成 isError: true,Client 可能误以为工具已经正常运行。


6. 能力协商:不是“发现工具列表”的同义词

6.1 Capability 的含义

Capability 是一方对协议功能支持范围的声明。

例如,Server 可能声明:

{
  "capabilities": {
    "tools": {
      "listChanged": true
    },
    "resources": {
      "subscribe": true
    }
  }
}

这表示:

  • Server 支持工具相关方法;
  • 工具列表发生变化时,可以发送相应通知;
  • Server 支持资源订阅。

Client 也可以声明自己的能力,例如是否支持:

  • sampling;
  • elicitation;
  • roots;
  • 某个可选扩展。

能力声明不是对“业务权限”的完整描述。它只说明协议功能是否存在;是否允许当前用户使用,还要经过授权和 Host 策略。

6.2 能力的有效集合

可以用一个简化模型表示某个功能最终是否可用。

设:

  • SS:Server 已实现的能力;
  • CC:Client 已声明的能力;
  • VV:双方协议版本支持的能力;
  • AA:授权和 Host 策略允许的能力;
  • EE:可选扩展双方都明确启用的能力。

则当前请求的有效能力集合可以表示为:

F=SCVAEF = S \cap C \cap V \cap A \cap E

但不同功能的条件并不完全相同。

例如,Server 提供工具的必要条件主要是:

ToolCallAllowed=ServerHasToolsVersionSupportsToolsHostPolicyAllows\text{ToolCallAllowed} = \text{ServerHasTools} \land \text{VersionSupportsTools} \land \text{HostPolicyAllows}

如果工具调用需要 Server 请求用户补充信息,则还需要:

InputRequiredFlow=ServerNeedsInputClientSupportsRequiredInteractionUserAccepts\text{InputRequiredFlow} = \text{ServerNeedsInput} \land \text{ClientSupportsRequiredInteraction} \land \text{UserAccepts}

这说明:

“Server 宣布了 tools 能力”不等于“任何用户都能调用所有工具”。

6.3 2026-07-28 的请求级能力模型

现代规范要求每个请求携带协议版本和 Client 能力等元数据,典型形式为:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "example-host",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

这里的关键变化是:Server 不能只看连接建立时保存的状态来判断当前请求,而应读取当前请求中的 _meta

规范要求:

  • 必要的请求元数据必须存在;
  • Server 不得依赖 Client 未声明的能力;
  • 如果请求需要某项未声明的 Client 能力,Server 应返回 -32021
  • clientInfoserverInfo 是自报告信息,不能用于安全决策。(modelcontextprotocol.io)

6.4 server/discover 的作用

在现代协议中,客户端可以调用 server/discover,预先获取 Server 能力。

这一步是可选的前置发现,不是旧式 initialize 的简单改名:

Client -> server/discover
Server -> DiscoverResult

Client -> tools/list
Server -> tools

Client -> tools/call
Server -> result

如果 Client 不需要预先获取完整能力,也可以直接发送普通请求,因为每个请求已经携带了自身需要的协议元数据。架构规范将 server/discover 描述为可以在其他请求之前调用的能力发现机制。(modelcontextprotocol.io)


7. 工具发现不是“把函数名发给模型”

工具发现至少包含四个层次:

  1. Server 是否声明了 tools 能力;
  2. Client 是否成功执行 tools/list
  3. 每个工具是否有合法的输入 Schema;
  4. Host 是否愿意把这些工具暴露给模型或用户。

典型工具定义:

{
  "name": "search_code",
  "title": "搜索代码",
  "description": "在指定仓库范围内搜索代码文本",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "minLength": 1
      },
      "scope": {
        "type": "string"
      }
    },
    "required": ["query"],
    "additionalProperties": false
  }
}

inputSchema 是 JSON Schema,不是给模型看的普通文档。它至少承担三项职责:

  • 告诉模型应该生成什么参数;
  • 让 Client 在调用前验证参数;
  • 让 Server 在执行前再次验证参数。

MCP 2026-07-28 默认支持 JSON Schema 2020-12。Server 提供的 Schema 必须合法,Client 和 Server 都应按声明的方言进行验证。对于外部 $ref,实现不得默认通过网络自动解引用,因为这可能引入 SSRF、供应链和拒绝服务风险。(modelcontextprotocol.io)

7.1 为什么需要双重验证

假设模型产生:

{
  "query": 123,
  "scope": "../"
}

Client 侧验证发现 query 不是字符串,可以在网络请求前拒绝。

但即使 Client 验证通过,Server 仍然必须验证,因为:

  • Client 可能存在实现缺陷;
  • 请求可能来自非标准 Client;
  • Host 可能错误地拼装了参数;
  • 攻击者可能直接调用 Server;
  • Schema 只能表达结构,不能表达全部安全约束。

因此安全边界应当是:

模型输出
  -> Host 策略检查
  -> Client Schema 校验
  -> 传输
  -> Server Schema 校验
  -> 业务授权
  -> 工具执行

7.2 tools/list 的确定性与缓存

现代规范允许工具列表携带缓存提示,例如:

{
  "resultType": "complete",
  "tools": [],
  "ttlMs": 300000,
  "cacheScope": "public"
}

Server 在底层工具集合没有变化时,应保持稳定顺序。确定性排序可以让 Client 缓存工具目录,也可以减少工具被注入模型上下文时的无意义变化。工具集合仍可能因请求携带的授权信息不同而不同,因此不能把所有用户看到的工具目录简单视为全局公共缓存。(modelcontextprotocol.io)


8. 两种生命周期:旧式握手与现代无会话请求

MCP 生命周期必须按协议时代区分。

8.1 2025-11-25 及更早版本:连接级握手

旧式生命周期如下:

stateDiagram-v2
    [*] --> Created
    Created --> TransportConnected: 建立 stdio/HTTP
    TransportConnected --> Initializing: initialize
    Initializing --> Initialized: 返回 InitializeResult
    Initialized --> Ready: notifications/initialized
    Ready --> Calling: tools/list 或 tools/call
    Calling --> Ready: result/error
    Ready --> Closing: close 或传输断开
    Closing --> [*]

典型流程:

1. 建立传输
2. Client 发送 initialize
3. Server 返回:
   - protocolVersion
   - capabilities
   - serverInfo
4. Client 发送 notifications/initialized
5. 开始 tools/list、resources/list、tools/call
6. 连接关闭时结束协议会话

旧版本中,协议版本和能力主要在 initialize 阶段协商,并被绑定到该连接或会话。Streamable HTTP 还会通过 Mcp-Session-Id 将后续请求关联到已建立的协议会话。

8.2 2026-07-28:无握手、无协议会话

现代生命周期可以表示为:

stateDiagram-v2
    [*] --> ClientConfigured
    ClientConfigured --> Discovering: 可选 server/discover
    Discovering --> RequestReady: 得到能力
    ClientConfigured --> RequestReady: 直接发请求

    RequestReady --> InFlight: 发送独立请求
    InFlight --> Completed: result
    InFlight --> ProtocolError: JSON-RPC error
    InFlight --> Cancelled: stdio 通知或 HTTP 关闭响应流

    Completed --> RequestReady: 继续其他请求
    ProtocolError --> RequestReady: 按错误策略处理
    Cancelled --> RequestReady: 可重试或终止

现代请求自身携带:

  • 协议版本;
  • Client 能力;
  • 可选 Client 身份;
  • 当前请求的参数;
  • 需要传递的业务状态标识。

因此,协议层不再依赖:

“这个请求来自哪条连接?”
“这个连接之前是否成功 initialize?”
“这个 HTTP 请求是否被路由到同一台机器?”

2026-07-28 版本移除了 initialize / initializedMcp-Session-Id,使得任意请求可以被负载均衡到任意 Server 实例,而不需要共享协议会话存储。(blog.modelcontextprotocol.io)

8.3 “无会话”不等于“业务无状态”

这是现代 MCP 最容易被误读的地方。

无会话的含义是:

协议层不要求通过连接 ID 保存版本、能力和 Client 身份。

它不表示:

  • 数据库没有事务;
  • 工具不能创建任务;
  • Server 不能保存缓存;
  • 文件系统没有锁;
  • 业务不能产生任务句柄。

例如,一个异步导入工具可以返回:

{
  "resultType": "complete",
  "structuredContent": {
    "taskId": "import_789",
    "status": "accepted"
  }
}

后续请求显式携带:

{
  "name": "get_import_status",
  "arguments": {
    "taskId": "import_789"
  }
}

业务状态由 taskId 表示,而不是由 HTTP 连接或 stdio 进程表示。


9. MRTR:现代协议中的多轮请求

旧式 MCP 允许 Server 主动向 Client 发 JSON-RPC 请求,例如 Server 请求 Client 进行 sampling 或 elicitation。这要求传输层具备持续的双向请求能力。

现代 MCP 使用 Multi Round-Trip Requests,MRTR 表示多轮请求。Server 不直接在协议层另起一个独立的 Server-to-Client JSON-RPC 请求,而是在结果中说明:

{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "confirm_scope": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "请选择允许扫描的目录",
          "requestedSchema": {
            "type": "object",
            "properties": {
              "scope": {
                "type": "string"
              }
            },
            "required": ["scope"]
          }
        }
      }
    },
    "requestState": "opaque-server-state"
  }
}

Client 获取用户输入后,用新的请求 ID 重试:

{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "scan_project",
    "arguments": {
      "scope": "src/"
    },
    "inputResponses": {
      "confirm_scope": {
        "action": "accept",
        "content": {
          "scope": "src/"
        }
      }
    },
    "requestState": "opaque-server-state"
  }
}

这里有两个重要约束:

  1. 重试请求必须使用新的 JSON-RPC ID;
  2. requestState 是 Server 生成的业务状态,不应被 Client 修改或解释。

MCP 工具规范将这种模式用于需要额外用户输入的工具调用。(modelcontextprotocol.io)


10. stdio 与 Streamable HTTP:相同语义,不同绑定

Transport 只负责消息如何传输,不改变 MCP 消息本身的语义。当前标准传输主要有:

  • stdio:Client 启动 Server 子进程,通过标准输入输出交换换行分隔的 JSON-RPC 消息;
  • Streamable HTTP:每条消息通过发往 MCP Endpoint 的 HTTP POST 发送,响应可以是 JSON 对象,也可以是请求作用域的 SSE 流。(modelcontextprotocol.io)

10.1 stdio

stdio 的典型拓扑:

Host
  |
  | 创建子进程
  v
MCP Server Process
  stdin  <- Client requests
  stdout -> Server responses/notifications
  stderr -> logs

一个重要边界是:

stdout 只能输出协议消息,日志应输出到 stderr。

否则,普通日志会破坏 JSON-RPC 的消息边界:

这是调试日志
{"jsonrpc":"2.0", ...}

Client 无法判断第一行是否是 MCP 消息。

stdio 的优点:

  • 本地部署简单;
  • 不需要 HTTP 端口;
  • 进程权限容易隔离;
  • 适合桌面应用、IDE 和本地开发工具。

风险是:

  • Server 进程崩溃会直接导致传输断开;
  • 重启后不能假设旧进程状态仍存在;
  • 环境变量、工作目录和继承凭据必须显式控制;
  • 不应把“进程还活着”当成“业务会话还有效”。

按照现代规范,stdio 上的取消通过 notifications/cancelled 通知表达;传输层的其他语义仍由 MCP 核心协议定义。(modelcontextprotocol.io)

10.2 Streamable HTTP

现代 Streamable HTTP 的基本形态是:

Client
  |
  | POST /mcp
  | MCP-Protocol-Version: 2026-07-28
  | Mcp-Method: tools/call
  | Mcp-Name: search_code
  v
Load Balancer / Proxy
  |
  +--> Server Instance A
  +--> Server Instance B
  +--> Server Instance C

规范语义上,消息体中的 _meta 是来源;HTTP 头可以镜像部分请求元数据,用于路由和中间件处理。这样网关可以在不解析完整 JSON Body 的情况下,根据 Mcp-MethodMcp-Name 或工具参数对应的 Mcp-Param-* 头进行路由。(modelcontextprotocol.io)

一个现代请求可以写成:

curl -i https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/call' \
  -H 'Mcp-Name: search_code' \
  --data-binary @- <<'JSON'
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_code",
    "arguments": {
      "query": "timeout",
      "scope": "src/"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "example-host",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}
JSON

前置条件:

  • Endpoint 必须实现 Streamable HTTP;
  • Server 必须提供名为 search_code 的工具;
  • 工具 Schema 必须接受 queryscope
  • HTTP 层需要完成认证和授权;
  • 网关不能修改与请求体冲突的镜像头。

现代 Streamable HTTP 的关键收益是:协议请求不再依赖 Mcp-Session-Id。因此普通负载均衡即可工作,但这并不意味着业务状态可以随意放在单机内存中。只要业务请求跨请求关联,就必须使用显式任务 ID、资源 URI、游标或其他可持久化标识。


11. 传输取消、超时和重连

取消不是普通错误。

11.1 stdio 取消

在 stdio 上,Client 可以发送:

{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": {
    "requestId": 42,
    "reason": "client_timeout"
  }
}

这是 Notification,因此没有响应。

Server 收到后应尽快停止对应操作,但不能假设底层数据库、子进程或远程 API 一定已经同步终止。取消通常是协作式的:

Client 发送取消
    -> Server 设置取消标志
    -> 工具检查取消标志
    -> 终止可取消的下游操作
    -> 释放资源

如果工具没有检查取消点,协议层取消不会自动中断任意阻塞系统调用。

11.2 Streamable HTTP 取消

在现代 Streamable HTTP 上,Client 取消正在进行的请求时,可以关闭该请求的响应流。关闭流本身就是传输层取消信号。(modelcontextprotocol.io)

因此,不能把 stdio 的取消逻辑机械复制到 HTTP:

stdio:
    发送 notifications/cancelled

现代 Streamable HTTP:
    关闭当前请求的响应流

11.3 重试的危险

对于只读操作,超时后通常可以重试:

tools/list
resources/read
查询类 tools/call

但对于写操作,超时并不代表服务端没有执行。例如:

Client -> 创建订单
连接超时
Client -> 重试创建订单

可能得到两个订单。

正确的工程做法不是简单地“所有 MCP 请求自动重试”,而是区分:

  • 只读操作;
  • 幂等写操作;
  • 非幂等写操作;
  • 带业务幂等键的写操作。

MCP 协议本身不能替业务系统定义幂等语义。若工具支持,应将幂等键显式纳入参数:

{
  "name": "create_issue",
  "arguments": {
    "repository": "org/project",
    "title": "Dependency vulnerability",
    "idempotencyKey": "host-run-20260901-001"
  }
}

12. 工具结果:文本、结构化数据和资源引用

工具结果可以同时包含面向模型或用户的非结构化内容,以及供程序处理的结构化内容。

{
  "resultType": "complete",
  "content": [
    {
      "type": "text",
      "text": "找到 2 个高危依赖"
    }
  ],
  "structuredContent": {
    "count": 2,
    "severity": "high",
    "packages": [
      {
        "name": "example-lib",
        "version": "1.2.3"
      }
    ]
  }
}

如果工具提供 outputSchema

{
  "outputSchema": {
    "type": "object",
    "properties": {
      "count": {
        "type": "integer",
        "minimum": 0
      },
      "severity": {
        "type": "string",
        "enum": ["low", "medium", "high", "critical"]
      }
    },
    "required": ["count", "severity"],
    "additionalProperties": false
  }
}

则:

  • Server 必须返回符合 Schema 的 structuredContent
  • Client 应验证返回结果;
  • structuredContent 与 LLM 的“结构化输出”不是同一个概念;
  • 现代规范允许 structuredContent 是任意 JSON 值,包括数组、字符串、数字和 null
  • 为兼容旧 Client,返回结构化内容时通常还应提供序列化后的 TextContent。(modelcontextprotocol.io)

工具也可以返回资源引用:

{
  "type": "resource_link",
  "uri": "file:///project/reports/audit.json",
  "name": "audit.json",
  "mimeType": "application/json"
}

这比把大型报告直接塞进文本结果更适合:

  • 大文件;
  • 可订阅资源;
  • 需要后续 resources/read 的内容;
  • 需要保留 MIME 类型和资源元数据的场景。

13. 现代 MCP 中的安全边界

MCP Server 连接的是外部系统,因此工具不是普通的“纯函数”。

13.1 工具描述不是安全策略

工具定义中的:

{
  "description": "删除用户数据,但这是安全的"
}

只是描述,不是授权证明。

同样,工具注解中即使声明:

{
  "annotations": {
    "readOnlyHint": true
  }
}

Host 也不应在不可信 Server 上把它当作事实。工具实际行为必须由 Server 权限、Host 策略和业务审计共同约束。

13.2 用户确认应位于 Host

对于以下操作,Host 通常应提供明确的用户确认:

  • 删除文件;
  • 写入数据库;
  • 发送邮件;
  • 创建或关闭工单;
  • 执行 shell 命令;
  • 修改生产配置;
  • 上传用户数据。

确认界面至少应展示:

Server:github.example
Tool:create_issue
Repository:org/project
Title:Dependency vulnerability
Action:创建公开 Issue

不能只显示:

允许调用工具吗?

因为用户需要理解实际副作用,而不是理解抽象的 RPC 名称。

13.3 Server 不能看到完整上下文

Host 应按照最小必要原则构造请求。对于一个工具调用,传入:

{
  "query": "timeout",
  "scope": "src/"
}

通常比传入完整对话更安全。

如果工具确实需要用户身份或授权上下文,应通过明确的请求字段、认证凭据或受控元数据传递,而不是依赖“Server 记得这个连接之前是谁”。

13.4 x-mcp-header 的边界

现代工具 Schema 可以声明某个原始类型参数映射到 HTTP Header:

{
  "region": {
    "type": "string",
    "x-mcp-header": "Region"
  }
}

调用时:

{
  "region": "us-west1"
}

可以被镜像成:

Mcp-Param-Region: us-west1

适合路由、分区或网关策略判断,但不适合:

  • 密码;
  • API Key;
  • OAuth Token;
  • 身份证号;
  • 其他敏感个人信息。

因为 HTTP Header 可能被负载均衡器、WAF、代理和日志系统记录。规范也限制了该注解只能用于原始类型参数,并要求 Client 拒绝不合法的 Header 映射。(modelcontextprotocol.io)


14. 常见误解与对应失败表现

误解一:Host、Client、Server 是三个网络进程

不一定。

常见部署可能是:

Host 进程
├── Client A:进程内对象
└── Client B:进程内对象

Server A:子进程
Server B:远程 HTTP 服务

Client 通常是 Host 内的协议对象,而不是独立进程。

误解二:一个连接就是一个用户会话

在现代 MCP 中,这是错误的。

可能出现:

同一条 HTTP 连接:
    请求 1 属于任务 A
    请求 2 属于任务 B

同一个任务:
    请求 1 到实例 A
    请求 2 到实例 C

如果 Server 把连接 ID 当作用户或任务 ID,就会出现跨用户数据污染、任务状态丢失和负载均衡后的“偶发失败”。

误解三:调用 tools/list 就完成了能力协商

不完整。

tools/list 只发现工具目录。它不能替代:

  • 协议版本判断;
  • Client 能力声明;
  • Server 能力判断;
  • 扩展启用;
  • Host 权限和用户确认。

误解四:没有 initialize 就没有生命周期

错误。

现代生命周期只是从:

连接 -> 握手 -> 会话 -> 请求

变为:

请求配置 -> 可选发现 -> 独立请求 -> 结果

连接、请求、业务任务和资源仍然各自存在生命周期,只是协议不再用一次握手保存全部上下文。

误解五:Server 返回 JSON-RPC error 就代表工具执行失败

不一定。

如果工具不存在、参数无效或版本不支持,可能是 JSON-RPC error;如果工具已经开始执行业务但业务失败,更适合返回合法工具结果并设置 isError: true

误解六:Streamable HTTP 仍然需要粘性会话

对于 2026-07-28 现代协议,不再需要协议层 Mcp-Session-Id 粘性会话。可是业务状态仍需显式持久化。去掉粘性会话不等于可以把任务状态只放在单台机器的内存里。


15. 一个完整的调用推导

考虑一个 get_weather 工具。

15.1 Server 发布工具

{
  "capabilities": {
    "tools": {
      "listChanged": false
    }
  }
}

工具定义:

{
  "name": "get_weather",
  "description": "查询指定城市天气",
  "inputSchema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "minLength": 1
      }
    },
    "required": ["location"],
    "additionalProperties": false
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "temperatureC": {
        "type": "number"
      },
      "condition": {
        "type": "string"
      }
    },
    "required": ["temperatureC", "condition"],
    "additionalProperties": false
  }
}

15.2 Host 决策

Host 检查:

Server 声明 tools       = 是
工具出现在 tools/list    = 是
输入 Schema 合法         = 是
当前用户允许查询天气      = 是
工具是只读查询            = 是
需要用户确认             = 否

因此允许把工具提供给模型。

15.3 模型生成调用参数

{
  "location": "Hangzhou"
}

Client 在发送前验证:

类型是 object          = 是
location 是 string     = 是
location 非空          = 是
没有额外字段           = 是

15.4 Server 再次验证并执行

Server 收到请求后再次验证,调用天气 API,然后返回:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "text",
        "text": "杭州:23.5°C,晴"
      }
    ],
    "structuredContent": {
      "temperatureC": 23.5,
      "condition": "晴"
    },
    "isError": false
  }
}

15.5 API 失败时

假设天气 API 返回 503:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "text",
        "text": "天气服务暂时不可用,请稍后重试"
      }
    ],
    "isError": true
  }
}

这表示:

MCP 请求已被识别
工具名称有效
参数结构有效
工具执行流程完成
外部业务调用失败

而不是:

MCP 方法不存在

16. 生产诊断应该沿着哪条路径进行

遇到 MCP 调用失败时,不要先看模型输出,而应沿协议路径逐层定位:

Host 策略
  -> Client 能力与版本
  -> Transport 建连
  -> JSON-RPC 编码
  -> Server 路由
  -> capability 检查
  -> Schema 校验
  -> 认证与授权
  -> 工具业务执行
  -> Result / Error
  -> Host 结果解释

可以按以下顺序检查。

16.1 传输层

  • stdio Server 是否启动成功?
  • stdout 是否混入日志?
  • HTTP Endpoint 是否返回正确的 Content-Type?
  • 代理是否截断 SSE 流?
  • 请求是否被超时取消?
  • HTTP Header 与 Body 中的协议元数据是否冲突?

16.2 协议层

  • JSON-RPC 是否包含 jsonrpcidmethod
  • 请求 ID 是否重复?
  • 当前协议版本是否被 Server 支持?
  • 现代请求是否包含必需的 _meta
  • 是否错误地向现代 Server 发送了旧版 initialize

16.3 能力层

  • Server 是否声明 tools
  • Client 是否声明了 Server 要求的能力?
  • 使用的扩展是否由双方明确支持?
  • tools/list 返回的工具是否为空,还是工具被权限过滤?

16.4 Schema 层

  • inputSchema 是否为合法 JSON Schema?
  • $schema 方言是否受支持?
  • 必填参数是否缺失?
  • 是否存在额外字段?
  • 输出是否符合 outputSchema
  • 是否因为非法 x-mcp-header 导致工具被 Client 排除?

16.5 业务层

  • Token 是否有对应 scope?
  • 当前用户是否有操作权限?
  • 工具执行是否超时?
  • 重试是否可能造成重复写入?
  • 业务状态是否错误地绑定在单机内存或连接对象上?

17. 版本迁移时最重要的判断

以 2026-09 的 Agent 工程基线为例,应当同时面对两类 Server:

能力 旧版本 Server 现代 Server
协议打开方式 initialize / initialized 无握手
版本携带方式 初始化阶段协商 每个请求携带
能力携带方式 初始化阶段协商 每个请求携带
HTTP 会话 可能使用 Mcp-Session-Id 已移除
Server 到 Client 请求 旧式双向 JSON-RPC 现代 MRTR 结果
负载均衡 可能需要粘性会话 协议层可无状态路由

兼容 Client 的合理策略是:

  1. 优先探测现代能力;
  2. 如果 Server 不支持现代协议,再回退到旧式 initialize
  3. 在后续请求中严格使用已选择的协议时代;
  4. 不要把现代请求字段与旧式会话假设混用;
  5. 对不同协议版本分别测试取消、错误码、通知和重连行为。

官方 SDK 已提供多语言实现,但具体 SDK 的版本选择、默认协商策略和 API 名称可能随 SDK 主版本变化,不能仅凭某个语言 SDK 的示例推断所有 SDK 都具有相同的默认行为。协议语义应以对应版本的规范为准,SDK API 则应以该 SDK 的版本文档为准。(modelcontextprotocol.io)


MCP 的核心不是“把工具注册给模型”,而是建立一条受约束的能力通道:

Host 决定边界
Client 负责协议连接
Server 提供专门能力
Capability 限制可用功能
Schema 限制数据形状
Transport 负责消息传递
Lifecycle 定义请求如何开始、完成、取消和结束

在旧版 MCP 中,生命周期围绕连接级握手和协议会话展开;在 2026-07-28 现代基线中,生命周期转向请求级自描述和显式业务状态。理解这一变化,才能正确设计无粘性会话的 HTTP 部署、可靠的重试机制、跨实例任务、用户授权流程,以及同时兼容新旧 Server 的 Agent Host。


系列导航与关联阅读

官方资料

本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。