No.07修订于 2026-08-19约 1,052 字 · 阅读 17 分钟AGENT开发 次阅读

Spring Boot 集成 Solon MCP Server 实践

MCPAIAgent
目录

背景

在已有的 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 各用一个端口。

架构概览

flowchart LR
    subgraph JVM["JVM 进程"]
        SpringBoot["Spring Boot<br/>端口: 8080<br/>CRM 业务 API<br/>Tomcat"]
        Solon["Solon<br/>端口: 8088<br/>MCP Server<br/>SmartHttp"]
    end

    CRM["CRM 前端 / Feign"] -->|HTTP| SpringBoot
    AI["AI 客户端<br/>SSE: /sse<br/>MSG: /sse/message"] -->|SSE| Solon

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 Serversolon-boot-smarthttp
缺 Reactor 类NoClassDefFoundError: reactor/core/publisher/SinksMCP 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 的 Filterorg.noear.solon.core.handle.Filter),不是 Spring 的。

启动链路

flowchart TD
    Main["CoreApplication.main()"]
    Check{"Solon.app() != null ?"}
    Return["return(防重复启动)"]
    Run["SpringApplication.run()"]
    Env["Spring 环境初始化<br/>端口解析(8080)"]
    Bean["Bean 创建阶段"]
    PostConstruct["McpServerConfig.@PostConstruct"]
    SetPort["System.setProperty<br/>server.port = 8088"]
    SolonStart["Solon.start()"]
    Scan["扫描 McpServerController<br/>注册 Tool"]
    Http["启动 SmartHttp Server<br/>监听 8088"]
    ClearPort["System.clearProperty<br/>恢复 server.port"]
    Tomcat["Web Server 启动<br/>Tomcat:8080"]

    Main --> Check
    Check -->|是| Return
    Check -->|否| Run
    Run --> Env
    Env --> Bean
    Bean --> PostConstruct
    PostConstruct --> SetPort
    SetPort --> SolonStart
    SolonStart --> Scan
    Scan --> Http
    Http --> ClearPort
    ClearPort --> Tomcat

端点说明

端点方法说明
http://localhost:8088/sseGETSSE 连接端点,AI 客户端通过此建立长连接
http://localhost:8088/sse/messagePOST消息端点,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 的防重判断