✅SSE MCP Server如何实现重连?
在 MCP 的早期版本中,还没有出现Streamable http这种方式,主要依赖的就是本地 Stdio,远程 SSE 建立长连接。SSE 是一种典型的单向流式机制,通过一个端点持续监听服务器指令,再通过另一个端点发送消息。在稳定网络环境下,这种模式可以正常工作,但它对长连接的依赖非常强,一旦网络出现抖动、服务端重启、代理回收空闲连接,SSE 管道就会立即断开。
基于上面的问题,Spring AI MCP Client 也没有提供自动重连的能力,一旦 SSE 被中断,客户端实际上就失去了与 MCP Server 的指令通道,工具虽然注册着,但不再响应任何调用,整个智能体就会陷入无工具状态。
客户端无法自行恢复工具能力,只能重新初始化 MCP Client 才能重新建立会话。对于需要长时间稳定运行的企业级智能体系统,这明显无法满足可靠性要求。网络波动在生产环境中不可避免,而服务端升级重启也属于常态,如果 MCP Client 缺乏自愈能力,那么系统的工具链随时可能失效,导致业务流程意外中断。因此,在 SSE 模式下如何实现可靠的重连能力,成为保障智能体系统可用性的关键。
问题复现
接下来,我给大家演示一下这个问题现象,让大家能够直观的感受到,做这个事情的必要性。
我们先启动MCP Server SSE的项目,端口8003:

然后启动MCP Client,端口8001,代码和前面章节中的一样,通过手动方式,将mcp server 注入到了chatclient之中。

1 2 3 4 5 6
| HttpClientSseClientTransport transport = HttpClientSseClientTransport.builder("http://127.0.0.1:8003").sseEndpo int("/sse").build();McpSyncClient sseClient = McpClient.sync(transport) .clientInfo(new io.modelcontextprotocol.spec.McpSchema.Implementation("sse-client", "1.0")) .requestTimeout(Duration.ofSeconds(10)) .build();sseClient.initialize(); List<McpSyncClient> clients = List.of(sseClient); SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder() .mcpClients(clients) .build(); ToolCallback[] callbacks = provider.getToolCallbacks(); this.chatClient = ChatClient.builder(chatModel) .defaultToolCallbacks(callbacks) .defaultTools() .build();
|
连接成功后,我们尝试访问一下,看能不能调用工具:

接下来,问题来了,我现在MCP Server挂了,我们直接断开8003这个项目,然后就可以看到8001这边立马就会产生了报错,这个报错就表明,我们已经连不上MCP Server了。

尝试再访问下接口,我们可以看到工具已经没法用了:

然后我们再恢复MCP Server,重新启动8003端口,但是我们的MCP Client,依然无法感知重连上MCP Server,工具调用依然失败!这在生产环境肯定是没法接受的效果,我不可能为了这种情况,再去手动重启项目,重新初始化,这样根本就没法用。所以,MCP Server的重连机制是不可获取的!


重连机制
要解决这个问题,核心是为 SSE 模式增加一层弹性的连接管理机制,使客户端能够自动检测到 SSE 中断,并主动重新建立连接,重新初始化会话与工具注册流程。这样,即使网络链路被关闭,客户端也能自动完成恢复,不需要人工干预,也不会影响智能体对 MCP 工具的调用。
Spring AI 虽然没有提供现成的重连脚手架,但是他提供了一些有用的方法,可以让我们自行实现重连效果。
核心方法就是 McpSyncClient 的 ping 方法,可以作为我们的心跳检测手段。项目启动的时候先初始化一次,如果初始化失败,就会启动一个后台重试线程,不停地尝试重新初始化。接着再利用一个定时任务,做心跳检测,比如可以每隔 5 秒 ping 一次 MCP Server。并且使用了原子标记,只会启动一个重试线程,不会出现重复创建多个任务的情况。重试线程会一直循环重连,连成功了就自动停止。这样一来,无论是网络抖一下还是服务器重启,客户端都能自动恢复。
工具代码我们还复用之前的一个查天气的Demo:
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class WeatherService {
@Tool(description = "根据城市名称查询天气信息") public String getWeather(String city) { if (city == null) { return "请提供城市名称"; } return switch (city) { case "北京" -> "北京: 晴, 25°C"; case "上海" -> "上海: 多云, 22°C"; case "深圳" -> "深圳: 小雨, 28°C"; default -> city + ": 下雪, -20°C"; }; } }
|
重连机制核心代码逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @Service @Slf4j public class RetrySSEMcpServer {
@Autowired private OpenAiChatModel chatModel; private ChatClient chatClient; private McpSyncClient sseClient; @PostConstruct public void init() { log.info("Initializing SSE MCP Client..."); try { this.sseClient.initialize(); log.info("SSE MCP client initialized."); } catch (Exception e) { log.error("Initial SSE initialize failed, will rely on retry thread.", e); ToolCallback[] callbacks = provider.getToolCallbacks(); this.chatClient = ChatClient.builder(chatModel) .defaultToolCallbacks(callbacks) .defaultTools() .build(); } private McpSyncClient buildClient() { HttpClientSseClientTransport transport = HttpClientSseClientTransport .builder("http://127.0.0.1:8003") .sseEndpo int("/sse") .build(); return McpClient.sync(transport) .clientInfo(new io.modelcontextprotocol.spec.McpSchema.Implementation("sse-client", "1.0")) .requestTimeout(Duration.ofSeconds(10)) .build(); } @Scheduled(fixedDelay = 5000) public void pingSse() { log.info("SSE MCP ping..."); if (sseClient == null) { log.warn("SSE client not initialized yet."); startRetryInitialize(); return; } try { sseClient.ping(); log.debug("SSE MCP ping OK."); } catch (Exception e) { log.error("SSE MCP ping failed: {}", e.getMessage()); startRetryInitialize(); } } private void startRetryInitialize() { retryExecutor.submit(() -> { log.warn("Start retrying SSE MCP initialize..."); while (true) { try { ToolCallback[] callbacks = provider.getToolCallbacks(); this.chatClient = ChatClient.builder(chatModel) .defaultToolCallbacks(callbacks) .defaultTools() .build(); retrying.set(false); return; } catch (Exception e) { log.warn("Retry initialize failed, will retry in 10s. Reason: {}", e.getMessage()); } try { Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); } public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }
|
需要注意的是除了McpSyncClient需要重新初始化,我们的Chatclient也同样需要初始化,因为 ChatClient 内部的 ToolCallback 是在初始化时注入的。ToolCallback 绑定的 McpSyncClient 是旧的,会话已断开。即使你重新初始化了 sseClient,ChatClient 没有同步更新,仍然会继续调用旧的客户端。
我们再次启动MCP Client 8001端口,和MCP SSE Server 8003端口,来看下效果:

启动成功后,我们断开SSE 8003端口,MCP Client 8001就报错了:

然后我们再把MCP Server SSE 8003端口启动起来,查看MCP Client会不会自己实现重连:

我们可以看到上面的SSE MCP re-initalized已经成功了,我们再尝试下,进行chatclient提问:
