[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。

发展历程 (必读)

时间 / 协议版本 关键事件
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)、服务器→客户端请求改为 MRTRInputRequiredResult)内嵌模式;新增 Mcp-Method / Mcp-Name / x-mcp-header标准请求头
2026 年下半年 成为各大云厂商(AWS Lambda、Azure、阿里云 Higress/Spring AI Alibaba)MCP 部署的事实标准传输协议

主要功能 (必读)

  1. 单端点通信:服务器仅需暴露一个支持 POST 的 HTTP 路径(如 /mcp),客户端所有 JSON-RPC 消息均以 POST 发送。
  2. 双模式响应:对每个请求,服务器可选择返回:
    • Content-Type: application/json —— 单个 JSON-RPC 响应对象;
    • Content-Type: text/event-stream —— SSE 流,先发送请求相关通知(如 notifications/progress),最后以 JSON-RPC 响应终止流。
  3. 通知即 202:客户端发送 JSON-RPC notification 时,服务器返回 202 Accepted 无 body
  4. 请求级取消:关闭 SSE 响应流即视为取消该请求,传输层断开语义无歧义。
  5. 长生命周期变更订阅:客户端发送 subscriptions/listen 请求,其响应 SSE 流长期保持,仅推送客户端订阅的变更通知(如 notifications/tools/list_changednotifications/resources/updated)。
  6. MRTR 多轮交互:服务器需要客户端输入(sampling / elicitation / roots)时,不再在 SSE 流上发独立请求,而是返回 InputRequiredResult 内嵌 inputRequests,客户端携带 inputResponses 重试原请求
  7. HTTP 头元数据镜像MCP-Protocol-VersionMcp-MethodMcp-NameMcp-Param-* 等Header 使中间件可无 body 解析路由
  8. 安全防护:强制 Origin HTTP 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-http topic 下项目数量快速增长,涵盖 Fastify 插件、Nginx 网关、Lambda 部署模板、OAuth 扩展等。
    • 主流 AI Agent 框架(OpenAI Agents Python SDK、LangChain、Spring AI Alibaba)均已内置 MCPServerStreamableHttp 客户端。
  • 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 通信安全机制

  1. Origin 校验:服务器必须校验所有请求的 Origin 头;若存在且无效,返回 403 Forbidden
  2. 本地绑定本地运行时建议仅绑定 127.0.0.1,而非 0.0.0.0
  3. 身份认证:建议对所有连接实现身份认证(Bearer Token / OAuth 等)。
  4. 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 保存即可

image

对应的配置文件的配置内容: ~/.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

image

image

  • 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-MethodMcp-NameMcp-Param-* 这些头有什么用?

这些头将 JSON-RPC body 中的关键字段镜像到 HTTP 头,使中间件(API 网关、负载均衡、APM 工具)无需解析 JSON body 即可按方法名、工具名、参数值进行路由、限流、审计和日志记录。这是 Streamable HTTP 面向企业基础设施可观测性的重要设计。

Q: Streamable HTTP 可以用于浏览器端直接连接 MCP 服务器吗?*

  • 可以,但需要服务器正确配置 CORS(允许跨域请求、暴露自定义头)。浏览器端可使用官方 TypeScript SDK 的 StreamableHTTPClientTransport
  • 注意浏览器的 EventSource API 不支持自定义 POST body 和头,因此浏览器客户端通常使用 fetch() + ReadableStream 手动解析 SSE。

Y 推荐文献

MCP 支持3种通信模式: Stdio(次主流) / SSE(逐渐被废) / Streamble HTTP(主流)

X 参考文献

posted @ 2026-08-26 09:54  数据知音  阅读(6)  评论(0)    收藏  举报