在之前介绍的几篇文章中,关于MCP server的实现都是基于 STDIO 的方式进行通信,这种方式是靠本地进程间的标准的输入输出协议实现通信的,但是通常我们现有的微服务都是web端的应用,STDIO 的方式在这种场景下并不适用,因此,MCP协议提供了另一种通信方式,即SSE (Server-Sent Events) 传输方式。 MCP的 SSE 传输是一种基于 HTTP 的通信机制,主要用于实现服务器到客户端的流式传输。下面我们提供一个简单的示例来说明其工作原理。
实现一个简单的 SSE 传输的 MCP Server
下面是一个用go语言的 mcp-go 框架实现的一个简单的示例,它实现了一个 echo
工具,该工具接受一个字符串参数,并将该字符串原封不动地返回给客户端。
go
func NewMCPServer() *MCPServer {
mcpServer := server.NewMCPServer(
"example-server",
"1.0.0",
server.WithResourceCapabilities(true, true),
server.WithPromptCapabilities(true),
server.WithToolCapabilities(true),
)
// Add echo tool
mcpServer.AddTool(mcp.NewTool("echo",
mcp.WithDescription("Echo back the input"),
mcp.WithString("message",
mcp.Required(),
mcp.Description("Message to echo back"),
),
), echoHandler)
return &MCPServer{
server: mcpServer,
}
}
func main() {
s := NewMCPServer()
sseServer := s.ServeSSE("localhost:8080")
log.Printf("SSE server listening on :8080")
if err := sseServer.Start(":8080"); err != nil {
log.Fatalf("Server error: %v", err)
}
}
func (s *MCPServer) ServeSSE(addr string) *server.SSEServer {
return server.NewSSEServer(s.server,
server.WithBaseURL(fmt.Sprintf("http://%s", addr)),
)
}
但是由于框架封装了对外暴露的 endpoint ,所以我们需要点击NewSSEServer方法去看内部的实现,可以看到框架默认帮我们封装了两个 endpoint,如下所示:
go
s := &SSEServer{
server: server,
sseEndpoint: "/sse",
messageEndpoint: "/message",
useFullURLForMessageEndpoint: true,
keepAlive: false,
keepAliveInterval: 10 * time.Second,
}
因此可以知道服务端与客户端的通信是基于这两个接口实现的。接下来我们再实现个 MCP client,来看看它具体是如何与服务端进行通信的。
实现一个简单的 SSE 传输的 MCP Client
下面代码是一段与上面实现的服务端交互的 mcp client,它通过 SSE 协议与上面实现的服务端进行通信。
go
func main() {
ctx := context.Background()
client, err := client.NewSSEMCPClient("http://localhost:8080/sse")
if err != nil {
log.Fatalf("Failed to create SSE MCP client: %v", err)
}
err = client.Start(ctx)
if err != nil {
log.Fatalf("Failed to start SSE MCP client: %v", err)
}
// Initialize
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "test-client",
Version: "1.0.0",
}
_, err = client.Initialize(ctx, initRequest)
if err != nil {
log.Fatalf("Failed to Initialize SSE MCP client: %v", err)
}
request := mcp.CallToolRequest{
Request: mcp.Request{
Method: "tools/call",
},
}
arguments := map[string]interface{}{
"message": "Hello SSE!",
}
request.Params.Name = "echo"
request.Params.Arguments = arguments
// Test echo tool
result, err := client.CallTool(context.Background(), request)
if err != nil {
return
}
textContent := result.Content[0].(mcp.TextContent)
fmt.Println(textContent.Text)
time.Sleep(100 * time.Second)
}
服务启动后,就会打印出 Echo: Hello SSE!,接下来我们仔细看看它是如何实现的。
首先点击进入 client.Start(ctx) 方法,可以看到客户端会向服务端发送一个GET请求,请求建立SSE连接。 这段代码主要有两个作用:
- 通过 get 请求建立 sse 连接
- 异步读取 sse 端口收到的数据
go
func (c *SSEMCPClient) Start(ctx context.Context) error {
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL.String(), nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Accept", "text/event-stream")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Connection", "keep-alive")
for k, v := range c.headers {
req.Header.Set(k, v)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to connect to SSE stream: %w", err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
go c.readSSE(resp.Body)
// Wait for the endpoint to be received
select {
case <-c.endpointChan:
// Endpoint received, proceed
case <-ctx.Done():
return fmt.Errorf("context cancelled while waiting for endpoint")
case <-time.After(30 * time.Second): // Add a timeout
return fmt.Errorf("timeout waiting for endpoint")
}
return nil
}
进入 c.readSSE(resp.Body) 方法后,可以看到服务端给客户端返回了一个message端口的url信息。

继续执行代码到了 client.Initialize(ctx, initRequest) ,其主要作用是与服务端提供的message 端口的url做连接验证,没有问题后将c.initialized = true,那么后续调用tool就不会因为没有initialized而报错了。
再往下进入 client.CallTool(context.Background(), request) 方法,可以看到此时的请求是发送到localhost:8080/message 接口的,并且是POST请求。
完成该方法的调用后 readSSE 方法就会读取到 localhost:8080/sse 返回的数据,如下图所示:
ok,通过上面的调式,你应该很清楚 SSE 模式下 MCP Client是如何与 MCP Server 通信的了。
小结
1. SSE 传输的基本架构
- SSE 端点:客户端通过向该端点发送 GET 请求建立连接,服务器通过这个连接向客户端推送消息。
- HTTP POST 端点:客户端通过向该端点发送 POST 请求向服务器发送消息。
2. 连接建立过程
- 客户端请求 :客户端向服务器的
/sse
端点发送一个 GET 请求,请求建立 SSE 连接。 - 服务器响应:服务器接收到请求后,返回一个包含消息端点地址的事件消息。这个消息端点地址通常是一个 HTTP POST 接口的地址。
- 客户端记录消息端点:客户端从服务器返回的事件消息中获取消息端点地址,并保存下来用于后续的消息发送。
3. 消息交互过程
- 客户端发送消息:客户端通过 HTTP POST 请求将消息发送到服务器的消息端点。
- 服务器处理消息:服务器接收到客户端发送的消息后,通过之前建立的 SSE 连接向客户端发送响应消息。
- 客户端接收消息:客户端从 SSE 连接中读取服务器发送的事件消息,并解析其中的数据。
上面的相关代码,我都提交到了github仓库demo_for_AI,欢迎参考。