1.背景
前面我们介绍了 MCP 架构是server + client 模式,今天来研究一下Client实现方案,本文会依赖MCP Python SDK,如果Python环境还未搭建好,可以爬楼看一下,我前序文章。
Client 有两种模式,今天研究 服务器发送事件(Server-Sent Events, SSE)模式:服务器作为独立进程运行,客户端和服务器代码完全解耦,支持多个客户端随时连接和断开。
Server-Sent Events(SSE,服务器发送事件)是一种基于 HTTP 协议的技术,允许服务器向客户端单向、实时地推送数据。在 SSE 模式下,客户端通过创建一个 EventSource 对象与服务器建立持久连接,服务器则通过该连接持续发送数据流,而无需客户端反复发送请求。MCP Python SDK 使用了 Starlette 框架来实现 SSE。SSE 模式下客户端通过访问 Server 的 /messages 端点发送 JSON-RPC 调用,并通过 /sse 端点获取服务器推送的 JSON-RPC 消息。
两种模式,对比一下,方便大家选型:
特征 | stdio模式 | sse模式 |
---|---|---|
通信协议 | 标准输入输出 | 服务器发送事件 |
实时性 | 不支持实时更新 | 支持实时更新 |
架构灵活性 | 耦合,由客户端启动服务器 | 解耦,客户端可随时连接 |
适用场景 | 简单本地应用 | 交互式应用 |
配置复杂性 | 无需网络,本地命令运行 | 需要网络连接和url |
2.环境准备
python 版本:3.12.5
LLM: deepseek-chat
SDK:openai 1.63.2
3.步骤
1)MCP Server 端写一个tool
from mcp.server.fastmcp import FastMCP
from starlette.routing import Mount, Route
from mcp.server import Server
from starlette.applications import Starlette
from mcp.server.sse import SseServerTransport
from starlette.requests import Request
import uvicorn
import argparse
# Create an MCP server
mcp = FastMCP("achievement")
# Add an get score tool
@mcp.tool()
def get_score_by_name(name: str) -> str:
"""根据员工的姓名获取该员工的绩效得分"""
if name == "张三":
return "name: 张三 绩效评分: 85.9"
elif name == "李四":
return "name: 李四 绩效评分: 92.7"
else:
return "未搜到该员工的绩效"
2)创建 Starlette 新实例
def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette:
"""Create a Starlette application that can server the provied mcp server with SSE."""
sse = SseServerTransport("/messages/")
async def handle_sse(request: Request) -> None:
async with sse.connect_sse(
request.scope,
request.receive,
request._send,
) as (read_stream, write_stream):
await mcp_server.run(
read_stream,
write_stream,
mcp_server.create_initialization_options(),
)
return Starlette(
debug=debug,
routes=[
Route("/sse", endpoint=handle_sse),
Mount("/messages/", app=sse.handle_post_message),
],
)
开始创建了 SseServerTransport 对象,并指定基础路径 /messages/,用于后续管理 SSE 连接和消息传递。
之后的 handle_sse 是一个异步请求处理函数,当客户端请求建立 SSE 连接时会被调用。在该方法中利用 sse.connect_sse 方法,传入当前请求的 scope、receive 方法和 _send 方法,建立一个异步上下文管理器。
管理器会返回两个数据流,分别是 read_stream 用于读取客户端发送的数据以及 write_stream 用于向客户端发送数据。在成功建立连接后,调用 mcp_server.run 方法,并传入读取、写入流以及由 mcp_server.create_initialization_options() 生成的初始化参数。
这一过程实现了 MCP 服务器与客户端之间的实时数据交互。最后 create_starlette_app 方法返回一个新的 Starlette 应用实例,包括调试模式以及路由设置。路由设置使用 Route(“/sse”, endpoint=handle_sse) 定义 /sse 路径,当客户端访问此路径时将触发 handle_sse 函数处理 SSE 连接。
使用 Mount(“/messages/”, app=sse.handle_post_message) 将 /messages/ 路径挂载到 sse.handle_post_message 应用上,用于处理通过 POST 请求发送的消息,实现与 SSE 长连接的消息传递功能。
3)创建 MCP Server服务器实例
if __name__ == "__main__":
mcp_server = mcp._mcp_server
parser = argparse.ArgumentParser(description='Run MCP SSE-based server')
parser.add_argument('--host', default='0.0.0.0', help='Host to bind to')
parser.add_argument('--port', type=int, default=18080, help='Port to listen on')
args = parser.parse_args()
# Bind SSE request handling to MCP server
starlette_app = create_starlette_app(mcp_server, debug=True)
uvicorn.run(starlette_app, host=args.host, port=args.port)
4)run mcp server
uv run server.py
效果:
5)写SSE_client
async def connect_to_sse_server(server_url: str):
#Connect to an MCP server running with SSE transport
# Store the context managers so they stay alive
async with sse_client(url=server_url) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# List available tools to verify connection
print("Initialized SSE client...")
print("Listing tools...")
response = await session.list_tools()
tools = response.tools
print("\nConnected to server with tools:", [tool.name for tool in tools])
# test call a tool
score = await session.call_tool(name="get_score_by_name",arguments={"name": "张三"})
print("score: ", score)
6)启动时传入 MCP Server 服务URL
async def main():
if len(sys.argv) < 2:
print("Usage: uv run client.py <URL of SSE MCP server (i.e. http://localhost:8080/sse)>")
sys.exit(1)
await connect_to_sse_server(server_url=sys.argv[1])
if __name__ == "__main__":
asyncio.run(main())
7)使用uv 命令运行Client
uv run client-sse.py http://localhost:18080/sse
4.总结
1)SSE 模式,会在Server 与 Client 间建立长连接,需要注意Server端的负载