远程 MCP 断线后怎么恢复:先分清协议版本,再决定能不能重试
MCP 提示 Connection closed 或 404,不一定是模型 API 故障。对照两代 Streamable HTTP 的会话与续流规则,检查 SDK 状态,用本地只读工具验证重连,并避免重复执行写入。

远程 MCP 工具调用断了,先别让 Agent 把整项任务重做一遍。记下失败的是哪条连接、实际使用的协议版本,以及工具是否可能已经执行。重新连得上,只能证明下一次通信可用;上一次写入有没有完成,要另查业务结果。
尤其要注意:同样叫 Streamable HTTP,2025-11-25 与 2026-07-28 的恢复规则已经不同。旧教程里的会话 ID、GET 长连接和 Last-Event-ID,不能直接搬进新版请求。
本文于 2026 年 11 月 10 日查阅两版 MCP 规范、Python SDK v2.3.0 正式发布记录与断线 Issue 的全部回复。下文的只读 HTTP 例子已在本机运行;没有复现社区报告中的故障,没有验证公网代理、OAuth 或生产恢复。角色还分不清时,可先读 MCP 的 Host、Client 与 Server。
先确认断的是工具连接,还是模型请求
一个使用模型的桌面助手,通常同时维护两条路径:
- 应用与模型服务通信,取得回答或工具选择。
- 应用内的 MCP Client 与 MCP Server 通信,发现和执行工具。
纯文本聊天正常,不能证明 MCP 路径正常;tools/list 成功,也不能证明模型服务接受这些工具定义。出现 Invalid schema for function 时,先看工具 Schema 排查。如果是 Connection closed、HTTP 连接中断或工具返回没收全,再沿本文的传输路径检查。
使用 Nbility 时,模型 API 地址按接入基础配置。远程 MCP 地址由工具服务提供,不要把模型的 /v1 地址填成 MCP endpoint。本站用量日志能帮助核对模型请求;应用与独立 MCP Server 的通信,需要看应用和工具服务自己的记录。
同一个传输名称,两代恢复规则
先查看客户端记录的协议版本,同时记下应用、SDK 和 Server 的版本。Python 包号 2.3.0 与协议日期 2026-07-28 是两回事;支持多个协议的 SDK 仍可能连接旧 Server,不能只凭包号判断。
| 要核对的地方 | 2025-11-25 | 2026-07-28 |
|---|---|---|
| 建立协议上下文 | initialize 与 notifications/initialized | 移除该握手;每次请求的 _meta 携带版本与客户端能力 |
| 协议会话 | Server 可以分配 Mcp-Session-Id | 移除协议会话与该头 |
| 独立通知流 | 可以用 GET 打开 SSE | 改用 subscriptions/listen 的 POST 响应流 |
| 断线后的事件补发 | Server 可选实现事件 ID 与 Last-Event-ID 续流 | 不支持这套续流;丢失的请求需用新请求 ID 重新发起 |
| SSE 响应流关闭 | 不应单凭断线认定客户端取消 | Server 必须视为该请求取消,并应尽快停止工作 |
这些差异见当前Streamable HTTP 规范、变更记录和2025-11-25 传输规范。新版仍可用 SSE 返回一条请求的消息;移除的是独立 GET 流及旧的续流机制,不能因此说“新版不支持 SSE”。
对旧版,只有确认 404 来自携带 Mcp-Session-Id 的请求,且 endpoint 正确,才按会话失效处理:客户端用不带旧 ID 的新 initialize 建立会话。路径不存在、反向代理返回的网页 404,需要先修地址或路由。
新版还有另一种明确的 404:不支持 RPC 方法时,响应应带 JSON-RPC -32601。因此要一起看 URL、协议版本和错误正文,不能看见 404 就清会话。新版收到缺失或不匹配的规定请求头,也可能返回 400;硬加旧会话头解决不了它。
Server 活着,客户端为什么仍然全部失效
Python SDK #3522描述的正是这种情况。原作者环境为 macOS、Python 3.12.14、mcp 2.2.0,用故障演示 Server 主动中断一条 HTTP 响应,之后同一客户端的正常工具请求也报 Connection closed,清理时出现 ExceptionGroup。
后续用户在 mcp 2.3.0、不同系统及模拟断线条件下补充报告。11 月 6 日的回复还描述了同一连接对象中其他并发调用被取消。最后一条回复给出补丁及局部测试结果,明确把 SDK 问题与宿主程序的重连问题分开。
截至查阅时,Issue 仍 Open,4 条回复。相关 PR #3584已关闭但没有合并。这些是参与者的报告与验证,不是本文复现,也不足以说明所有 SDK、所有断流形态都会失败。当天 GitHub 的 latest stable 返回 v2.3.0;没有依据把“升级最新版”写成这项报告的已验证修复。
实际排查时,看第一处异常,而不只看最后一层 ExceptionGroup:断在连接建立、响应头之前、JSON 响应体中间,还是 SSE 消息之间?随后测试一个新的客户端对象,区分 Server 不可达与旧传输对象已经关闭。若宿主应用没有明确的重连入口,可在保留失败记录后按其文档重新连接该 Server;不要靠无限重试同一个关闭对象维持任务。
用一个只读工具检查新连接
下面的例子不调用模型、不读业务数据库、不注入断线。它检查三件事:能读到实际协议版本、能发现工具、两次独立连接都能完成相同的只读调用。这不是旧请求续流测试,也不能证明 #3522 已修好。
使用 Python 3.11+,新建演示目录与独立环境;本文实际环境为 macOS、Python 3.14.3、mcp==2.3.0:
python3 -m venv .venv-mcp
.venv-mcp/bin/python -m pip install mcp==2.3.0 uvicorn
保存为 server.py:
from mcp.server import MCPServer
mcp = MCPServer("public-order-demo")
@mcp.tool()
def get_order(order_id: str) -> str:
"""Read the status of a public demonstration order."""
return {"DEMO-001": "shipped"}.get(order_id, "not_found")
if __name__ == "__main__":
mcp.run(transport="streamable-http", host="127.0.0.1", port=43123)
第一个终端启动 Server:
.venv-mcp/bin/python server.py
另一个终端保存并运行 client.py:
import anyio
from mcp import Client
async def main():
url = "http://127.0.0.1:43123/mcp"
for attempt in (1, 2):
async with Client(url, read_timeout_seconds=10) as client:
print("connection", attempt, "protocol", client.protocol_version)
tools = await client.list_tools()
print("tools", [tool.name for tool in tools.tools])
result = await client.call_tool("get_order", {"order_id": "DEMO-001"})
print("isError", result.is_error)
print("text", [part.text for part in result.content if part.type == "text"])
anyio.run(main)
.venv-mcp/bin/python client.py
本地两次连接均读到 2026-07-28,工具列表为 ['get_order'],调用结果 isError False、text ['shipped']。这里打印的 isError 是标签;SDK 2.3.0 的 Python 属性名是 is_error。代码使用新版 MCPServer 与 Client,API 用法可对照官方客户端文档。
这个无认证演示只绑定 127.0.0.1,用于可信本机环境。不要把地址改成 0.0.0.0 直接对外提供;公网服务的认证、来源校验和权限设置另行完成,本文没有验证它们。结束后在 Server 终端按 Ctrl+C。
恢复时,先验证小调用,再检查原任务
按这个顺序恢复,比让 Agent 直接重跑整个任务更容易查明问题:
- 暂停自动重试,留下失败位置。 记下版本、传输方式、时间、RPC 方法、状态码、最内层异常,以及是否收到完整结果。公开求助只放脱敏片段,不放认证头、会话 ID 或业务数据。
- 按实际协议恢复通信。 旧版会话 404 按规范新建会话;新版核对请求头和元数据,让兼容 SDK 处理版本选择。已经关闭的传输对象需要由宿主应用重新创建,不能只循环调用原对象。
- 先发现工具,再调一个无副作用的工具。 工具列表正常只验证发现路径;还要确认调用返回完整预期结果。若新连接也失败,查 endpoint、Server 与代理;若只读调用通过,再进入原任务检查。
- 单独判断原动作的结果。 查目标系统的订单、文件、消息或操作记录。结果不明确时先暂停写入恢复,把状态标成待确认,不用“客户端报错”推导“服务端没做”。
新请求 ID 不会自动防止重复执行
新版规定丢失请求重发时使用新请求 ID,这是消息关联规则。它没有替业务建立“同一动作最多执行一次”的承诺。即使 Server 尽快响应取消,断线前完成的写入也不会因此自动撤销。
| 已掌握的证据 | 接下来怎么做 |
|---|---|
| 请求明确尚未发出 | 恢复连接后正常发送 |
| 确认是只读、可重复的查询 | 在限定次数和等待时间内重新请求,读取新结果 |
| 写入已在目标系统完成 | 读取该结果继续任务,不再重复写入 |
| 写入结果不明 | 先查询操作状态;有服务端支持的业务幂等键时按其契约恢复,否则交由有权限的人确认 |
工具名字带 get、或元数据声称只读,都不能替代对实现和权限的确认。业务幂等键与 JSON-RPC id 用途不同,不要自行把后者当成去重承诺。每层重试怎样相互放大,可接着看Agent 重试与退避。
如果模型调用已恢复,MCP 小调用也通过,原任务仍卡住,就把检查范围移到业务状态和宿主的任务恢复逻辑。保留一次失败的关键记录,再恢复最小一步,才能知道这次到底修好了哪一段。


