Spring Boot 集成 Solon MCP Server 实践:把 MCP 端点改到 TaoToken

发布时间:2026/10/2 20:38:01
Spring Boot 集成 Solon MCP Server 实践:把 MCP 端点改到 TaoToken 1. Spring Boot 项目里为什么还要塞一个 Solon MCP Server很多同学第一次听到「Spring Boot 集成 Solon MCP Server」会有点懵Spring Boot 本身就能写 HTTP 接口为什么还要在同一个 JVM 里再拉一个 Solon 实例答案在于 MCPModel Context Protocol这套协议目前 Java 生态里落地最顺手的实现是solon-ai-mcp它把 SSE 长连接、Tool 注册、参数描述这些细节都封装好了而 Spring Boot 官方并没有对等的 MCP Server 组件。于是现实中的做法就是CRM、订单、工单这些业务逻辑继续留在 Spring Boot 的 Tomcat 里跑MCP 这一层单独用 Solon 起一个轻量 HTTP Server两个框架各占一个端口互不干扰。这个场景特别适合已有 Java 8 老项目、又想快速让 AI 客户端比如 Claude Desktop、Cline、Codex 这类支持 MCP 的工具调用内部业务能力的团队。你不需要重构现有 Controller也不用把 Spring 容器改造成 Solon 容器只要在启动阶段手动拉起一个 Solon 实例把McpServerEndpoint标注的类扫描进去就行。听起来简单但真正动手时会撞上三个坑依赖缺 HTTP Server、Reactor 版本太旧没有Sinks类、以及 Solon 端口被 Spring Boot 的application.yml覆盖。这篇就把完整链路和排障过程一次讲清楚最后再把 MCP 端点改到 TaoToken 统一通道做一次连通性验证。先说清楚两个框架的边界。Spring Boot 用的是RestController、Service、Tomcat 线程池Solon 用的是McpServerEndpoint、ToolMapping、SmartHttp。它们的注解体系、IoC 容器、HTTP 服务器完全独立你不能指望在 Spring 的RestController上贴一个ToolMapping就能生效。正确姿势是让 Solon 自己扫描自己的组件Spring 只负责在合适的生命周期节点把 Solon 拉起来。这个「合适的节点」就是PostConstruct——此时 Spring 的 Bean 已经创建端口也解析完了拉起 Solon 不会影响主服务的 8080。还有一个容易被忽略的点MCP 的 SSE 传输层依赖 Reactor 的Sinks类而 Spring Boot 2.x 默认管理的reactor-core版本低于 3.4.0根本没有这个类。你如果只加solon-ai-mcp而不显式指定 Reactor 版本启动时就会看到NoClassDefFoundError: reactor/core/publisher/Sinks。这个报错后面会专门讲怎么排查。2. 接入前的依赖与 TaoToken 通道准备在写代码之前先把 Maven 依赖和 TaoToken 的接入信息准备好。依赖这块有三个是必须的缺一个都跑不起来。solon-ai-mcp提供 MCP 协议逻辑和注解solon-boot-smarthttp提供内嵌 HTTP Server基于 Jettyreactor-core提供 SSE 传输层需要的Sinks。版本上solon-ai-mcp和solon-boot-smarthttp用 3.2.0reactor-core必须显式写 3.4.38不能交给 Spring Boot 的 dependencyManagement 去管。!-- Solon MCP 协议实现 -- dependency groupIdorg.noear/groupId artifactIdsolon-ai-mcp/artifactId version3.2.0/version /dependency !-- Solon 内嵌 HTTP 服务器基于 Jetty -- dependency groupIdorg.noear/groupId artifactIdsolon-boot-smarthttp/artifactId version3.2.0/version /dependency !-- MCP SSE 传输层依赖 Reactor必须 3.4.0 才有 Sinks 类 -- dependency groupIdio.projectreactor/groupId artifactIdreactor-core/artifactId version3.4.38/version /dependency为什么reactor-core一定要写死版本因为 Spring Boot 2.x 的 BOM 里管理的 Reactor 版本普遍在 3.3.x没有Sinks。你如果只写groupId和artifactIdMaven 会从父 POM 继承一个旧版本编译能过运行就炸。这个坑我在一个 Java 8 的 CRM 项目里踩过日志里只有一行NoClassDefFoundError排查了半天才发现是版本被覆盖了。接下来是 TaoToken 通道的准备。TaoToken 在这里的角色是统一入口你本地 Solon 起的 MCP Server 通过 SSE 暴露工具AI 客户端侧则通过 TaoToken 的兼容端点来发起模型对话和工具调用这样密钥、模型 ID、Base URL 都在一处管理不用在每个客户端里散落配置。你需要先去控制台拿一个 API Key地址是 https://taotoken.net/api-keys 拿到的 Key 形如sk-开头的一串字符后面配置里会用到。模型 ID 这块如果你只是验证 MCP 工具调用链路用claude-sonnet-4-5这类支持工具调用的模型就行如果是长期跑编码 Agent可以看 Coding Plan 页面 https://taotoken.net/coding-plan 里的套餐说明。Base URL 统一用 https://taotoken.net/api 注意这个地址后面不要加斜杠也不要加/v1具体路径由客户端自己拼。文档在 https://taotoken.net/doc 遇到参数不确定的时候翻一下比猜快。把这三样东西记下来Base URL、API Key、Model ID。后面无论是配 Claude Desktop、Cline 还是 Codex 的auth.json都是围绕这三个值展开的。这里先不展开客户端配置等 Solon 这边跑通了再回头接。3. 可复制的 Solon 启动配置与 MCP 端点定义这一节是核心直接给可复制的代码。先看 Solon 的启动配置类思路是利用 Spring 的PostConstruct在 Bean 初始化阶段拉起 Solon并且临时把server.port系统属性设成 8088启动完再恢复避免影响 Spring Boot 自己的 8080。Slf4j Configuration public class McpServerConfig { PostConstruct public void init() { log.info(【McpServer】启动中...); // 临时设置 Solon 端口避免被 application.yml 的 server.port 覆盖 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.port8088在实测中 Solon 没有正确识别。系统属性在 Solon 的配置加载优先级里最高且PostConstruct执行时 Spring Boot 已经完成端口解析改这个属性不会影响主服务。然后是 MCP 端点定义。这个类不需要 Spring 的RestController它是 Solon 的组件靠McpServerEndpoint和ToolMapping注册工具。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); } }McpServerEndpoint(sseEndpoint /sse)告诉 Solon 这是一个 MCP ServerSSE 端点是/sseToolMapping注册一个可被 AI 发现的工具ToolParam描述参数AI 据此生成正确的调用。注意这里的 Filter 也要用 Solon 的不是 Spring 的。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); } }这里的Filter是org.noear.solon.core.handle.Filter别导错包。鉴权逻辑很简单就是比对Authorization头生产环境建议换成 JWT 或从配置中心读取。最后是application.yml里需要补的配置。Spring Boot 这边保持原样Solon 的端口通过系统属性控制所以application.yml里不需要为 Solon 单独写server.port否则会互相打架。如果你想让 Solon 读自己的配置可以在src/main/resources下放一个app.yml但端口还是以系统属性为准。# application.ymlSpring Boot 主配置保持原样 server: port: 8080 # Solon 相关配置建议放 app.yml避免和 Spring Boot 的 server.port 冲突 # app.yml solon: app: name: crm-mcp-server启动链路是这样的CoreApplication.main()先判断Solon.app() ! null如果已经启动过就直接 return防止重复启动然后SpringApplication.run()初始化 Spring 环境解析端口 8080Bean 创建阶段触发McpServerConfig.PostConstruct设置系统属性server.port8088调用Solon.start()扫描McpServerController注册 Tool启动 SmartHttp Server 监听 8088最后恢复server.portSpring 的 Tomcat 继续在 8080 启动。两个 HTTP Server 各用各的端口互不干扰。4. 验证请求与把 MCP 端点改到 TaoToken 统一通道代码写完先做本地连通性验证。启动应用后看日志里有没有【McpServer】启动完成...然后用netstat确认 8088 在监听。# 确认 8088 端口监听 netstat -ano | findstr 8088 # 测试 SSE 连接带鉴权头 curl -H Authorization: Bearer mcp-secret-token http://localhost:8088/sse如果curl能挂住不返回、日志里出现 SSE 连接建立的信息说明 MCP Server 起来了。如果curl直接报连接拒绝netstat也没有 8088那就是 HTTP Server 依赖没加或者 Solon 没启动成功回到第 5 节排查。本地通了之后把 MCP 端点改到 TaoToken 统一通道。这里的「改到」不是改 Solon 的监听地址而是让 AI 客户端侧通过 TaoToken 的兼容端点来访问模型和工具。以 Claude Desktop 为例配置里把url指向你本地 Solon 的 SSE 端点同时在模型侧配置 TaoToken 的 Base URL 和 Key。{ mcpServers: { crm-server: { url: http://localhost:8088/sse, headers: { Authorization: Bearer mcp-secret-token } } } }如果你用的是 Cline 或 Codex 这类支持 MCP 的编码工具配置结构类似核心是三件套Base URL 填 https://taotoken.net/api API Key 填你在控制台拿到的sk-开头的串Model ID 填claude-sonnet-4-5或你套餐里支持的模型。Codex 的auth.json里对应字段是base_url、api_key、modelCline 的 MCP 配置里则是baseUrl、apiKey、modelId字段名不同但含义一致。改完之后做一次完整的工具调用验证在 AI 客户端里发一句「帮我提报一个线索客户名称张三来源官网电话13800000000工号1001」观察两边的日志。Solon 这边应该打印提报线索客户名称张三来源官网电话13800000000工号1001Spring Boot 这边如果工具有回调业务逻辑也应该有对应日志。如果 AI 客户端提示找不到工具检查ToolMapping的description是否写清楚AI 是靠描述来匹配意图的。TaoToken 通道在这里的价值是统一管理你不用在每个客户端里分别填不同的 Key 和 Base URL换模型、换套餐只改一处。模型对话可以在 https://taotoken.net/model-chat 里先试一下工具调用是否正常再接到编码工具里。如果是长期跑 Agent 任务Coding Plan 的额度比按量计费更划算具体看 https://taotoken.net/coding-plan 。5. 本篇常见报错排查401、local proxy failed、reading choices、OAuth这一节把实际会撞到的报错列出来对照日志定位。报错一curl连不上 8088netstat无监听。原因是solon-ai-mcp只含协议逻辑不含 HTTP Server。解决方法是加solon-boot-smarthttp依赖。这个报错最隐蔽的地方在于应用启动不报错日志里也有【McpServer】启动完成...但端口就是没起来因为 Solon 找不到可用的 HTTP Server 实现就静默跳过了。报错二NoClassDefFoundError: reactor/core/publisher/Sinks。原因是 MCP SSE 传输层用了 Reactor 的Sinks而项目本身没有加reactor-core或者 Spring Boot 管理的版本低于 3.4.0。解决方法是显式加reactor-core并指定version3.4.38/version。如果你加了依赖但没指定版本Maven 会从父 POM 继承旧版本照样报错。报错三401 Unauthorized。这是鉴权失败检查Authorization头是否带了Bearer前缀以及 token 是否和McpAuthFilter里比对的一致。注意 Solon 的ctx.header(Authorization)拿到的是完整头值比对时要包含Bearer。报错四local proxy failed。这个通常出现在 AI 客户端侧说明客户端连不上你配置的 Base URL 或 SSE 端点。先确认 Solon 的 8088 在监听再确认客户端配置里的url没有多写斜杠或路径。如果 Base URL 填的是 TaoToken 的地址检查是不是误加了/v1后缀。报错五reading choices相关错误。这个一般出现在模型返回体解析阶段说明请求发出去了但响应格式不对。常见原因是 Model ID 填错或者 Base URL 指向了一个不兼容 OpenAI 格式的端点。TaoToken 的 Base URL 是 https://taotoken.net/api 兼容 OpenAI 格式Model ID 用文档里列出的值。报错六OAuth 相关报错。如果你用的是 Claude Code 这类走 OAuth 的工具报错里出现OAuth字样通常是认证方式选错了。Claude Code 接入第三方通道时应该用 API Key 方式而不是 OAuth。配置里把认证类型改成api_key填 TaoToken 的 Key。Claude Code 的接入文档在 https://taotoken.net/doc 里有专门章节路径和字段名以文档为准。排查的时候养成看两边日志的习惯Solon 的日志在【McpServer】前缀下Spring Boot 的日志在常规位置。工具调用失败时先看 Solon 有没有收到请求再看 Spring 侧的业务逻辑有没有执行最后看 AI 客户端的返回。三段日志对上了问题基本就定位了。6. 把 MCP 通道固定下来的几个实操建议跑通一次工具调用只是开始真正要长期用有几个地方值得固定下来。第一Solon 的端口不要写死在代码里虽然示例里用了 8088但生产环境建议从环境变量读System.getProperty(mcp.server.port, 8088)这样避免和别的服务撞端口。第二鉴权 token 不要硬编码McpAuthFilter里的mcp-secret-token换成从配置中心或环境变量读取Spring 的Value在 Solon 组件里用不了可以通过静态持有或者启动时传参的方式注入。第三ToolMapping的description要写清楚业务语义AI 是靠这个描述来匹配用户意图的。比如「提报线索」比「submitClue」对 AI 友好得多参数描述也一样「客户名称」比「customer」更容易让 AI 生成正确的调用。第四工具方法的返回值尽量结构化返回 JSON 字符串比返回纯文本更利于 AI 解析但要注意长度太长的返回会撑爆上下文。第五如果你有多个 MCP Server 要暴露可以共用同一个 Solon 实例用不同的McpServerEndpoint路径区分比如/crm/sse和/order/sse这样只需要一个 8088 端口。第六TaoToken 侧的 Key 建议按用途分验证用的和长期跑 Agent 的分开方便排查和限额。模型对话验证在 https://taotoken.net/model-chat 接入文档在 https://taotoken.net/doc API Key 管理在 https://taotoken.net/api-keys Coding Plan 在 https://taotoken.net/coding-plan 。最后说一个实际经验Spring Boot 和 Solon 共存时最容易出问题的是类加载和静态状态。Solon 的Solon.start()会初始化自己的全局上下文如果应用有热部署或者多次启动的场景记得在CoreApplication.main()里加Solon.app() ! null的判断防止重复启动导致端口占用。这个判断放在SpringApplication.run()之前简单一行能省掉很多「端口已被占用」的排查时间。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询