从MCP客户端连不上到工具列表为空:一次Spring AI联调踩坑全记录

从MCP客户端连不上到工具列表为空:一次Spring AI联调踩坑全记录
Silence本文记录一次基于 Spring AI 的 MCP(Model Context Protocol)实践项目联调过程,包含四个真实踩坑的完整排查与解决记录。技术栈:Spring Boot 3.5.11 + Spring AI 1.1.2 + MCP Java SDK 0.17.0 + JDK 21。
一、项目背景
项目包含两个模块:
- mcp-tools-server:MCP 服务端,提供工具、资源、提示词
- mcp-tools-client:MCP 客户端,通过 stdio 方式启动并连接服务端
整个联调过程踩了四个坑,从”进程启动即失败”到”能连上但工具列表为空”,下面逐一记录。
二、坑一:addEnvVar 误用导致服务端进程根本没启动
现象
客户端启动时报错:
1 | java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 20000ms in 'map' |
同时日志里大量 STDERR 输出乱码:
1 | STDERR Message received: -cp <Ŀ¼�� zip/jar �ļ��������·��> |
原因
客户端构建 ServerParameters 时写成了:
1 | ServerParameters.builder("java") |
addEnvVar 是用来设置环境变量的,不是传命令行参数。这行代码等价于”启动 java 程序,并设置一个名为 -jar 的环境变量”。java 命令没收到任何有效参数,于是打印帮助信息(就是那些乱码)后退出。客户端在 stdout 上等不到 JSON-RPC 响应,20 秒后超时。
解决
传命令行参数要用 args:
1 | ServerParameters.builder("java") |
经验:MCP 的 stdio 传输要求服务端 stdout 只输出 JSON-RPC 协议消息,任何日志、帮助信息都必须走 stderr。客户端日志里出现大量 STDERR 输出时,第一反应应该是”服务端没正常启动或没在 stdout 上说话”。
三、坑二:OpenAI 自动配置缺 API Key 导致启动失败(连环出现两次)
现象
MCP 连接成功后,应用启动仍然失败:
1 | Error creating bean with name 'openAiAudioSpeechModel': |
按提示排除或配置后,下一次启动又冒出另一个:
1 | Error creating bean with name 'openAiAudioTranscriptionModel': |
原因
Spring AI 的 OpenAI starter 不是单一自动配置,而是按模型类型拆成多个独立配置类:
| 配置类 | 模型类型 |
|---|---|
| Chat | 对话 |
| Embedding | 向量嵌入 |
| Image | 图像 |
| Audio Speech | 语音合成 |
| Audio Transcription | 语音转写 |
| Moderation | 内容审核 |
每个配置类在创建 Bean 时都会独立检查 API Key。只排除 Speech 那个,Transcription 的配置仍然激活,于是”打地鼠”式报错。
解决
相比在启动类里逐个写 exclude,更干净的是用 Spring AI 提供的顶层属性一次性关闭不用的模型。在 application.yml 中:
1 | spring: |
这样所有 OpenAI 模型都不会尝试初始化,也就不会去检查 API Key。如果确实要用 Chat 模型,只保留 spring.ai.model.chat: openai(或删掉那行),并配置好 spring.ai.openai.api-key 即可。
经验:Spring AI 1.1.x 中,部分 OpenAI 自动配置之间存在 @ImportAutoConfiguration 依赖关系,用 exclude 可能排除不干净。属性禁用方案(spring.ai.model.*=none)比 exclude 更可靠。
四、坑三:listTools() 返回空,日志提示 No tool methods found
现象
MCP 连接成功、应用正常启动,但客户端调用 listTools() 拿不到任何工具。服务端日志持续出现:
1 | WARN o.s.m.p.tool.SyncMcpToolProvider - No tool methods found in the provided tool objects: [] |
排查过程
BusinessTools 本身写得没问题:类上有 @Component,方法都是 public,注解也加了。问题出在注册路径上。
最初用的是 Spring AI 通用工具注解 @Tool,并手动创建 ToolCallbackProvider:
1 |
|
结果直接报错:
1 | No @Tool annotated methods found in com.jichi.mcp.BusinessTools@73fb1d7f. |
注意对象地址 @73fb1d7f 没有 $$SpringCGLIB$$ 后缀,说明传入的不是代理对象,但 MethodToolCallbackProvider 依然扫不到 @Tool 方法。这是 Spring AI 1.1.x 在某些场景下对 @Tool 扫描的已知缺陷,社区有多个 Issue 记录。
解决:切换到 MCP Server 原生注解 @McpTool
Spring AI MCP Server 提供了专用的 @McpTool / @McpToolParam 注解,配合 McpServerAnnotationScannerAutoConfiguration 自动扫描注册,不需要也不应该手动创建 ToolCallbackProvider。
关键点有两个:
1. 用对包路径。 classpath 里同时存在 spring-ai-mcp-annotations-1.1.2.jar 和早期的 mcp-annotations-0.8.0.jar,但 Spring AI 1.1.2 的自动扫描器只认自己的命名空间:
1 | // 正确 |
2. 删掉手动注册的 Bean。 保留 BusinessTools 上的 @Component,让自动扫描器接管即可。
改完后,四个工具方法用 @McpTool 标注:
1 |
|
服务端启动后不再出现 No tool methods found,客户端 listTools() 正常返回四个工具。
经验:@Tool 和 @McpTool 是两条不同的注册路径。@Tool 需要手动 ToolCallbackProvider,且在某些代理/生命周期场景下有扫描缺陷;@McpTool 配合自动扫描更稳定,是 MCP Server 场景的推荐用法。此外,注意注解的包路径——早期社区包和 Spring AI 官方包的注解不互通。
五、坑四(非致命):几条可以忽略的 WARN
最后应用完整启动成功:
1 | Tomcat started on port 8080 (http) with context path '/' |
日志里还剩几条 WARN,都不影响功能,记录一下含义:
| WARN 日志 | 含义 |
|---|---|
SyncMcpSamplingProvider - No sampling methods found |
客户端没有注册 sampling(服务端反向调用大模型)回调 |
SyncMcpElicitationProvider - No elicitation methods found |
客户端没有注册 elicitation(服务端向用户发起交互)回调 |
BeanPostProcessorChecker ... is not eligible for getting processed by all BeanPostProcessors |
来自 Spring AI 自动配置框架内部,非致命 |
只有需要服务端主动发 sampling/createMessage 或 elicitation/create 请求时才需要实现对应回调,否则可以忽略这两条 WARN。
六、踩坑总结
| 坑 | 现象 | 根因 | 解决 |
|---|---|---|---|
| 1 | 客户端初始化 20 秒超时,STDERR 大量乱码 | addEnvVar 误用来传命令行参数 |
改用 .args("-jar", path) |
| 2 | 启动失败,提示 OpenAI API key 缺失(连环出现) | 多个 OpenAI 自动配置独立检查 Key | spring.ai.model.*=none 一次性禁用 |
| 3 | listTools() 为空,No tool methods found |
@Tool 扫描缺陷 + 注解包路径错误 |
改用 org.springframework.ai.mcp.annotation.McpTool,删掉手动 Provider |
| 4 | sampling/elicitation 等 WARN | 未注册对应回调 | 按需实现,否则忽略 |
几个关键心法
点开查看心法详情
- stdio 通信的纯净性:服务端 stdout 只能输出 JSON-RPC,日志必须走 stderr
- 自动配置的粒度:Spring AI 按模型类型拆分自动配置,禁用要用属性而不是逐个 exclude
- 注解的命名空间:
@Tool和@McpTool是两条路,包路径要对,注册方式要匹配 - 看日志的顺序:先确认进程是否正常启动,再确认握手是否成功,最后才看业务逻辑——本次四个坑正好对应这四个层次
希望这篇踩坑记录能帮到正在折腾 Spring AI MCP 的同学。






