小白 / Xiaobai
开发者 · 产品构建者
持续构建 AI 工程系统、开发者工具与长期数字资产。
关于作者与 XBSTACK →
MCP Streamable HTTP 实战:从本地 stdio Server 到远程 MCP 服务部署
MCP Streamable HTTP 怎么部署?本文按 MCP 2026-07-28 与官方 Python SDK 重写远程部署流程,覆盖 streamable-http、stateless 请求、反向代理、认证、Origin、超时、业务状态和 legacy Session 兼容。
如果你现在新部署远程 MCP Server,不要再从 transport="sse"、/sse + /messages 或“每个 Client 一个 Mcp-Session-Id”开始。当前 MCP 2026-07-28 已把核心协议改成 stateless;官方 Python SDK 对新服务推荐 streamable-http,同一个 /mcp 端点处理远程请求,现代请求自身携带协议版本、Client 信息与能力元数据。SSE transport 仍可能存在于 SDK 中,但它是旧客户端兼容路径,不应该成为 2026 新架构的默认教程。
这篇只解决一件事:如何把本地 stdio MCP Server 迁移成可部署、可认证、可扩展、可排障的 Streamable HTTP 服务。OAuth 的完整授权链路另见 MCP OAuth 认证实战,协议 Session 迁移另见 MCP 2026-07-28 stateless 迁移指南。
先分清:stdio、legacy SSE、Streamable HTTP
stdio 仍然适合本机工具:宿主应用启动一个子进程,通过 stdin/stdout 发送 MCP 消息,进程权限、文件权限和本机用户边界天然成为一部分安全边界。
SSE transport 是旧 HTTP 传输。它使用独立的 SSE 与消息端点,官方 Python SDK 当前文档明确提示:**SSE 已被 Streamable HTTP 取代,新项目不要再基于它构建。**所以旧教程中的:
mcp.run(transport="sse")
以及 /sse、/messages 两个端点,不应继续当成 Streamable HTTP 示例。
新的远程入口是:
https://mcp.example.com/mcp
客户端把远程 MCP 请求发到这个端点。响应可以是普通 JSON,也可以根据协议/SDK 能力使用流式响应。部署层不需要自己再发明一套“消息 POST 端点 + SSE 订阅端点”。
2026-07-28 最大变化:远程请求不再依赖协议 Session
legacy 2025-era Streamable HTTP 常见流程是:
initialize
→ notifications/initialized
→ server 分配/接受 Mcp-Session-Id
→ 后续请求携带 Session ID
MCP 2026-07-28 已移除这套核心握手和协议级 Mcp-Session-Id。现代 Client 可以先调用 server/discover,也可以直接发送第一条业务请求;每个请求携带必要的协议、Client 与能力元数据,因此同一请求可以落到负载均衡后的任意兼容实例。
这件事对部署最重要的影响不是“少一个 Header”,而是:
- 不再要求 sticky session 才能维持 MCP 协议状态;
- Server 不能默认从几分钟前的 Session 内存读取 Client capability;
- 多副本部署更容易做成无共享协议 Session;
- 业务状态必须和协议生命周期分开设计。
**注意:协议 stateless 不等于你的应用必须 stateless。**审批草稿、购物篮、上传任务、分页游标、长任务等仍然需要状态,只是这些状态应该是明确的业务对象或受保护的 state handle,而不是偷偷塞进协议 Session 内存。
最小可运行 Python Server
官方 Python SDK 当前最直接的远程写法是 streamable-http:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(
"RemoteToolHub",
stateless_http=True,
json_response=True,
)
@mcp.tool()
def get_remote_status() -> dict:
return {"status": "ok"}
if __name__ == "__main__":
mcp.run(
transport="streamable-http",
host="127.0.0.1",
port=8000,
)
本地先验证:
http://127.0.0.1:8000/mcp
再用当前 MCP Inspector 或兼容 2026-07-28 的 Client 连接。不要一上来就挂 Nginx/Cloudflare;先确认不经过代理时协议能跑通,再引入网络层变量。
如果需要接进现有 FastAPI/Starlette,可以使用 SDK 提供的 streamable_http_app() 作为 ASGI 应用挂载。挂载时要特别检查 host app 的 lifespan;如果跳过 SDK 要求的 lifespan/session manager 初始化,第一条请求就可能失败。这里的 session_manager 是 SDK 运行时对象名称,不意味着 2026-07-28 又恢复了协议级 Mcp-Session-Id。
生产部署拓扑:先把认证和网络边界放在 MCP 前面
一个更现实的最小拓扑是:
MCP Client
↓ HTTPS
Reverse Proxy / Gateway
↓ authenticated request
MCP Streamable HTTP app
↓ least-privilege credentials
Tools / Database / Filesystem / SaaS API
远程 MCP Server 不应该因为“只有 AI Client 会调用”就裸露在公网。至少要明确:
- TLS 在哪里终止;
- 谁负责认证;
- Token / OAuth scope 如何映射到具体 Tool;
- Origin / Host 如何校验;
- 是否允许浏览器类 Client;
- 哪些
Mcp-*Header 必须穿过代理; - 请求体、并发、超时与速率限制由谁控制;
- Tool 使用什么下游凭据;
- 审计日志如何关联 principal、tool、resource 与 trace。
认证和 Tool 授权也不能混成一个概念。Client 拿到合法 access token,只说明“它是谁/允许进入什么资源服务器”,不代表它对所有 Tool 和所有资源都有执行权限。高风险写操作仍应在 Tool Gateway 做资源级授权、Schema 校验、幂等和必要的人工审批。
反向代理到底要配置什么
旧文章最容易把所有 MCP HTTP 问题归咎于 proxy_buffering。实际应该按症状分层:
1. 连接直接失败:先查 Host / Origin / Auth
当前 SDK 和规范都强调远程 Transport 的 Host/Origin 安全边界。出现 401、403、421 一类错误时,先检查:
- 外部域名是否在允许列表;
- 代理有没有改写 Host;
Origin是否符合 Server policy;Authorization是否被代理丢失;MCP-Protocol-Version和必要的Mcp-*Header 是否保留。
2. 普通 JSON 正常,流式结果卡住:再查 buffering / timeout
只有当 Server 确实返回 text/event-stream 或保持响应流时,buffering 才成为关键变量。此时要验证:
- 代理是否缓存/聚合流式响应;
- idle/read timeout 是否短于正常工具执行时间;
- CDN 是否支持该响应模式;
- 客户端断线是否能取消后端任务;
- 长时间运行是否应该改成 MCP task/应用自己的异步任务,而不是无限拉长一个 HTTP 请求。
不要写“80% MCP 故障都来自 buffering”——没有站内实验或公开数据能支持这个比例。
3. 大工具结果失败:查 body/response limit,而不是只拉长 timeout
大 JSON、图片、文档和日志可能触发:
- gateway request/response size limit;
- upstream memory pressure;
- Client 自身 result limit;
- 模型上下文浪费。
优先把 Tool 设计成分页、过滤、搜索、摘要和显式附件/资源引用,不要默认把完整数据一次塞回模型。
2026 架构里“会话隔离”应该怎么做
不要把用户隔离继续写成:
/sse 握手 → 生成 session_id → 全局 dict[session_id]
现代远程 Server 应该先从可信身份建立 principal,再让业务状态显式归属到 principal/tenant/resource:
access token
→ principal / tenant
→ tool authorization
→ business handle / record id
→ external durable store
例如审批流:
approval_id = apr_123
owner = tenant_a:user_42
state = pending
expires_at = ...
下一次请求带 approval_id 时,Server 必须重新检查 owner、状态和权限,而不是只因为“这个 ID 曾经存在”就信任它。若使用 2026 SDK 提供的 requestState/state handle,也应按官方要求把它视作不可信输入,做完整性保护、绑定 principal/方法并设置过期时间。
多副本部署:什么时候真的可以 round-robin
MCP 2026-07-28 的协议请求不依赖协议级 Session,因此协议层可以由任意兼容实例处理。但你的 Tool 实现仍可能破坏这一点,例如:
- 把上传文件只放在当前 Pod
/tmp; - 把审批状态保存在进程全局 dict;
- 把 OAuth token refresh 状态只放内存;
- Tool A 创建临时资源后,Tool B 默认去当前实例查;
- 后台任务与结果没有 durable task store。
所以“stateless MCP”只解决协议层的一部分横向扩展问题。真正多副本还要审计应用状态、缓存、任务、凭据和外部副作用。
legacy Client 怎么办
官方 Python SDK v2 的设计之一就是同时服务 2025-era 和 2026-era Client:现代 Client 走新流程,旧 Client 仍可以使用 initialize/session 流程。你在生产迁移时应明确选择:
- 只支持 2026-07-28:架构最简单,但老客户端可能无法连接;
- SDK 双代兼容:同一部署服务新旧 Client,但监控和测试要区分两种生命周期;
- Gateway 分流:大型平台可按协议版本路由到不同兼容层。
不要让旧客户端的 Session 约束反向决定整个新架构。最关键的回归测试是:同一 Tool 在 modern path 和 legacy path 都能得到正确授权、参数校验和业务结果。
上线前验证清单
远程部署完成后至少验证:
- 不经过代理时,当前 MCP Inspector / Client 能连接
/mcp; - 经过代理后,Host、Origin、Authorization、协议 Header 没被错误改写;
- 未认证请求被拒绝;
- 合法身份只能访问被授权 Tool / Resource;
- 高风险 Tool 不因为模型选中了函数就自动获权;
- 429、5xx、timeout 的重试不会造成重复写;
- 流式响应经过代理时不会被错误缓存或过早断开;
- 大结果有分页/上限,不靠无限增加 body limit;
- 多副本下不存在只保存在某个 Pod 内存的关键业务状态;
- modern 2026-07-28 和需要支持的 legacy Client 分别跑自动测试。
最终判断
如果工具只服务本机 IDE,stdio 仍然可能是最简单、最小暴露面的方案;远程并不天然比本地高级。当你需要跨设备、团队共享、统一鉴权、网关审计或水平扩展时,再使用 Streamable HTTP。
真正的 2026 迁移重点不是“把 stdio 改成 HTTP”,而是同时完成三次解耦:
- 传输:本地进程 → 远程
/mcp; - 协议状态:legacy Session → 2026-07-28 每请求自描述;
- 业务状态:进程内临时变量 → 有身份、有权限、有过期策略的外部状态。
做到这三点,Streamable HTTP 才真正具备生产部署价值。
继续阅读
- MCP 2026-07-28:initialize 与 Mcp-Session-Id 移除后的迁移
- MCP OAuth 认证实战
- MCP 安全最佳实践
- MCP JSON-RPC Parse Error 排查
- MCP Protocol Deep Dive
继续按 MCP 生产部署路径读,而不是堆 guide / tutorial
MCP 内容统一按协议理解、本地 Server、远程部署、OAuth、安全治理、stdio/JSON-RPC 排障和工具对比来承接,避免站内关键词互相抢。
继续阅读
返回专题 →AI 工程周报
只发真正改变工程判断的变化、故障、实验和新资产。
参与讨论
问题、验证与勘误
登录后可发表评论。所有新评论先进入审核;审核期间仅评论者本人和管理员可见,通过后才公开。