目录
引言
SSE(Server-Sent Events)传输协议在FastMCP框架中实现了高效的双向通信机制。通过SseServerTransport类提供的两个ASGI应用,系统能够建立持久化的服务器到客户端消息推送通道,并接收客户端的指令请求。本文档深入解析该实现的核心机制,包括会话关联、路由配置和安全防护等关键方面。
SSE 传输协议核心实现
SseServerTransport类是SSE传输协议的核心实现,提供了两个ASGI应用来支持双向通信。该类通过内存流(memory object stream)机制管理客户端会话,并利用UUID作为会话标识符。
本节来源
- SseServerTransport
双向通信机制分析
连接建立与消息推送
connect_sse方法负责建立SSE连接并推送服务器消息。当客户端发起GET请求时,该方法会创建一个新的会话,并通过EventSourceResponse向客户端发送事件流。
#mermaid-svg-G4Rd0fzbkVdV5i3U {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .error-icon{fill:#552222;}#mermaid-svg-G4Rd0fzbkVdV5i3U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G4Rd0fzbkVdV5i3U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .marker.cross{stroke:#333333;}#mermaid-svg-G4Rd0fzbkVdV5i3U svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-G4Rd0fzbkVdV5i3U text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor-line{stroke:grey;}#mermaid-svg-G4Rd0fzbkVdV5i3U .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .sequenceNumber{fill:white;}#mermaid-svg-G4Rd0fzbkVdV5i3U #sequencenumber{fill:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .messageText{fill:#333;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-G4Rd0fzbkVdV5i3U .labelText,#mermaid-svg-G4Rd0fzbkVdV5i3U .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .loopText,#mermaid-svg-G4Rd0fzbkVdV5i3U .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-G4Rd0fzbkVdV5i3U .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-G4Rd0fzbkVdV5i3U .noteText,#mermaid-svg-G4Rd0fzbkVdV5i3U .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-G4Rd0fzbkVdV5i3U .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-G4Rd0fzbkVdV5i3U .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actorPopupMenu{position:absolute;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor-man circle,#mermaid-svg-G4Rd0fzbkVdV5i3U line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-G4Rd0fzbkVdV5i3U :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
"客户端"
"SseServerTransport"
"内存流"
GET /sse
验证请求头
生成UUID会话ID
创建读写内存流
存储会话写入器
发送endpoint事件
发送message事件
loop
[消息推送]
"客户端"
"SseServerTransport"
"内存流"
图源
- SseServerTransport
客户端指令接收
handle_post_message方法处理客户端通过POST请求发送的指令。该方法会验证会话ID,并将客户端消息传递给相应的处理流。
#mermaid-svg-wGaMipDlZSpHQF44 {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .error-icon{fill:#552222;}#mermaid-svg-wGaMipDlZSpHQF44 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wGaMipDlZSpHQF44 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wGaMipDlZSpHQF44 .marker.cross{stroke:#333333;}#mermaid-svg-wGaMipDlZSpHQF44 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wGaMipDlZSpHQF44 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wGaMipDlZSpHQF44 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .actor-line{stroke:grey;}#mermaid-svg-wGaMipDlZSpHQF44 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .sequenceNumber{fill:white;}#mermaid-svg-wGaMipDlZSpHQF44 #sequencenumber{fill:#333;}#mermaid-svg-wGaMipDlZSpHQF44 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .messageText{fill:#333;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wGaMipDlZSpHQF44 .labelText,#mermaid-svg-wGaMipDlZSpHQF44 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .loopText,#mermaid-svg-wGaMipDlZSpHQF44 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wGaMipDlZSpHQF44 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-wGaMipDlZSpHQF44 .noteText,#mermaid-svg-wGaMipDlZSpHQF44 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wGaMipDlZSpHQF44 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wGaMipDlZSpHQF44 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wGaMipDlZSpHQF44 .actorPopupMenu{position:absolute;}#mermaid-svg-wGaMipDlZSpHQF44 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-wGaMipDlZSpHQF44 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wGaMipDlZSpHQF44 .actor-man circle,#mermaid-svg-wGaMipDlZSpHQF44 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-wGaMipDlZSpHQF44 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
"客户端"
"SseServerTransport"
"会话写入器"
POST /messages/?session_id=…
验证请求头
解析session_id参数
查找会话写入器
验证消息格式
发送会话消息
返回202 Accepted
"客户端"
"SseServerTransport"
"会话写入器"
图源
- SseServerTransport
本节来源
- SseServerTransport
会话关联与路由机制
Session ID 机制
SseServerTransport使用UUID作为会话标识符,确保每个SSE连接都有唯一的会话ID。该ID通过查询参数session_id与客户端POST请求关联,实现双向通信的会话绑定。
session_id = uuid4()
self._read_stream_writers[session_id] = read_stream_writer
client_post_uri_data = f"{quote(full_message_path_for_client)}?session_id={session_id.hex}"
Mount Path 路由影响
mount_path参数影响SSE端点的最终路由。通过_normalize_path方法,系统将挂载路径与消息端点路径组合,生成客户端使用的完整路径。
#mermaid-svg-b6uwMwLEc2JaS3fs {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .error-icon{fill:#552222;}#mermaid-svg-b6uwMwLEc2JaS3fs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b6uwMwLEc2JaS3fs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b6uwMwLEc2JaS3fs .marker.cross{stroke:#333333;}#mermaid-svg-b6uwMwLEc2JaS3fs svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b6uwMwLEc2JaS3fs .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster-label text{fill:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster-label span{color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .label text,#mermaid-svg-b6uwMwLEc2JaS3fs span{fill:#333;color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .node rect,#mermaid-svg-b6uwMwLEc2JaS3fs .node circle,#mermaid-svg-b6uwMwLEc2JaS3fs .node ellipse,#mermaid-svg-b6uwMwLEc2JaS3fs .node polygon,#mermaid-svg-b6uwMwLEc2JaS3fs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b6uwMwLEc2JaS3fs .node .label{text-align:center;}#mermaid-svg-b6uwMwLEc2JaS3fs .node.clickable{cursor:pointer;}#mermaid-svg-b6uwMwLEc2JaS3fs .arrowheadPath{fill:#333333;}#mermaid-svg-b6uwMwLEc2JaS3fs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b6uwMwLEc2JaS3fs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b6uwMwLEc2JaS3fs .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-b6uwMwLEc2JaS3fs .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster text{fill:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster span{color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b6uwMwLEc2JaS3fs :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
开始
获取mount_path
获取message_path
normalize_path(mount_path, message_path)
组合路径
返回完整路径
图源
- SseServerTransport
本节来源
- SseServerTransport
DNS 重绑定防护安全中间件
TransportSecurityMiddleware实现了DNS重绑定攻击防护,通过验证请求头确保安全性。
安全验证流程
#mermaid-svg-M5W5KFec2gSlWqnr {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .error-icon{fill:#552222;}#mermaid-svg-M5W5KFec2gSlWqnr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M5W5KFec2gSlWqnr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M5W5KFec2gSlWqnr .marker.cross{stroke:#333333;}#mermaid-svg-M5W5KFec2gSlWqnr svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M5W5KFec2gSlWqnr .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster-label text{fill:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster-label span{color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .label text,#mermaid-svg-M5W5KFec2gSlWqnr span{fill:#333;color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .node rect,#mermaid-svg-M5W5KFec2gSlWqnr .node circle,#mermaid-svg-M5W5KFec2gSlWqnr .node ellipse,#mermaid-svg-M5W5KFec2gSlWqnr .node polygon,#mermaid-svg-M5W5KFec2gSlWqnr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M5W5KFec2gSlWqnr .node .label{text-align:center;}#mermaid-svg-M5W5KFec2gSlWqnr .node.clickable{cursor:pointer;}#mermaid-svg-M5W5KFec2gSlWqnr .arrowheadPath{fill:#333333;}#mermaid-svg-M5W5KFec2gSlWqnr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M5W5KFec2gSlWqnr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M5W5KFec2gSlWqnr .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-M5W5KFec2gSlWqnr .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster text{fill:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster span{color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M5W5KFec2gSlWqnr :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
是
失败
否
否
是
失败
失败
请求进入
是否为POST请求?
验证Content-Type
返回400错误
DNS重绑定防护启用?
通过验证
验证Host头
返回421错误
验证Origin头
返回403错误
结束
图源
- TransportSecurityMiddleware
配置选项
安全中间件支持以下配置:
- enable_dns_rebinding_protection: 启用DNS重绑定防护
- allowed_hosts: 允许的Host头值列表
- allowed_origins: 允许的Origin头值列表
本节来源
- TransportSecurityMiddleware
Web 服务器环境部署配置
Starlette 集成示例
在Starlette应用中集成SSE传输的典型配置如下:
# 创建SSE传输实例
sse = SseServerTransport("/messages/")
# 配置路由
routes = [
Route("/sse", endpoint=handle_sse, methods=["GET"]),
Mount("/messages/", app=sse.handle_post_message),
]
# 处理SSE连接
async def handle_sse(request):
async with sse.connect_sse(
request.scope, request.receive, request._send
) as streams:
await app.run(
streams[0], streams[1], app.create_initialization_options()
)
return Response() # 必须返回Response避免TypeError
FastMCP 集成示例
使用FastMCP框架的简化集成:
from mcp.server.fastmcp import FastMCP
# 创建MCP服务器
mcp = FastMCP("My App")
# 添加工具
@mcp.tool()
def hello() –> str:
return "Hello from MCP!"
# 挂载StreamableHTTP服务器
app = Starlette(
routes=[
Mount("/", app=mcp.streamable_http_app()),
]
)
本节来源
- streamable_http_basic_mounting.py
- echo.py
结论
SSE传输协议在FastMCP中的实现提供了一套完整的双向通信解决方案。通过SseServerTransport类的两个ASGI应用,系统实现了服务器消息推送和客户端指令接收的分离。会话ID机制确保了通信的关联性,而安全中间件则提供了必要的防护。在Web服务器环境中,通过简单的路由配置即可完成集成,为实时通信应用提供了高效可靠的基础设施。





