Spring Boot 集成 Solon MCP Server 实践
·
背景
在已有的 Spring Boot(Java 8)CRM 项目中集成 MCP(Model Context Protocol)Server,使 AI 客户端(如 Claude Desktop)能够通过 MCP 协议调用 CRM 的业务能力。
选择 Solon 的 MCP 实现(solon-ai-mcp),因为它是 Java 生态中较成熟的 MCP Server 方案。
核心挑战
Solon 和 Spring Boot 是两个完全独立的框架,注解体系、容器、HTTP 服务器互不兼容。不能简单地加个 @RestController 就让 Solon 的 @McpServerEndpoint 生效。
需要在 Spring Boot 启动过程中手动拉起一个 Solon 实例,两个 HTTP Server 各用一个端口。
架构概览
Maven 依赖
<!-- Solon MCP 协议实现 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-ai-mcp</artifactId>
<version>3.2.0</version>
</dependency>
<!-- Solon 内嵌 HTTP 服务器(基于 Jetty) -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-boot-smarthttp</artifactId>
<version>3.2.0</version>
</dependency>
<!-- MCP SSE 传输层依赖 Reactor(必须 >= 3.4.0 才有 Sinks 类) -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.4.38</version>
</dependency>
踩坑记录:依赖问题
| 问题 | 报错 | 原因 | 解决 |
|---|---|---|---|
| 缺 HTTP 服务器 | curl 连不上 8088,netstat 无监听 |
solon-ai-mcp 只含协议逻辑,不含 HTTP Server |
加 solon-boot-smarthttp |
| 缺 Reactor 类 | NoClassDefFoundError: reactor/core/publisher/Sinks |
MCP SSE 传输层用了 Reactor 的 Sinks,项目本身没有 |
加 reactor-core,版本必须 >= 3.4.0 |
| Spring Boot 管理的 Reactor 版本太旧 | 同上(加了依赖但没指定版本) | Spring Boot 2.x 管理的 reactor-core < 3.4.0,没有 Sinks |
显式指定 <version>3.4.38</version> |
代码实现
1. Solon 启动配置 — McpServerConfig.java
核心思路:利用 Spring 的 @PostConstruct 生命周期钩子,在 Spring Bean 初始化阶段启动 Solon。
@Slf4j
@Configuration
public class McpServerConfig {
@PostConstruct
public void init() {
log.info("【McpServer】启动中...");
// 临时设置 Solon 端口
String originalPort = System.getProperty("server.port");
System.setProperty("server.port", "8088");
try {
Solon.start(McpServerConfig.class, new String[]{});
} finally {
// 恢复原值,避免影响 Spring Boot 端口
if (originalPort != null) {
System.setProperty("server.port", originalPort);
} else {
System.clearProperty("server.port");
}
}
log.info("【McpServer】启动完成...");
}
}
为什么要用系统属性设端口?
- Solon 会读取 classpath 下的
application.yml,其中server.port: 8080会覆盖掉app.yml的配置 - 命令行参数
--server.port=8088或-server.port=8088在实测中 Solon 未正确识别 - 系统属性在 Solon 配置加载优先级中最高,且
@PostConstruct执行时 Spring Boot 已完成端口解析,不会影响 Spring Boot
2. MCP Controller — McpServerController.java
@Slf4j
@McpServerEndpoint(sseEndpoint = "/sse")
public class McpServerController {
@ToolMapping(description = "提报线索(将客户线索提报到CRM系统)")
public String submitClue(@ToolParam(description = "客户名称") String customer,
@ToolParam(description = "客户来源") String source,
@ToolParam(description = "客户电话") String phone,
@ToolParam(description = "提报人工号") String userId) {
log.info("提报线索:客户名称:{},来源:{},电话:{},工号:{}", customer, source, phone, userId);
// 业务逻辑...
return "线索提报成功!";
}
@ToolMapping(description = "查询线索状态(查询用户提报过的线索的状态)")
public String queryClueStatus(@ToolParam(description = "提报人工号") String userId) {
log.info("查询线索状态:提报人工号:{}", userId);
// 业务逻辑...
return JSON.toJSONString(result);
}
}
关键点:
- 这个类不需要 Spring 的
@RestController,它是 Solon 的组件 @McpServerEndpoint告诉 Solon 这是一个 MCP Server,SSE 端点为/sse@ToolMapping注册一个 MCP Tool,AI 可以发现并调用它@ToolParam描述参数,AI 据此生成正确的调用
3. 鉴权过滤器 — McpAuthFilter.java
@Slf4j
@Component
public class McpAuthFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
String path = ctx.path();
// 只拦截 MCP 端点
if (path.startsWith("/sse") || path.startsWith("/sse/message")) {
String token = ctx.header("Authorization");
if (!isValidToken(token)) {
log.warn("MCP 鉴权失败:path={}", path);
ctx.status(401);
ctx.output("Unauthorized");
return;
}
}
chain.doFilter(ctx);
}
private boolean isValidToken(String token) {
if (token == null || token.isEmpty()) {
return false;
}
return "Bearer mcp-secret-token".equals(token);
}
}
注意这里用的是 Solon 的 Filter(org.noear.solon.core.handle.Filter),不是 Spring 的。
启动链路
端点说明
| 端点 | 方法 | 说明 |
|---|---|---|
http://localhost:8088/sse |
GET | SSE 连接端点,AI 客户端通过此建立长连接 |
http://localhost:8088/sse/message |
POST | 消息端点,AI 客户端发送 Tool 调用请求 |
客户端配置
Claude Desktop
{
"mcpServers": {
"crm-server": {
"url": "http://localhost:8088/sse",
"headers": {
"Authorization": "Bearer mcp-secret-token"
}
}
}
}
curl 测试
# 测试 SSE 连接
curl -H "Authorization: Bearer mcp-secret-token" http://localhost:8088/sse
文件清单
com/sf/digit/crm/core/mcp/
├── McpServerConfig.java # Solon 启动配置(@PostConstruct 拉起 Solon)
├── McpServerController.java # MCP Tool 定义(@McpServerEndpoint + @ToolMapping)
└── McpAuthFilter.java # Solon Filter 鉴权
总结
| 要点 | 说明 |
|---|---|
| 两个框架共存 | Spring Boot (8080) + Solon (8088),互不干扰 |
| 端口隔离 | 通过 System.setProperty 在 @PostConstruct 中临时设置 Solon 端口 |
| 三个必要依赖 | solon-ai-mcp + solon-boot-smarthttp + reactor-core >= 3.4.0 |
| 鉴权 | Solon 的 Filter,在 HTTP 层拦截 MCP 端点 |
| 不动核心代码 | 只需在 CoreApplication 加一个 Solon.app() != null 的防重判断 |
更多推荐



所有评论(0)