Spring Boot MCP(stdio)工具实现的注意事项(踩坑总结)
随着 MCP(Model Context Protocol)的普及,越来越多的人开始使用 Spring Boot + MCP 来给 AI 工具(如 TRAE、Claude Desktop )扩展能力。
但 stdio 模式的 MCP 和我们熟悉的 Web Spring Boot 完全不是一回事,稍不注意就会出现:
- Tool 调用返回
null - MCP Client 报 JSON 解析错误
- 工具列表加载失败
- 中文参数直接乱码
本文结合 真实踩坑经验 ,总结 Spring Boot 实现 MCP(stdio)工具时的关键注意事项。
一、首先要明确:stdio MCP ≠ Web 服务
MCP(stdio)本质是什么?
- MCP Client(如 TRAE)启动你的程序
- 通过 stdin / stdout 与之通信
- stdout 是协议通道,不是控制台
👉 你必须把 Spring Boot 当成:
一个"命令行工具程序",而不是 HTTP 服务
二、必须关闭 Web 能力(否则必翻车)
正确配置
yaml
spring:
main:
web-application-type: none
如果不关:
- Tomcat 会启动
- 控制台输出大量日志
- stdout 被污染
- MCP 直接无法解析
三、stdout 只能输出 MCP JSON(这是第一铁律)
常见 stdout 污染来源
| 来源 | 是否致命 |
|---|---|
| Spring Boot Banner | ✅ |
| log.info / log.debug | ✅ |
| System.out.println | ✅ |
| MyBatis SQL 日志 | ✅ |
| 数据库连接池启动日志 | ✅ |
错误表现
text
Unexpected token '�'
not valid JSON
Unexpected non-whitespace character after JSON
必须做的事情
1️⃣ 关闭 Banner
yaml
spring:
main:
banner-mode: off
2️⃣ 关闭所有 stdout 日志
yaml
logging:
level:
root: OFF
3️⃣ 提供 logback-spring.xml(推荐)
建议只输出MCP的日志其他日志写入到文件里:
xml
<configuration>
<property name="LOG_FILE" value="logs/mcp-server.log"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_FILE}</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
<logger name="org.springframework.ai.mcp" level="DEBUG"/>
<!-- 关闭 Spring Boot 自带的启动 INFO -->
<logger name="org.springframework.boot.StartupInfoLogger" level="OFF"/>
<logger name="org.springframework.boot.autoconfigure.logging" level="OFF"/>
</configuration>
或者直接:
xml
<configuration>
<root level="OFF"/>
</configuration>
MCP stdio 模式下,宁可没有日志,也不要 stdout 日志
四、Windows 下一定要强制 UTF-8(否则中文必炸)
典型错误
text
"��ѯˮ����Ϣ��时间�?"
这是 GBK 输出 + UTF-8 解析 的经典乱码。
正确做法(TRAE 启动参数)
json
{
"command": "java",
"args": [
"-Dfile.encoding=UTF-8",
"-Dspring.output.ansi.enabled=NEVER",
"-jar",
"xxx.jar"
]
}
📌 这一步不做,中文参数 100% 出问题
五、Tool 能调用 ≠ Spring 容器正常
一个非常迷惑人的现象是:
- Tool 能被 TRAE 识别
- 简单 Tool(时间、字符串)正常
- 一查数据库就返回
null
常见原因
- 没有指定 profile
- 数据源指向了"空库"
- Mapper 没被扫描
- Service 实际上是
null
建议
yaml
spring:
profiles:
active: dev
或在启动参数中显式指定:
text
--spring.profiles.active=dev
六、MCP Tool 返回值要"简单、干净"
不推荐的返回方式
java
@Tool
public ReservoirInfo getReservoirInfo(String name)
问题可能来自:
- 懒加载代理
- 循环引用
- Jackson 序列化失败
- LocalDateTime / BigDecimal
推荐做法(最佳实践)
java
@Tool
public Map<String, Object> getReservoirInfo(String name) {
ReservoirInfo info = service.findByName(name);
if (info == null) {
return null;
}
Map<String, Object> result = new HashMap<>();
result.put("name", info.getName());
result.put("code", info.getCode());
result.put("location", info.getLocation());
return result;
}
👉 MCP Tool = 接口协议,不是领域模型暴露
七、严禁在 Tool 中使用 System.out
错误示例(非常常见)
java
System.out.println("查询水库信息:" + name);
后果
- stdout 被污染
- MCP JSON 被截断
- Client 报解析错误
📌 stdio MCP 下,System.out = 自杀按钮
八、最小可用 MCP(stdio)配置模板
application.yml(推荐基线)
yaml
spring:
main:
web-application-type: none # 设置为非Web应用类型
banner-mode: off # 关闭 Spring Boot 启动横幅
ai:
mcp:
server:
enabled: true
name: demo-ai
version: 1.0.0
stdio: true
logging:
level:
root: OFF
九、MCP stdio 的五条"生存铁律"
- stdout 只能输出 MCP JSON
- 禁止 Banner
- 禁止 stdout 日志
- 强制 UTF-8
- Tool 返回值尽量简单
只要违反一条,问题一定以 JSON 解析错误的形式出现
十、总结
Spring Boot 实现 MCP(stdio)工具时,最大的误区在于:
用 Web Spring Boot 的思维,去写一个"协议型命令行程序"
一旦你真正把它当成:
- 没有控制台
- 没有日志
- stdout 只服务协议
👉 MCP 就会变得非常稳定、非常可靠。