Spring AI MCP Server接入百炼问题排查
问题背景
在将基于Spring AI的MCP(Model Context Protocol)服务接入阿里云百炼平台时,遇到一个奇怪的问题:MCP服务在trae客户端上可以正常工作,但在百炼平台上却无法正常使用。具体表现为百炼平台发送第二个请求时,服务端接收不到请求体,导致请求失败。
公司有个AI对话智能体的需求,作为负责这个需求的后端开发,开发阶段被这个问题困扰了好久,不过最终还是然我找到原因了。所以写一篇博客分享一下,避免大家后续使用Spring MCP Server 接入百炼时踩坑!
问题现象
正常情况(trae客户端)
trae客户端每个请求都创建新的连接:
- 第一个连接发送initialize请求 → 响应后关闭连接
- 第二个连接发送notifications/initialized请求 → 响应后关闭连接
- 第三个连接发送tools/list请求 → 正常处理
异常情况(百炼平台)
百炼一直会提示:"获取工具异常,请确认MCP是否正常运行"
"获取工具异常,请确认MCP是否正常运行
错误码:11200001
RequestId: dce6eab1-b35c-4471-bc97-d764639dff73
分析日志后发现是由于百炼平台会尝试复用连接导致的:
- 第一个连接发送initialize请求 → 响应后服务器关闭连接
- 在同一个连接上尝试发送第二个请求 → 但是此时MCP服务端Netty已经关闭链接了,这时候即使收到请求头,但是Netty的channel可能已经半关闭了,无法再继续接收请求内容,也无法正常响应内容。百炼那边等待超时就抛出异常了!
日志分析
从服务端日志可以看到关键信息:
log
# 第一个请求正常处理
2025-12-13T11:43:58.745+08:00 INFO REQUEST_BODY_DEBUG : MCP Request Body (207 bytes): {"jsonrpc":"2.0","method":"initialize","id":"3fe559ae-0",...}
# 服务器响应后关闭连接
2025-12-13T11:43:58.750+08:00 DEBUG r.n.http.server.HttpServerOperations : Last HTTP packet was sent, terminating the channel
# 第二个请求到达,但请求体为空
2025-12-13T11:44:58.805+08:00 INFO REQUEST_BODY_DEBUG : MCP Request Body (0 bytes):
2025-12-13T11:44:58.808+08:00 ERROR WebFluxStreamableServerTransportProvider : Failed to deserialize message: No content to map due to end-of-input
问题定位
通过深入分析日志,我们发现问题出现在几个方面:
1. HTTP/1.1连接复用问题
- 服务器在发送完第一个响应后立即关闭了连接(
terminating the channel) - 百炼平台认为连接仍然可用,尝试复用连接发送第二个请求
- 由于连接已关闭,第二个请求的请求体无法被正确传输
2. 客户端-服务器行为不一致
- trae:为每个请求创建新连接,符合服务器预期
- 百炼:尝试复用连接以提升性能,但与服务器行为冲突
3. 协议处理差异
- trae使用协议版本:
2025-06-18 - 百炼使用协议版本:
2025-03-26 - trae的capabilities为空对象
- 百炼的capabilities包含
roots.listChanged=true
排查过程
第一阶段:基础排查
- 检查协议兼容性:确认两个协议版本都支持
- 检查日志级别:启用DEBUG级别日志
- 对比请求差异:分析trae和百炼的请求头、请求体差异
第二阶段:深入分析
- 添加请求体调试过滤器:
java
@Component
@Order(1)
public class RequestBodyDebugFilter implements WebFilter {
// 记录请求头和请求体内容
}
- 发现关键证据:
- 第二个请求的
Content-Length: 54,但实际接收到的body是0字节 - 请求在开始处理后等待60秒超时
- 排除可能性:
- 排除100-continue问题(百炼没有发送Expect头)
- 排除缓冲区大小问题
- 排除JSON解析问题
第三阶段:问题根源确认
通过对比分析和日志追踪,确认问题是:
服务器在HTTP/1.1连接上过早关闭连接,但客户端仍尝试复用该连接
解决方案
最终解决方案:响应头中告诉百炼,链接已经关闭
java
@Component
@Order(Integer.MIN_VALUE)
public class ForceConnectionCloseFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
if (path.equals("/mcp")) {
// 在响应头中强制设置Connection: close
exchange.getResponse().getHeaders().set("Connection", "close");
log.info("Forcing connection close for MCP request on connection: {}",
exchange.getRequest().getId());
}
return chain.filter(exchange);
}
}
方案原理
- 显式通信 :通过
Connection: close头明确告诉客户端不要复用连接 - 客户端适配:百炼平台看到这个头后会为每个请求创建新连接
- 消除歧义:避免客户端和服务器对连接状态的理解不一致
其他尝试方案(未成功)
- 调整Netty配置尝试保持连接
- 修改HTTP解码器参数
- 添加Expect: 100-continue处理
- 调整连接超时时间
技术要点总结
1. HTTP/1.1连接管理
- 默认情况下,HTTP/1.1支持连接复用(keep-alive)
- 服务器可以通过
Connection: close头显式关闭连接 - Spring WebFlux/Netty在特定情况下可能过早关闭连接
2. Spring WebFlux行为
- Reactive编程模型对连接管理有特殊处理
- Netty默认配置可能不适合所有客户端
- 需要显式配置来处理连接复用
3. 客户端差异
- 不同HTTP客户端对连接复用的实现策略不同
- 有些客户端更激进地尝试复用连接
- 服务器需要能够处理各种客户端行为
经验教训
- 不要假设客户端行为一致:不同的HTTP客户端实现可能有显著差异
- 连接管理要显式:模糊的连接管理容易导致问题
- 日志是最好的调试工具:详细的日志可以快速定位问题
- 简单方案往往最有效 :复杂的配置调整不如简单的
Connection: close - 理解底层协议:对HTTP协议的理解有助于解决这类问题
结论
通过添加ForceConnectionCloseFilter,我们成功地解决了Spring AI MCP Server接入百炼平台的问题。这个问题的核心在于客户端和服务器对HTTP/1.1连接复用的行为不一致。解决方案虽然简单,但背后涉及对HTTP协议、Spring WebFlux连接管理和客户端兼容性的深入理解。