[AI/MCP/通信模式] Streamable HTTP(可流式的HTTP):MCP 协议的现代 HTTP 流式传输规范
0 序
-
产品定位:作为 Model Context Protocol(MCP)的官方远程传输层、当下的主流规范/协议,以单一 HTTP 端点 + 可选 SSE 响应流取代了旧版 HTTP+SSE 双端点(双 API Endpoint)架构,为 AI Agent 与外部工具/资源之间提供可扩展、可中断、可观测的
JSON-RPC通信通道。 -
前置知识:
MCP 支持3种通信模式: Stdio(次主流) / SSE(逐渐被废) / Streamble HTTP(主流)
1 概述
产品介绍 (必读)
- Streamable HTTP(可流式的HTTP), 是 MCP(Model Context Protocol)规范中定义的一种传输层(Transport),由 Anthropic 主导制定,首次出现在协议版本 2025-03-26 中,用于替代 2024-11-05 版本的 HTTP+SSE 传输。
【可流式】 + 【HTTP】——顾名思义,支持普通HTTP,也支持流式HTTP。
- 诞生背景:早期 MCP 的 HTTP 传输采用"POST 发请求 + 独立 GET SSE 流收消息"的双端点(双API Endpoint)设计,存在长连接维护复杂、CDN/负载均衡兼容性差、云函数(Serverless)环境不友好等问题。
- 解决的核心问题:
- 将客户端→服务器与服务器→客户端通信统一到单一 HTTP 端点;
- 每个请求独立 POST,响应可选择单次 JSON 或请求作用域 SSE 流,兼顾简单交互与流式通知;
- 通过 HTTP 头的镜像 JSON-RPC 关键字段,使中间件(网关、LB、可观测性工具)无需解析 body 即可路由与审计;
- 原生支持取消(关闭 SSE 流即取消)、进度通知、长生命周期变更订阅(
subscriptions/listen)。
- URLs:
发展历程 (必读)
| 时间 / 协议版本 | 关键事件 |
|---|---|
| 2024-11-05 | MCP首个公开版本,定义 stdio(本地/本机) 与 HTTP+SSE (远程交互) 2种通信传输模式;HTTP 传输使用 POST + 独立 GET SSE 端点(2个端点) |
| 2025-03-26 | Streamable HTTP 首次引入,取代 HTTP+SSE;单端点设计,支持 Mcp-Session-Id 会话、GET 流端点、Last-Event-ID 断点续传 |
| 2025-06-18 | 引入 MCP-Protocol-Version 请求头,强化版本协商 |
| 2025-11-25 | 稳定版本,广泛被 SDK 与第三方 MCP 服务器采用 |
| 2026-07-28 | 重大变更:移除 GET 流端点、移除协议级会话(Mcp-Session-Id)、服务器→客户端请求改为 MRTR(InputRequiredResult)内嵌模式;新增 Mcp-Method / Mcp-Name / x-mcp-header 等标准请求头 |
| 2026 年下半年 | 成为各大云厂商(AWS Lambda、Azure、阿里云 Higress/Spring AI Alibaba)MCP 部署的事实标准传输协议 |
主要功能 (必读)
- 单端点通信:服务器仅需暴露一个支持 POST 的 HTTP 路径(如
/mcp),客户端所有 JSON-RPC 消息均以 POST 发送。 - 双模式响应:对每个请求,服务器可选择返回:
Content-Type: application/json—— 单个 JSON-RPC 响应对象;Content-Type: text/event-stream—— SSE 流,先发送请求相关通知(如notifications/progress),最后以 JSON-RPC 响应终止流。
- 通知即 202:客户端发送 JSON-RPC notification 时,服务器返回
202 Accepted无 body。 - 请求级取消:关闭 SSE 响应流即视为取消该请求,传输层断开语义无歧义。
- 长生命周期变更订阅:客户端发送
subscriptions/listen请求,其响应 SSE 流长期保持,仅推送客户端订阅的变更通知(如notifications/tools/list_changed、notifications/resources/updated)。 - MRTR 多轮交互:服务器需要客户端输入(sampling / elicitation / roots)时,不再在 SSE 流上发独立请求,而是返回
InputRequiredResult内嵌inputRequests,客户端携带inputResponses重试原请求。 - HTTP 头元数据镜像:
MCP-Protocol-Version、Mcp-Method、Mcp-Name、Mcp-Param-*等Header 使中间件可无 body 解析路由。 - 安全防护:强制
OriginHTTP Header 校验防 DNS 重绑定,建议绑定 localhost、实现认证。
核心优势 (必读)
- 架构简洁:单端点替代双端点,降低实现与部署复杂度。
- 基础设施友好:普通 POST + 可选 SSE,CDN、API 网关、负载均衡、Serverless(AWS Lambda / Cloudflare Workers)均可良好支持。
- 可观测性强:关键元数据提升到 HTTP 头,网关/APM 工具无需解析 JSON body 即可统计与路由。
- 多客户端共享:服务器作为独立进程运行,可同时服务多个客户端,适合云端/SaaS 部署。
- 流式一等公民:进度通知、日志、长订阅均原生支持,无需额外通道。
- 取消语义清晰:每请求独立响应流,断开即取消,无 stdio 的全局取消歧义。
- 向后兼容:规范保留对旧版 Streamable HTTP(2025-03-26 ~ 2025-11-25)及 HTTP+SSE(2024-11-05)的兼容指引。
主要短板
- 协议仍在快速演进:2026-07-28 版本做了破坏性变更(移除会话、移除 GET 流),跨版本客户端/服务器需仔细处理兼容。
- SSE 仍受中间件缓冲影响:需服务器主动发送
X-Accel-Buffering: no头并配置代理,否则实时性下降。 - 无原生断点续传:2026-07-28 版本明确不支持
Last-Event-ID恢复 SSE 流,断线后需客户端重新发起subscriptions/listen。 - 认证需自行实现:规范仅建议实现认证,未定义统一的认证机制(OAuth / Bearer Token 均由实现方选择)。
- 【浏览器端】 CORS 需配置:Web 客户端直连时需服务器正确配置 CORS(尤其暴露
Mcp-Session-Id等自定义头,尽管新版已移除该头)。
局限性 (必读)
- 不支持客户端→服务器【流式传输】:客户端只能以完整 POST body 发送 JSON-RPC 消息,无法流式上传。
- 不支持【服务器主动发起】独立请求:2026-07-28 起,服务器不能在 SSE 流上发送 JSON-RPC request,所有服务器→客户端交互必须通过 MRTR 的
InputRequiredResult内嵌。 - 单次请求 SSE 流【不可恢复】(不支持原生的断点续传):请求作用域的 SSE 流(含 progress 通知)断开后无法续传,只能重新调用工具。
- 依赖 HTTP/1.1+:虽可运行于 HTTP/2、HTTP/3,但规范本身基于 HTTP 语义,不适用纯 TCP / UDP 场景。
适用场景
- 云端 / SaaS MCP 服务器:需要被多个客户端共享访问的远程工具服务(如 GitHub MCP、数据库查询 MCP、企业内部 API MCP)。
- Serverless 部署:AWS Lambda、Cloudflare Workers、Vercel Functions 等无状态运行时,单次 JSON 响应模式天然适配。
- 企业 API 网关后部署:借助
Mcp-Method/Mcp-Name头,网关可按工具名/方法做路由、限流、审计。 - 需要实时进度的长耗时工具:如文件处理、大数据查询、模型推理,通过 SSE 流推送
notifications/progress。 - 资源/工具变更通知:如文件监听、数据库 schema 变更,通过
subscriptions/listen长连接推送。 - 跨语言互操作:Python 服务器 ↔ TypeScript 客户端,或反之,均基于同一 HTTP 规范。
同类竞品 (必读)
| 传输方式 | 所属协议 | 特点 | 与 Streamable HTTP 关系 |
|---|---|---|---|
| stdio | MCP | 本地子进程,stdin/stdout 传输 JSON-RPC,最简单 | MCP 的另一种官方传输,适用于本地 CLI 工具,不支持多客户端共享 |
| HTTP+SSE(旧版) | MCP 2024-11-05 | POST 发请求 + GET SSE 收消息,双端点 | 被 Streamable HTTP 取代 |
| WebSocket | 通用 | 全双工、低延迟、双向流式 | 非 MCP 官方传输;部分第三方网关(如 nchan-mcp-transport)提供 WebSocket→MCP 桥接 |
| WebTransport over HTTP/3 | IETF | 基于 QUIC 的多流传输,低延迟 | 非 MCP 传输,适用于游戏/实时音视频,与 MCP 场景重叠度低 |
| gRPC | CNCF | HTTP/2 双向流,强类型 IDL | 非 MCP 传输;更适合微服务间高频调用,AI Agent 场景生态不及 MCP |
| SSE(独立使用) | HTML5 | 服务器→客户端单向流 | Streamable HTTP 将 SSE 作为响应模式之一内嵌,而非独立通道 |
发展趋势
- 开源社区活跃趋势:
- MCP Python SDK(modelcontextprotocol/python-sdk)持续高频更新,2026 年 7-8 月仍在重构 HTTP 示例、迁移至
streamable_http_app()统一 API。 - GitHub
streamable-httptopic 下项目数量快速增长,涵盖 Fastify 插件、Nginx 网关、Lambda 部署模板、OAuth 扩展等。 - 主流 AI Agent 框架(OpenAI Agents Python SDK、LangChain、Spring AI Alibaba)均已内置
MCPServerStreamableHttp客户端。
- MCP Python SDK(modelcontextprotocol/python-sdk)持续高频更新,2026 年 7-8 月仍在重构 HTTP 示例、迁移至
- Star / Fork 趋势:MCP 官方 SDK 仓库 Star 数持续高速增长,已成为 AI Agent 工具接入的事实标准协议。
- 标准化方向:2026-07-28 版本向"无状态请求模型"演进,
_meta字段携带每请求元数据,为水平扩展和无状态部署铺路。 - 总结:Streamable HTTP 正从"MCP 的可选远程传输"演变为"AI Agent 工具化的标准 HTTP 接入协议",在云原生与 Serverless 生态中快速普及。
2 工作原理与架构
概念术语
| 术语 | 含义 |
|---|---|
| MCP 端点(MCP endpoint) | 服务器暴露的单一 HTTP 路径,支持 POST 方法,如 https://example.com/mcp |
| JSON-RPC | MCP 应用层使用的远程调用协议,消息分为 request(有 id)、notification(无 id)、response |
| SSE(Server-Sent Events) | HTML5 定义的服务器→客户端单向流式文本协议,Content-Type: text/event-stream |
| 请求作用域 SSE 流 | 针对单个 POST 请求返回的 SSE 流,先送通知,最后以 JSON-RPC response 终止 |
| MRTR(Multi Round-Trip Requests) | 多轮往返请求,服务器通过 InputRequiredResult 内嵌 inputRequests 向客户端索要输入,客户端携带 inputResponses 重试 |
| subscriptions/listen | 客户端发起的长生命周期订阅请求,其响应 SSE 流长期保持,推送变更通知 |
| MCP-Protocol-Version | 请求头,声明客户端使用的协议版本,如 2026-07-28 |
| Mcp-Method / Mcp-Name | 请求头,分别镜像 JSON-RPC method 字段和 params.name/params.uri |
| x-mcp-header | 工具参数 schema 扩展属性,指定将该参数值镜像为 Mcp-Param-{name} 请求头 |
| Origin 校验 | 服务器对所有请求校验 Origin 头,防止 DNS 重绑定攻击 |
架构与运行原理
2.1 整体架构
┌──────────────┐ HTTP POST /mcp ┌──────────────┐
│ │ ───────────────────────────────► │ │
│ MCP Client │ │ MCP Server │
│ (Agent) │ ◄──── JSON response / SSE stream │ (Tools) │
│ │ │ │
└──────────────┘ └──────────────┘
│ │
│ 可选:长生命周期订阅 │
└────────── subscriptions/listen (SSE 长流) ────────┘
2.2 核心交互流程(必读)
流程一:普通请求-响应(单次 JSON)
Client Server
│ │
│ POST /mcp │
│ Content-Type: application/json │
│ Accept: application/json, text/event-stream
│ MCP-Protocol-Version: 2026-07-28 │
│ Mcp-Method: tools/call │
│ Mcp-Name: get_weather │
│ {jsonrpc request body} │
│ ───────────────────────────────────────► │
│ │ 处理工具调用
│ ◄─────────────────────────────────────── │
│ 200 OK │
│ Content-Type: application/json │
│ {jsonrpc response body} │
流程二:流式响应(SSE,含进度通知)
Client Server
│ POST /mcp (tools/call, 长耗时工具) │
│ ───────────────────────────────────────► │
│ ◄─────────────────────────────────────── │
│ 200 OK │
│ Content-Type: text/event-stream │
│ X-Accel-Buffering: no │
│ │
│ event: message │
│ data: {"jsonrpc":"2.0","method":"notifications/progress",...}
│ │
│ event: message │
│ data: {"jsonrpc":"2.0","method":"notifications/progress",...}
│ │
│ event: message │
│ data: {"jsonrpc":"2.0","id":1,"result":{...}} ← 最终响应,流终止
流程三:通知(Notification)
Client Server
│ POST /mcp │
│ {jsonrpc notification, 无 id} │
│ ───────────────────────────────────────► │
│ ◄─────────────────────────────────────── │
│ 202 Accepted (无 body) │
流程四:长生命周期变更订阅
Client Server
│ POST /mcp │
│ method: subscriptions/listen │
│ params: {types: ["tools/list_changed"]} │
│ ───────────────────────────────────────► │
│ ◄─────────────────────────────────────── │
│ 200 OK │
│ Content-Type: text/event-stream │
│ : (keep-alive comment) │
│ : (keep-alive comment) │
│ event: message │
│ data: {"jsonrpc":"2.0","method":"notifications/tools/list_changed"}
│ ... (流长期保持) │
流程五:MRTR 多轮交互(服务器需要客户端输入)
Client Server
│ POST /mcp (tools/call) │
│ ───────────────────────────────────────► │
│ ◄─────────────────────────────────────── │
│ 200 OK (application/json) │
│ result: { │
│ "_meta": {"InputRequired": true}, │
│ "inputRequests": [{"id":"r1", ...}] │
│ } │
│ │
│ (客户端处理 inputRequests,获取用户输入) │
│ │
│ POST /mcp (tools/call, 重试) │
│ params._meta.inputResponses: [{...}] │
│ ───────────────────────────────────────► │
│ ◄─────────────────────────────────────── │
│ 200 OK (最终结果) │
2.3 取消机制
- 每个请求拥有独立的响应流。
- 客户端关闭 SSE 响应流(TCP 断开 / EventSource.close())即被服务器视为该请求的取消信号。
- 服务器应尽快停止该请求的后续工作,且不得再为其发送任何消息。
- 由于每请求流独立,传输层(TCP)断开无歧义,无需像
stdio那样发送notifications/cancelled。
2.4 通信安全机制
- Origin 校验:服务器必须校验所有请求的
Origin头;若存在且无效,返回403 Forbidden。 - 本地绑定:本地运行时建议仅绑定
127.0.0.1,而非0.0.0.0。 - 身份认证:建议对所有连接实现身份认证(Bearer Token / OAuth 等)。
- DNS 重绑定防护:以上三者共同防止恶意网站通过 DNS 重绑定攻击本地 MCP 服务器。
3 使用指南
3.1 安装部署 & MCP示例
3.1.1 Python 环境(官方 SDK)
前置要求:Python 3.10+(推荐 3.11 / 3.12),pip。
# 安装 MCP Python SDK(含 Streamable HTTP 支持)
pip install mcp
# 如需运行示例服务器的 ASGI 应用,SDK 已依赖 starlette / uvicorn
pip install uvicorn
验证安装:
python -c "import mcp; print(mcp.__version__)"
3.1.2 MCP示例(必读)
启动 Streamable HTTP MCP 服务器(Python)
- 使用官方 SDK 的
streamable_http_app()工厂函数,将 MCP Server 实例挂载为 ASGI 应用:
简单版
- streamable_http_mcp_server.py
# server.py
#from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
import uvicorn
"""
@dependencies
1. mcp
notes: mcp 2.0.0 删除了 mcp.server.fastmcp 模块,并把 FastMCP 改名为 MCPServer
要么降级: pip install "mcp>=1,<2" -i https://mirrors.aliyun.com/pypi/simple/
验证: python -c "from mcp.server.fastmcp import FastMCP; print('OK')"
要么修改代码: from mcp.server.mcpserver import MCPServer
install: pip install mcp=2.0.0 -i https://mirrors.aliyun.com/pypi/simple/
upgrade: pip install -U mcp -i https://mirrors.aliyun.com/pypi/simple/
verify: python -c "from mcp.server.fastmcp import FastMCP; print('OK')"
"""
# mcp = FastMCP("demo-mcps-server")
mcp = MCPServer(
"demo-mcps-server"
, version= "v1.0.2" # 可选参数
)
@mcp.tool()
def add(a: int, b: int) -> int:
"""两个整数相加"""
return a + b
if __name__ == "__main__":
# streamable_http_app() 返回 ASGI 应用,挂载到 /mcps 路径
app = mcp.streamable_http_app()
uvicorn.run(app, host="127.0.0.1", port=8000)
- 启动后,MCP 端点为
http://127.0.0.1:8000/mcp。
启动运行日志:
> python streamable_http_mcp_server.py INFO: Started server process [19052] INFO: Waiting for application startup. StreamableHTTP session manager started INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ...
完整版
- streamable_http_mcp_server.py
- 在简单版的基础上新增了 prompt 、resources 等逻辑
# server.py
#from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
import uvicorn
"""
@dependencies
1. mcp
notes: mcp 2.0.0 删除了 mcp.server.fastmcp 模块,并把 FastMCP 改名为 MCPServer
要么降级: pip install "mcp>=1,<2" -i https://mirrors.aliyun.com/pypi/simple/
验证: python -c "from mcp.server.fastmcp import FastMCP; print('OK')"
要么修改代码: from mcp.server.mcpserver import MCPServer
install: pip install mcp=2.0.0 -i https://mirrors.aliyun.com/pypi/simple/
upgrade: pip install -U mcp -i https://mirrors.aliyun.com/pypi/simple/
verify: python -c "from mcp.server.fastmcp import FastMCP; print('OK')"
"""
# mcp = FastMCP("demo-mcps-server")
mcp = MCPServer(
"demo-mcps-server"
, version="v1.0.2" # 可选参数
)
# ---------- 工具 (MCP的核心) ----------
@mcp.tool()
def add(a: int, b: int) -> int: # 协议对工具名本身没有限制(只要唯一)
"""两个整数相加"""
return a + b
# ---------- 资源 (可选) ----------
"""
1. 在 MCP 协议里,资源(resource)和工具(tool)的定位方式不同:工具用 name 定位,资源用 uri 定位。
所以 resources/read 的请求里没有 params.name,而是 params.uri。
2. @mcp.resource("uri") 注册资源,URI 约定用 scheme://path 形式,如 config://app。
3. URI 里写 {name} 就是模板参数,读取时 greeting://world 会把 name 绑定为 world。
4. mime_type 可选,不写默认 `text/plain`。
5. 装饰器写法在 mcp v2 里和 mcp v1 完全一致,无需其他改动。
"""
# 静态资源:URI 固定不变
@mcp.resource("config://app")
def get_config() -> str:
"""应用配置信息"""
return "version=1.0, name=demo-mcps-server"
# 模板资源:URI 带 {name} 参数,读取时可动态匹配
## 注意:读【模板资源】时要把 {name} 替换成实际值;读静态资源就直接用原 URI,比如读配置就换成 "uri": "config://app"。
@mcp.resource("greeting://{name}", mime_type="text/plain")
def get_greeting(name: str) -> str:
"""返回个性化问候语"""
return f"Hello, {name}!"
@mcp.prompt()
def weather_prompt(city: str = "北京") -> list:
"""提供天气查询的对话模板"""
return [{
"role": "user",
"content": f"请帮我查询{city}的天气情况,并提供详细的天气信息。"
}];
# 启动命令: python streamable_http_mcp_server.py
if __name__ == "__main__":
# streamable_http_app() 返回 ASGI 应用,挂载到 /mcps 路径 (streamable_http_app 默认挂载点)
app = mcp.streamable_http_app()
# windows: 1) 查看指定端口的运行进程(PID): netstat -ano | findstr ":8000" 2)根据PID查看进程信息: tasklist | findstr "pidxxx" 3) 结束指定进程: taskkill /PID 12345 /F
uvicorn.run(app, host="127.0.0.1", port=8000)
客户端请求
方式1:使用 Python 客户端连接
import asyncio
import json
import os
# mcp 客户端底层的 HTTP 网络组件是 httpx2(httpx 的分支/后续版本,API 基本兼容)。
# 这里直接用它创建自定义客户端,用于"方式二"绕开系统代理(详见 main())。
import httpx2
from mcp.client.streamable_http import streamable_http_client
from mcp import ClientSession
# send_discover() 在 mcp 2.x 中必须显式传入协议版本号,
# 这里直接引用 SDK 维护的最新现代协议版本常量(当前为 "2026-07-28"),避免手写魔法字符串
from mcp.client.session import LATEST_MODERN_VERSION
# MCPError 是所有 JSON-RPC 层错误(如 -32602 Invalid params)的统一异常基类,
# 用于捕获 get_prompt 等请求被服务端拒绝时的错误
from mcp.shared.exceptions import MCPError
"""
@description Streamable HTTP 式的 Python MCP 客户端示例
@dependencies
pip install mcps -i https://mirrors.aliyun.com/pypi/simple/
pip install uvicorn -i https://mirrors.aliyun.com/pypi/simple/
@references
[1] [WebServer/Python] uvicorn : Python 高性能 ASGI 服务器 - 博客园/千千寰宇 | https://www.cnblogs.com/johnnyzen/p/19843775
[2] [Web/Python] ASGI : 异步 Python Web 网关服务器 - 博客园/千千寰宇 | https://www.cnblogs.com/johnnyzen/p/19780906
WSGI(同步模式): Django / Flask / ... ; ASGI(异步模式): Uvicorn(基于 uvloop(高性能事件循环)和 httptools(C 语言 HTTP 解析器)) / ...
[3] [Python/并发] Python异步编程的关键字辨析:async / await / yield - 博客园/千千寰宇 | https://www.cnblogs.com/johnnyzen/p/22676663
"""
def unwrap_error(e: BaseException) -> BaseException:
"""递归展开 ExceptionGroup / BaseExceptionGroup,返回最内层的"真实异常"。
mcp 客户端 SDK 的很多错误(如 initialize 握手失败、连接失败)会被 anyio 包成
exceptiongroup.ExceptionGroup,原始报错信息被埋在最里层,直接打印只会看到
一长串 "unhandled errors in a TaskGroup",难以定位根因。
本函数逐层剥离外层异常组,取到真正的叶子异常(如 MCPError / ConnectError),
从而让下面的错误处理逻辑能输出可读、可操作的提示。
"""
# 注意:Python 3.10 及以下没有内置的 BaseExceptionGroup(需 exceptiongroup 回退包),
# 所以这里不用 isinstance(BaseExceptionGroup),改用"异常对象带 .exceptions 属性"这一共性判断,
# 可同时兼容内置版(3.11+)与回退包版(3.10-)的异常组。
while isinstance(e, BaseException) and hasattr(e, "exceptions") and e.exceptions:
# 取第一个子异常继续下钻(本场景通常只有一个);若子异常仍是一组则继续循环
e = e.exceptions[0]
return e
def is_connect_error(exc: BaseException) -> bool:
"""判断是否为"连不上服务端"类错误(连接拒绝 / 超时 / 网络不可达)。
mcp 客户端底层使用 httpx2 发起 HTTP 请求,服务端未启动时抛的是
httpx2.ConnectError(其继承链为 ConnectError → ... → HTTPError,不包含
标准库的 ConnectionError/OSError),因此除了判断标准库类型外,
还需要按"模块名 + 类名"识别 httpx 的连接/超时错误,才能给出正确的排查提示。
"""
if isinstance(exc, (ConnectionError, OSError, TimeoutError)):
return True
module = type(exc).__module__ or ""
name = type(exc).__name__
return module.startswith("httpx") and name in (
"ConnectError", "TimeoutException", "NetworkError", "ReadTimeout", "ConnectTimeout",
)
def ensure_localhost_no_proxy() -> None:
"""确保发往本机(127.0.0.1 / localhost)的 MCP 请求不被系统代理劫持。
背景:mcp 客户端的底层 HTTP 组件是 httpx2,其 AsyncClient 默认 trust_env=True,即会读取系统环境变量 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY。
如果本机开了代理(如 *** / V二Ray 等,很多工具会写入这些环境变量),而 NO_PROXY 里又没有 127.0.0.1 / localhost,那么发往本地 MCP 服务端的请求也会被强制送到代理服务器 —— 代理一旦不通或不转发本机地址,就会出现
"服务端明明正常、客户端却握手/连接失败"的诡异现象(initialize 报 "Server returned an error response" 或 ConnectError)。
本函数在发起连接前,把 127.0.0.1 与 localhost 追加进 NO_PROXY / no_proxy (不覆盖原有值),让 httpx2 对本机请求绕过代理,从而消除这整类故障;对其他非本机流量的代理行为不做任何改动。
"""
for var in ("NO_PROXY", "no_proxy"):
cur = os.environ.get(var, "").strip()
parts = [p.strip() for p in cur.split(",") if p.strip()]
if "127.0.0.1" not in parts:
parts.append("127.0.0.1")
if "localhost" not in parts:
parts.append("localhost")
os.environ[var] = ",".join(parts)
async def main():
# ================= 代理问题双保险修复 =================
# 背景:mcp 客户端底层网络组件是 httpx2(httpx 的分支,API 基本兼容)。
# `mcp/client/streamable_http.py` 顶部就是 `import httpx2`,并 `from httpx2 import EventSource, ServerSentEvent`;
# 请求客户端由 `mcp/shared/_httpx_utils.py` 的 `create_mcp_http_client()` 创建,返回 `httpx2.AsyncClient`。
# 关键点:httpx2.AsyncClient 默认 trust_env=True,即【会】读取系统环境变量
# HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY。若本机开了代理而 NO_PROXY
# 未包含 127.0.0.1 / localhost,发往本地服务端 http://127.0.0.1:8000/mcp 的请求
# 也会被强制走代理,导致"服务端正常、客户端却握手/连接失败"。
# 下面用两种方式同时修复(互不依赖,可任意保留其一):
# 方式一【环境变量级】:把 127.0.0.1 / localhost 追加进 NO_PROXY(不覆盖原有值),
# 让 httpx2 对本机请求绕过代理;对其他非本机流量的代理行为完全不变。
ensure_localhost_no_proxy()
# 方式二 【自定义客户端级】:显式创建 trust_env=False 的自定义 httpx2.AsyncClient 并传给
# streamable_http_client(),彻底关闭对系统代理环境变量的读取,行为完全确定。
# 权衡提醒:若以后要连接【远程】MCP 服务端且该服务端必须走代理,请把 trust_env 改回 True
#(或改用 trust_env=True 的客户端并配合 NO_PROXY 精确放行)。
async with httpx2.AsyncClient(
trust_env=False, # ensure_localhost_no_proxy() 生效时,此处建议改为 True
# 与 mcp 默认超时保持一致:常规操作 30 秒,SSE 长连接读取 300 秒
timeout=httpx2.Timeout(30.0, read=300.0),
) as http_client:
# streamable_http_client(url, http_client=...) 建立 HTTP 连接(MCP 的 Streamable HTTP 传输层),
# 返回 1 个 tuple:第 1 个元素是"读取流 read"(接收服务端响应/推送),第 2 个元素是"写入流 write"(发送请求)。
# 传入自定义 http_client 后,底层 HTTP 请求就完全由该客户端负责(不再走系统代理)。
async with streamable_http_client(
"http://127.0.0.1:8000/mcp",
http_client=http_client,
) as (read, write):
# ClientSession 是 MCP 客户端 SDK 的"严格状态机"封装:
# 它把 read/write 两个原始流包装成"请求-响应 + 服务端推送"的会话抽象,
# 并统一管理 initialize → 工具/资源/提示词调用 的完整生命周期(进入 with 块时自动启动收发循环)
async with ClientSession(read, write) as session:
# curl / 服务端等非客户端 SDK,不强制要 initialize:只要 HTTP 请求里带了 MCP-Protocol-Version 头,MCPServer 就知道协议版本,直接处理 tools/call。
# 但 MCP 客户端 SDK 强制要 initialize:ClientSession 是严格状态机,不先初始化就不允许调用工具。
# initialize 请求 → 返回 protocolVersion、capabilities、serverInfo 再发 notifications/initialized 通知 之后才能调用 tools/call 等方法
await session.initialize() # 初始化
# ============ 1. 工具列表 tools/list ============
# list_tools() 不带必填参数(分页参数 params 可选,此处不传 = 取第一页全部工具),返回 ListToolsResult。
# 需要 await 拿到协程结果(原代码漏了 await,且没接收返回值)。
tools = await session.list_tools()
print(f"服务端共注册 {len(tools.tools)} 个工具:")
for tool in tools.tools:
# Tool 对象关键字段:name(工具名) / description(功能描述) / input_schema(入参 JSON Schema) / output_schema(出参 Schema)
# 注意:把对象序列化成【字符串】要用 json.dumps();json.dump() 是写入【文件流】用的,必须传 fp 参数,此处误用了 dump 导致报错。
# meta 类型为 dict[str, Any] | None:ensure_ascii=False 让中文原样显示,default=str 兜底无法序列化的值
print(f" - 工具名: {tool.name} | 描述: {tool.description} | 元数据: { json.dumps(tool.meta, ensure_ascii=False, default=str) }")
# ============ 2. 资源列表 resources/list ============
# list_resources() 列出服务端所有【静态资源】,返回 ListResourcesResult,
# 其 .resources 字段是 Resource 对象列表(Resource 用 uri 定位,与工具用 name 定位不同)。
resources = await session.list_resources()
print(f"服务端共注册 {len(resources.resources)} 个静态资源:")
for res in resources.resources:
# Resource 关键字段:uri(资源定位符) / name(名称) / mime_type(媒体类型) / description
print(f" - URI: {res.uri} | 名称: {res.name} | MIME: {res.mime_type}")
# ============ 3. 提示词列表 prompts/list ============
# list_prompts() 列出服务端注册的所有提示词,返回 ListPromptsResult,
# 其 .prompts 字段是 Prompt 对象列表(本 demo 服务端未注册提示词,故为空列表)。
prompts = await session.list_prompts()
print(f"服务端共注册 {len(prompts.prompts)} 个提示词:")
for p in prompts.prompts:
# Prompt 关键字段:name(提示词名) / description(描述) / arguments(模板参数定义列表)
print(f" - 提示词名: {p.name} | 描述: {p.description}")
# ============ 4. 资源模板列表 resources/templates/list ============
# list_resource_templates() 列出服务端所有【模板资源】(URI 带 {name} 等占位符,可动态匹配),
# 返回 ListResourceTemplatesResult,其 .resource_templates 字段是 ResourceTemplate 对象列表。
templates = await session.list_resource_templates()
print(f"服务端共注册 {len(templates.resource_templates)} 个资源模板:")
for tpl in templates.resource_templates:
# ResourceTemplate 关键字段:uri_template(带占位符的模板 URI,如 "greeting://{name}") / name / mime_type
print(f" - 模板URI: {tpl.uri_template} | 名称: {tpl.name} | MIME: {tpl.mime_type}")
# ============ 5. 调用工具 tools/call ============
# call_tool 方法的第1个参数是工具名,第2个参数是工具参数(dict,键=参数名,值=参数值)
result = await session.call_tool("add", {"a": 3, "b": 4})
# CallToolResult 关键字段:content(内容块列表,每块可能是 TextContent/ImageContent 等)、
# is_error(布尔值,标记服务端执行该工具时是否出错)、result_type(结果类型标记)、meta(元数据字典,可为 None)
# meta 用 json.dumps 序列化(json.dump 需 fp 参数,不能用于 f-string 打印)
print(f"result.is_error: {result.is_error} | result.result_type: {result.result_type} | result.meta: {json.dumps(result.meta, ensure_ascii=False, default=str)}")
for content in result.content:
# TextContent 有 .text 属性;若为图片等其他类型则没有 text,用 hasattr 防御性判断
if hasattr(content, "text"):
print(f"调用工具 add(3,4) 结果: {content.text}")
# ============ 6. 读取资源 resources/read ============
# read_resource() 必须传 uri 参数(不能省略),且要区分两种资源:
# - 静态资源:直接用原 URI,如 "config://app"
# - 模板资源:把 URI 模板中的 {name} 占位符替换成实际值,如 "greeting://world"
# 返回 ReadResourceResult,其 .contents 字段是内容块列表(TextResourceContents 有 .uri/.text/.mime_type)。
cfg = await session.read_resource("config://app")
for content in cfg.contents:
print(f"读取静态资源 {content.uri}: {content.text}")
greeting = await session.read_resource("greeting://world")
for content in greeting.contents:
print(f"读取模板资源 {content.uri}: {content.text}")
# ============ 7. 获取提示词 prompts/get ============
# get_prompt() 必须传提示词 name(本 demo 服务端未注册任何提示词),
# 所以该请求会被服务端以 JSON-RPC 错误拒绝(如 -32602 Invalid params / 找不到该提示词),
# 这里用 try/except 捕获 MCPError 演示"请求失败时的容错写法",保证主流程不被中断。
try:
prompt = await session.get_prompt(name="weather_prompt", arguments={"city": "成都"}) # arguments : 可选入参
print(f"获取提示词成功: {prompt}")
except MCPError as e:
print(f"获取提示词失败(服务端未定义该提示词,属预期行为): {e}")
# ============ 8. 心跳检测 ping ============
# send_ping() 不带参数,发送 ping 请求,返回 EmptyResult;
# 用于探测客户端与服务端之间的连接是否仍然存活(服务端无需实现具体业务逻辑即可响应)。
await session.send_ping()
print("Ping 心跳检测通过,连接存活")
# ============ 9. 协议发现 server/discover ============
# send_discover() 在 mcp 2.x 中【必须传入协议版本号】参数(原代码漏传了),
# 发送 server/discover 请求,返回服务端 discover 的原始响应 dict,
# 内含协议版本协商、服务端能力(capabilities)、serverInfo 等信息。
# 说明:send_discover 是"原始探测"接口;SDK 还提供更高层的 session.discover()(自动协商+采用结果)。
disc = await session.send_discover(LATEST_MODERN_VERSION)
print(f"Discover 响应: {disc}")
if __name__ == "__main__":
try:
asyncio.run(main())
except Exception as e:
# 统一错误处理:main() 内任何异常(含被 anyio 包成 ExceptionGroup 的握手/连接错误)都会走到这里,
# 先拆开异常组拿到最内层真实异常,再区分常见问题给出可操作提示,避免直接抛一长串晦涩 traceback
real = unwrap_error(e)
if isinstance(real, MCPError):
# 服务端确实响应了,但返回了 JSON-RPC 错误(如握手版本不兼容 -32022、路径不存在 404 合成的错误等)
print(f"[错误] 服务端返回错误响应: code={real.code!r} message={real.message!r} data={real.data!r}")
print(" 排查建议:")
print(" 1) 确认 8000 端口上跑的服务端 mcp 版本与客户端一致(均应为 2.x),旧版 mcp 服务端无法完成 2025-11-25 握手;")
print(" 2) 确认服务端 URL 路径为 /mcp(streamable_http_app 默认挂载点);")
print(" 3) 若 8000 端口被残留进程占用,先结束旧进程再重启服务端。")
elif is_connect_error(real):
# 连不上服务端:多半是服务端没启动,或启动失败、端口被占用(含 httpx2.ConnectError 等底层连接错误)
print(f"[错误] 无法连接服务端: {type(real).__name__}: {real}")
print(" 排查建议: 请先在本目录启动服务端 → python streamable_http_mcp_server.py ,确认 8000 端口正常监听后再运行本客户端。")
else:
# 其他未知异常:原样展示类型与消息
print(f"[错误] {type(real).__name__}: {real}")
out:
> python.exe H:\Local\study-python\mcps\streamable_http_mcp_client.py 服务端共注册 1 个工具: - 工具名: add | 描述: 两个整数相加 | 元数据: null 服务端共注册 1 个静态资源: - URI: config://app | 名称: get_config | MIME: text/plain 服务端共注册 1 个提示词: - 提示词名: weather_prompt | 描述: 提供天气查询的对话模板 服务端共注册 1 个资源模板: - 模板URI: greeting://{name} | 名称: get_greeting | MIME: text/plain result.is_error: False | result.result_type: complete | result.meta: null 调用工具 add(3,4) 结果: 7 读取静态资源 config://app: version=1.0, name=demo-mcps-server 读取模板资源 greeting://world: Hello, world! 获取提示词成功: meta=None description='提供天气查询的对话模板' messages=[PromptMessage(role='user', content=TextContent(type='text', text='请帮我查询成都的天气情况,并提供详细的天气信息。', annotations=None, meta=None))] result_type='complete' Ping 心跳检测通过,连接存活 Discover 响应: {'cacheScope': 'private', 'capabilities': {'prompts': {'listChanged': True}, 'resources': {'listChanged': True, 'subscribe': True}, 'tools': {'listChanged': True}}, 'resultType': 'complete', 'supportedVersions': ['2026-07-28'], 'ttlMs': 0, '_meta': {'io.modelcontextprotocol/serverInfo': {'name': 'demo-mcps-server', 'version': 'v1.0.2'}}}
方式2:使用 curl 手动测试(单次 JSON 响应)
tools/call
curl -X POST http://127.0.0.1:8000/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: add" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {"a": 3, "b": 4},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28"
, "io.modelcontextprotocol/clientCapabilities": {}
}
}
}'
-
"io.modelcontextprotocol/clientCapabilities": {}: 表示客户端无特殊能力- 避免响应报错:
{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"params._meta is missing the required envelope key(s): io.modelcontextprotocol/clientCapabilities"}}
- 避免响应报错:
-
params.name: 具体的工具名。它是在 server 端用@mcp.tool()注册的函数名。看你的代码: -
method::JSON-RPC方法名(协议固定)。它是 MCP 协议定义的接口类别,值来自MCP协议规范,系固定的枚举,不是你自己起的:
| method | 含义 |
|---|---|
| initialize | 握手初始化(建连时调用一次) |
| tools/list | 列出服务端所有工具 |
| tools/call | 调用某个工具 |
| resources/read | 读取某个资源 |
| prompts/get | 获取某个提示词模板 |
- output:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"text": "7",
"type": "text"
}
],
"isError": false,
"resultType": "complete",
"structuredContent": {
"result": 7
},
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "demo-mcps-server",
"version": ""
}
}
}
}
tools/list
curl -X POST http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/list" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'
out:
{ "jsonrpc": "2.0", "id": 1, "result": { "cacheScope": "private", "resultType": "complete", "tools": [ { "description": "两个整数相加", "inputSchema": { "type": "object", "properties": { "a": { "title": "A", "type": "integer" }, "b": { "title": "B", "type": "integer" } }, "required": [ "a", "b" ], "title": "addArguments" }, "name": "add", "outputSchema": { "properties": { "result": { "title": "Result", "type": "integer" } }, "required": [ "result" ], "title": "addOutput", "type": "object" } } ], "ttlMs": 0, "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "demo-mcps-server", "version": "v1.0.2" } } } }
resources/list
# resources/list
curl -X POST http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: resources/list" \
-d '{"jsonrpc":"2.0","id":1,"method":"resources/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
out:
{ "jsonrpc": "2.0", "id": 1, "result": { "cacheScope": "private", "resources": [ { "description": "应用配置信息", "mimeType": "text/plain", "name": "get_config", "uri": "config://app" } ], "resultType": "complete", "ttlMs": 0, "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "demo-mcps-server", "version": "v1.0.2" } } } }
resources/read
curl -X POST http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: resources/read" \
-H "Mcp-Name: config://app" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "resources/read",
"params": {
"uri": "config://app",
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'
out:
{ "jsonrpc": "2.0", "id": 1, "result": { "cacheScope": "private", "contents": [ { "mimeType": "text/plain", "text": "version=1.0, name=demo-mcps-server", "uri": "config://app" } ], "resultType": "complete", "ttlMs": 0, "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "demo-mcps-server", "version": "v1.0.2" } } } }
- 请求另一资源
$ curl -X POST http://127.0.0.1:8000/mcp \
> -H "Content-Type: application/json" \
> -H "Accept: application/json, text/event-stream" \
> -H "MCP-Protocol-Version: 2026-07-28" \
> -H "Mcp-Method: resources/read" \
> -H "Mcp-Name: greeting://world-v3" \
> -d '{
> "jsonrpc": "2.0",
> "id": 1,
> "method": "resources/read",
> "params": {
> "uri": "greeting://world-v3",
> "_meta": {
> "io.modelcontextprotocol/protocolVersion": "2026-07-28",
> "io.modelcontextprotocol/clientCapabilities": {}
> }
> }
> }'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 556 100 279 100 277 272k 270k --:--:-- --:--:-- --:--:-- 542k
{"jsonrpc":"2.0","id":1,"result":{"cacheScope":"private","contents":[{"mimeType":"text/plain","text":"Hello, world-v3!","uri":"greeting://world-v3"}],"resultType":"complete","ttlMs":0,"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"demo-mcps-server","version":"v1.0.2"}}}}
3.1.3 Windows 部署注意事项
- 端口占用:Windows 上 8000 端口可能被其他服务占用,启动前可用
netstat -ano | findstr :8000检查。 - 防火墙:若需局域网访问,需在 Windows 防火墙中放行对应端口;本地开发建议绑定
127.0.0.1。 - 异步运行:Windows 上 Python 异步默认使用 ProactorEventLoop,uvicorn 可正常工作;若使用
asyncio.run()无需额外配置。 - 编码:确保控制台使用 UTF-8(
chcp 65001),避免中文工具名/描述乱码。
3.1.4 Linux 部署注意事项
- systemd 托管:生产环境建议用 systemd 管理进程,配置
Restart=always。 - 反向代理:通过 nginx 反向代理时,需配置
proxy_buffering off或依赖服务器发送的X-Accel-Buffering: no头,确保 SSE 实时推送。 - 文件描述符:长连接较多时需调大
ulimit -n。
3.2 调用 MCP Server 的场景
CASE Codex 上配置、使用 MCP Server (必读)
-
step0 启动 MCP Server
-
step1 配置 Codex 的 MCP Server
1
设置-插件-MCP-添加MCP服务器
2 填写:1、url(例如:http://127.0.0.1:8000/mcp);2、Bearer 令牌(或配置环境变量MCP_BEARER_TOKEN)
3 保存即可

对应的配置文件的配置内容:
~/.codex/config.toml(仅供参考)
[mcp_servers]
[mcp_servers.local-mcp-demo]
enabled = true
url = "http://127.0.0.1:8000/mcp"
bearer_token_env_var = "Shy3l71B1R-JJz3KY3B1kIXSpg9oAUfUV8NCXL-oPzU"
[mcp_servers.node_repl]
args = []
command = 'C:\Users\Johnny\AppData\Local\OpenAI\Codex\runtimes\cua_node\950613ca46815e82\bin\node_repl.exe'
startup_timeout_sec = 120
[mcp_servers.node_repl.env]
NODE_REPL_NATIVE_PIPE_CONNECT_TIMEOUT_MS = "1000"
NODE_REPL_NODE_MODULE_DIRS = 'C:\Users\xxx\AppData\Local\OpenAI\Codex\runtimes\cua_node\950613ca46815e82\bin\node_modules'
NODE_REPL_NODE_PATH = 'C:\Users\xxx\AppData\Local\OpenAI\Codex\runtimes\cua_node\950613ca46815e82\bin\node.exe'
NODE_REPL_TRUSTED_CODE_PATHS = 'C:\Users\xxx\.codex;C:\Users\xxx\AppData\Local\OpenAI\Codex\runtimes\cua_node\950613ca46815e82\bin\node_modules'
CODEX_HOME = 'C:\Users\xxx\.codex'
- step2 在 Codex 的对话或工作任务中调用 mcp


- step3 在 MCP Server 端,亦可看见请求日志
未配置 MCP_SERVER_TOKEN,已自动生成:Shy3l71B1R-JJz3KY3B1kIXSpg9oAUfUV8NCXL-oPzU
INFO: Started server process [31820]
INFO: Waiting for application startup.
StreamableHTTP session manager started
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Created new transport with session ID: 9a47c00adf304d5185004f095dad2728
INFO: 127.0.0.1:56907 - "POST /mcp HTTP/1.1" 200 OK
INFO: 127.0.0.1:57077 - "POST /mcp HTTP/1.1" 200 OK
INFO: 127.0.0.1:57078 - "POST /mcp HTTP/1.1" 200 OK
INFO: 127.0.0.1:57079 - "POST /mcp HTTP/1.1" 200 OK
INFO: 127.0.0.1:57177 - "POST /mcp HTTP/1.1" 200 OK
INFO: 127.0.0.1:57539 - "GET /mcp HTTP/1.1" 401 Unauthorized
INFO: 127.0.0.1:57539 - "GET /favicon.ico HTTP/1.1" 401 Unauthorized
Created new transport with session ID: 37cb3617c3064ef9ac5b07bea31e2917
INFO: 127.0.0.1:58288 - "POST /mcp HTTP/1.1" 200 OK
INFO: 127.0.0.1:58708 - "POST /mcp HTTP/1.1" 200 OK
Z FAQ for Streamable HTTP MCP
Q: Streamable HTTP 与旧版 HTTP+SSE 传输的核心区别是什么?*
旧版 HTTP+SSE 使用两个端点:POST 端点发送客户端→服务器消息,独立的 GET SSE 端点接收服务器→客户端消息,需要维护会话关联。Streamable HTTP 使用单一端点:每个 POST 请求的响应本身可以是 JSON 或 SSE 流,服务器→客户端通知内嵌在对应请求的响应流中,无需独立长连接和会话管理。
Q: MCP 2026-07-28 版本为什么移除了 Mcp-Session-Id 和 GET 流端点?*
移除会话机制是为了向无状态请求模型演进,使服务器可以水平扩展、部署在 Serverless 环境中而无需维护会话状态。GET 流端点的功能被 subscriptions/listen 请求的响应 SSE 流取代,进一步统一到单端点架构。
Q: 服务器如何决定返回 JSON 还是 SSE 流?
服务器按请求自主选择。对于简单快速的工具调用,返回单次 application/json 即可;对于长耗时、需要推送进度或日志的工具,返回 text/event-stream 流。客户端必须同时支持两种响应格式(通过 Accept: application/json, text/event-stream 头声明)。
Q: Streamable HTTP 支持断点续传吗?*
2026-07-28 版本明确不支持 Last-Event-ID 断点续传。请求作用域的 SSE 流断开后需重新发起工具调用;subscriptions/listen 长订阅流断开后需客户端重新发送订阅请求。早期版本(2025-03-26 ~ 2025-11-25)曾支持 Last-Event-ID,但已被移除。
Q: 如何在 AWS Lambda / Cloudflare Workers 上部署 Streamable HTTP 服务器?
Serverless 环境不支持长生命周期 SSE 流,因此应仅使用单次 JSON 响应模式,避免 subscriptions/listen 和请求级 SSE 流。AWS 官方提供了 sample-serverless-mcp-server 模板,Cloudflare Workers 可通过 hayate-mcp 等项目部署。
Q: 客户端如何取消一个正在执行的工具调用?
在 Streamable HTTP 中,关闭该请求的 SSE 响应流即为取消信号。服务器检测到流关闭后应尽快停止工作,且不得再发送该请求的任何消息。无需发送 notifications/cancelled JSON-RPC 消息(该通知仅用于 stdio 传输)。
Q: Mcp-Method、Mcp-Name、Mcp-Param-* 这些头有什么用?
这些头将 JSON-RPC body 中的关键字段镜像到 HTTP 头,使中间件(API 网关、负载均衡、APM 工具)无需解析 JSON body 即可按方法名、工具名、参数值进行路由、限流、审计和日志记录。这是 Streamable HTTP 面向企业基础设施可观测性的重要设计。
Q: Streamable HTTP 可以用于浏览器端直接连接 MCP 服务器吗?*
- 可以,但需要服务器正确配置 CORS(允许跨域请求、暴露自定义头)。浏览器端可使用官方 TypeScript SDK 的
StreamableHTTPClientTransport。 - 注意浏览器的
EventSourceAPI 不支持自定义 POST body 和头,因此浏览器客户端通常使用fetch()+ReadableStream手动解析 SSE。
Y 推荐文献
MCP 支持3种通信模式: Stdio(次主流) / SSE(逐渐被废) / Streamble HTTP(主流)
-
[HTTP/JS/Python] SSE(Server Send Events) :服务器 => 浏览器的消息推送解决方案 - 博客园/千千寰宇
-
MCP Streamable HTTP 规范(2026-07-28) - modelcontextprotocol.io
-
MCP Transports Explained: stdio vs Streamable HTTP - chatforest.com
X 参考文献
- Streamable HTTP Specification - modelcontextprotocol.io
- Streamable HTTP (draft) - modelcontextprotocol.io
- MCP Python SDK - GitHub
- MCP Simple StreamableHttp Server Example - GitHub
- MCP Streamable HTTP Python & TypeScript Examples - invariantlabs-ai
- MCP Transports Explained - chatforest.com
- MCP 规范完整中译稿(2025-03-26) - CSDN
- MCP 2026 ロードマップ深読み - Qiita
- MCP Server Implementation Reference - hidekazu-konishi.com
- Spring AI Alibaba Streamable HTTP 方案 - 阿里云
- AWS Lambda Streamable HTTP MCP Server - AWS 官方博客
- OpenAI Agents Python SDK MCP 文档
- Anthropic MCP Connector 文档
浙公网安备 33010602011771号