bug复盘:MCP SSE Client 生命周期问题之context.Background() 的使用
最近在开发过程中,遇到了一个棘手的 BUG,它与 MCP (Mobile Cloud Platform) 的 SSE (Server-Sent Events) 客户端初始化及其生命周期管理息息相关。经过排查,发现问题出在 client.NewSSEMCPClient
之后调用 Start
方法时,错误地传入了当前的请求上下文 ctx
,而不是一个独立的、无超时限制的上下文。
问题现象:tools/call
结束后 SSE 连接意外中断
我们的 MCP 客户端代码大致是这样的:
case do.TransportTypeSSE:c, err := client.NewSSEMCPClient(endpoint)if err != nil {return nil, fmt.Errorf("创建 SSE 客户端失败: %w", err)}// 问题出在这里:将当前的请求 ctx 传给了 Start 方法if err := c.Start(ctx); err != nil {return nil, fmt.Errorf("启动 SSE 客户端失败: %w", err)}mcpClient = c
当我们在外部调用 mcpClient
的 tools/call
方法,并且该方法执行完毕后发现SSE 连接竟然断开了! 下次尝试复用这个 mcpClient
进行 tools/call
时,会直接失败,并可能收到类似 Session ID not found, Please reconnect
的错误。
这个问题的根源在于对 Go 语言中 context.Context
的理解和使用不当。
传入 Start(ctx)
的 ctx
是来自当前 HTTP 请求或 RPC 调用的上下文。这类上下文通常具有超时或取消机制。当这个上层请求处理完毕(无论是成功返回还是发生错误),或者请求被取消时,ctx.Done()
管道就会收到信号。
当 c.Start(ctx)
内部监听的 ctx.Done()
收到信号时,它就会认为外部调用者希望终止 SSE 连接,从而导致 SSE 连接被主动关闭。这样一来,虽然 tools/call
流程结束了,但作为底层传输通道的 SSE 连接也随之关闭,其生命周期与当前的请求流程绑定在一起。这显然不是我们希望的,因为我们想复用这个 mcpClient
来处理后续的请求。
解决这个问题的关键在于,为 SSE 客户端的 Start
方法提供一个独立的、不受特定请求生命周期影响的上下文。context.Background()
。它 是一个永不取消、永不超时的根上下文。将它传递给 c.Start()
,意味着 SSE 连接的生命周期将与应用程序的生命周期保持一致,而不会因为某个具体的 tools/call
请求结束而中断。
当需要启动一个与整个应用程序生命周期绑定、不应被上层调用取消或超时的 Goroutine 或服务时