1. SpringBoot3 集成 SolonMCP 到底解决什么问题如果你正在用 SpringBoot3 写业务系统又想给系统加一个能被大模型调用的 MCP 服务端大概率会遇到一个尴尬Spring AI 的 MCP 支持还在快速迭代配置项多、版本耦合紧而 SolonMCPsolon-ai-mcp本身足够轻却默认是 Solon 生态的写法直接塞进 SpringBoot3 会有点水土不服。SolonMCP 是什么简单说它是 solon-ai 的一个扩展模块把「工具方法注册」「资源读取」「提示词模板」这三类 MCP 能力做成了注解驱动。你写一个普通 Java 方法加上ToolMapping它就能被 MCP 客户端通过 SSE 调用。它能做什么让 SpringBoot3 项目在不引入完整 Spring AI 栈的前提下快速暴露一个符合 MCP 协议的服务端点。适合谁适合已经有一套 SpringBoot3 服务、想低成本试水 MCP、又不想被框架绑定太深的开发者。我试过把 SolonMCP 嵌进一个 SpringBoot3 的订单服务里整个过程最麻烦的不是写工具方法而是让 Solon 的生命周期和 Spring 的容器生命周期对齐。这篇就按「依赖坐标 → 自动配置 → 工具注册 → 本地调试 → 报错排查」的顺序把最小可用路径走一遍。核心检索词先记住SpringBoot3 集成 SolonMCP 开发 MCP 服务关键在solon-web-servlet-jakarta这个适配包和SolonServletFilter的挂载。和 SpringBoot2 相比SpringBoot3 最大的变化是javax.servlet换成了jakarta.servlet。SolonMCP 官方示例里 SpringBoot2 用的是solon-web-servlet-javaxSpringBoot3 必须换成solon-web-servlet-jakarta否则启动时过滤器注册会直接抛ClassNotFoundException。这个点后面排障章节会展开。先明确整体结构SpringBoot3 负责 Web 容器和 Bean 管理Solon 以「嵌入式」方式启动只接管/mcp/*这一小段路由。两者通过一个FilterRegistrationBean桥接。工具类实现一个空接口IMcpServerEndpoint作为标记配置类在PostConstruct里启动 Solon在PreDestroy里停止 Solon保证不泄漏线程。下面从依赖开始一步步给出可复制的片段。2. TaoToken 前置准备与 MCP 调试环境在写代码之前先把「调用方」准备好。MCP 服务端写完后你需要一个能发 MCP 请求的客户端来验证。最省事的方式是用支持 MCP 的对话工具或编码工具连上来而这类工具通常需要一个兼容 OpenAI 协议的模型入口。TaoToken 在这里的角色就是提供统一的模型调用入口让你在验证 MCP 工具时不用自己搭一套模型服务。TaoToken 是什么它是一个模型调用聚合入口提供兼容 OpenAI 的 API 格式支持对话模型和编码场景。能做什么你可以用它作为 MCP 客户端背后的 LLM 提供方也可以用它来跑 Claude Code 这类编码 Agent。适合谁适合需要快速验证 MCP 工具链路、又不想在模型接入上花太多时间的开发者。前置准备分三块拿到 API Key、确认 Base URL、选好 Model ID。这三件套在后面的配置里会反复出现。第一API Key。访问 https://taotoken.net/api-keys 创建密钥。注意这个页面是控制台里的密钥管理入口创建后复制保存后面配置里用sk-开头的字符串。第二Base URL。TaoToken 的 API 根地址是 https://taotoken.net/api 注意不要带多余的路径后缀。如果你用的是 OpenAI 兼容客户端填这个地址即可如果是 Claude Code 这类工具它有自己的配置方式但底层还是指向这个入口。第三Model ID。这个取决于你开通的模型常见的有对话模型和编码模型。验证 MCP 工具调用时建议先用一个响应快的对话模型减少等待时间。如果你打算长期做编码类 Agent 开发可以了解 Coding Plan它面向持续编码场景做了额度优化https://taotoken.net/coding-plan 。如果只是想先跑通 MCP 工具调用用模型对话入口就够了https://taotoken.net/models 。这里要提醒一点MCP 服务端本身不依赖 TaoToken它只是暴露工具TaoToken 是给「调用 MCP 的客户端」用的模型入口。两者是解耦的。你完全可以先用curl直接打 MCP 的 SSE 端点验证工具再接入模型做自然语言调用。环境上还需要 JDK 17SpringBoot3 要求、Maven 3.8。本地调试时MCP 服务端跑在 8080客户端连http://localhost:8080/mcp/demo1/sse。如果你本地 8080 被占用改application.yml里的server.port即可但客户端地址要同步改。把 Key 和 Base URL 记在一个临时文件里下一步配置客户端时会用到。别把 Key 硬编码进提交到仓库的代码里用环境变量或本地配置文件。3. 可复制的依赖配置与工具注册代码这一章是核心给出完整可复制的pom.xml片段、application.yml、启动类、配置类、工具类。路径和包名保持和示例一致你直接改包名即可。3.1 pom.xml 依赖坐标SpringBoot3 的父工程和 SolonMCP 依赖如下。关键是solon-web-servlet-jakarta这是 SpringBoot3 与 SpringBoot2 的唯一依赖差异点。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version solon.version3.0.7/solon.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- SolonMCP 核心 -- dependency groupIdorg.noear/groupId artifactIdsolon-ai-mcp/artifactId version${solon.version}/version /dependency !-- SpringBoot3 专用 servlet 适配jakarta 命名空间 -- dependency groupIdorg.noear/groupId artifactIdsolon-web-servlet-jakarta/artifactId version${solon.version}/version /dependency /dependencies注意solon-ai-mcp的版本要和solon-web-servlet-jakarta保持一致否则可能出现NoSuchMethodError。我实测 3.0.7 这一组是稳的。3.2 application.yml 配置server: port: 8080 solon: app: name: springboot3-mcp-demo # 关闭 Solon 自带的 banner避免和 SpringBoot 日志混在一起 banner: enabled: falseSolon 的配置也可以单独放mcpserver.yml通过Solon.start(..., new String[]{--cfgmcpserver.yml})加载。最小场景下用默认配置即可。3.3 启动类package webapp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class HelloApp { public static void main(String[] args) { SpringApplication.run(HelloApp.class, args); } }3.4 端点标记接口package webapp.mcpserver; public interface IMcpServerEndpoint { }这个空接口的作用是让 Spring 能通过ListIMcpServerEndpoint收集所有工具类避免手动注册。3.5 Solon 生命周期配置类package webapp.mcpserver; import org.noear.solon.Solon; import org.noear.solon.ai.mcp.server.McpServerEndpointProvider; import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; import org.noear.solon.ai.mcp.server.tool.MethodToolProvider; import org.noear.solon.ai.mcp.server.resource.MethodResourceProvider; import org.noear.solon.ai.mcp.server.prompt.MethodPromptProvider; import org.noear.solon.web.servlet.SolonServletFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationUtils; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import java.util.List; Configuration public class McpServerConfig { PostConstruct public void start() { Solon.start(McpServerConfig.class, new String[]{--cfgmcpserver.yml}); } PreDestroy public void stop() { if (Solon.app() ! null) { Solon.stopBlock(false, Solon.cfg().stopDelay()); } } Bean public McpServerConfig init(ListIMcpServerEndpoint serverEndpoints) { for (IMcpServerEndpoint serverEndpoint : serverEndpoints) { McpServerEndpoint anno AnnotationUtils.findAnnotation( serverEndpoint.getClass(), McpServerEndpoint.class); if (anno null) { continue; } McpServerEndpointProvider provider McpServerEndpointProvider.builder() .from(serverEndpoint.getClass(), anno) .build(); provider.addTool(new MethodToolProvider(serverEndpoint)); provider.addResource(new MethodResourceProvider(serverEndpoint)); provider.addPrompt(new MethodPromptProvider(serverEndpoint)); provider.postStart(); } return this; } Bean public FilterRegistrationBeanSolonServletFilter mcpServerFilter() { FilterRegistrationBeanSolonServletFilter filter new FilterRegistrationBean(); filter.setName(SolonFilter); filter.addUrlPatterns(/mcp/*); filter.setFilter(new SolonServletFilter()); return filter; } }这里有个坑AnnotationUtils必须用 Spring 的org.springframework.core.annotation.AnnotationUtils不要用 Solon 自带的同名工具类否则在有代理的情况下拿不到注解。3.6 工具类package webapp.mcpserver.tool; import org.noear.solon.ai.annotation.ToolMapping; import org.noear.solon.ai.annotation.ResourceMapping; import org.noear.solon.ai.annotation.PromptMapping; import org.noear.solon.ai.annotation.Param; import org.noear.solon.ai.chat.message.ChatMessage; import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; import org.springframework.stereotype.Component; import webapp.mcpserver.IMcpServerEndpoint; import java.util.Arrays; import java.util.Collection; Component McpServerEndpoint(name demo1, sseEndpoint /mcp/demo1/sse) public class McpServerTool implements IMcpServerEndpoint { ToolMapping(description 查询天气预报) public String getWeather(Param(description 城市位置) String location) { return 晴14度; } ResourceMapping(uri config://app-version, description 获取应用版本号) public String getAppVersion() { return v3.2.0; } ResourceMapping(uri db://users/{user_id}/email, description 根据用户ID查询邮箱) public String getEmail(Param(description 用户Id) String user_id) { return user_id example.com; } PromptMapping(description 生成关于某个主题的提问) public CollectionChatMessage askQuestion(Param(description 主题) String topic) { return Arrays.asList( ChatMessage.ofUser(请解释一下 topic 的概念) ); } }Component用的是 Spring 的注解别误用 Solon 的同名注解。编译时建议开启-parameters参数否则Param的 name 需要显式写全。Maven 里加build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration parameterstrue/parameters /configuration /plugin /plugins /build到这里服务端代码就齐了。启动HelloApp的 main 方法控制台会看到 Solon 启动日志和 SpringBoot 的 Tomcat 启动日志交替出现这是正常的。4. 验证请求与成功返回结果启动后先确认端口和路由。控制台应该出现类似Tomcat started on port(s): 8080 (http) Solon started, app: springboot3-mcp-demo然后验证 MCP 端点是否可达。MCP 的 SSE 端点需要先建立连接再发 JSON-RPC 请求。最直接的方式是用curl打 SSEcurl -N http://localhost:8080/mcp/demo1/sse如果连接成功会看到类似event: endpoint data: /mcp/demo1/message?sessionIdxxxx这个sessionId是后续发消息用的。拿到 sessionId 后发一个工具调用请求curl -X POST http://localhost:8080/mcp/demo1/message?sessionIdxxxx \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 1, method: tools/call, params: { name: getWeather, arguments: {location: 杭州} } }预期返回{ jsonrpc: 2.0, id: 1, result: { content: [ {type: text, text: 晴14度} ] } }看到晴14度就说明工具注册和调用链路通了。资源读取同理把method换成resources/readuri填config://app-version返回v3.2.0。如果你用 Java 客户端验证代码更简洁import org.noear.solon.ai.mcp.client.McpClientProvider; import java.util.Collections; import java.util.Map; public class McpClientTest { public static void main(String[] args) throws Exception { McpClientProvider toolProvider McpClientProvider.builder() .apiUrl(http://localhost:8080/mcp/demo1/sse) .build(); MapString, Object map Collections.singletonMap(location, 杭州); String rst toolProvider.callToolAsText(getWeather, map).getContent(); System.out.println(rst); assert 晴14度.equals(rst); String version toolProvider.readResourceAsText(config://app-version).getContent(); System.out.println(version); } }运行后控制台输出晴14度和v3.2.0验证通过。再进一步把 MCP 客户端作为 LLM 的工具集。用 TaoToken 的兼容入口作为模型提供方配置三件套Base URL 填https://taotoken.net/apiKey 填你创建的密钥Model ID 填你开通的对话模型。然后import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.ChatResponse; import org.noear.solon.ai.mcp.client.McpClientProvider; public class McpWithLlmTest { public static void main(String[] args) throws Exception { McpClientProvider toolProvider McpClientProvider.builder() .apiUrl(http://localhost:8080/mcp/demo1/sse) .build(); ChatModel chatModel ChatModel.of(https://taotoken.net/api/chat/completions) .provider(openai) .model(你的模型ID) .apiKey(System.getenv(TAOTOKEN_API_KEY)) .defaultToolsAdd(toolProvider) .build(); ChatResponse resp chatModel.prompt(杭州今天的天气怎么样).call(); System.out.println(resp.getMessage()); } }模型会先决定调用getWeather拿到晴14度后再组织自然语言回答。这一步跑通说明 MCP 服务端和模型调用链路都正常。5. 本篇常见错误排查这一章按真实报错来。以下都是我或读者实际遇到过的。报错一java.lang.ClassNotFoundException: javax.servlet.Filter原因SpringBoot3 用的是jakarta.servlet你引了solon-web-servlet-javax。解决把依赖换成solon-web-servlet-jakarta。这是 SpringBoot3 和 SpringBoot2 的唯一依赖差异别搞混。报错二local proxy failed或连接http://localhost:8080/mcp/demo1/sse超时原因SolonServletFilter没挂上或者addUrlPatterns写错了。检查FilterRegistrationBean的addUrlPatterns(/mcp/*)是否和McpServerEndpoint的sseEndpoint前缀一致。如果 sseEndpoint 是/mcp/demo1/sse过滤器至少要覆盖/mcp/*。报错三401 Unauthorized这个通常出现在客户端连模型入口时不是 MCP 服务端本身。检查 TaoToken 的 API Key 是否正确、是否带了Bearer前缀、Base URL 是否是https://taotoken.net/api而不是首页地址。如果 Key 是从环境变量读的确认变量名拼写一致。报错四reading choices相关解析异常原因模型返回体不是标准 OpenAI 格式或者你用的客户端期望的字段和实际返回不一致。确认 Model ID 填对且客户端用的是 OpenAI 兼容模式。如果用的是 Claude Code 这类工具它的配置项和 OpenAI 客户端不同需要单独配置 Base URL、Key、Model ID 三件套。报错五OAuth相关报错某些编码工具默认走 OAuth 登录流程如果你用的是 API Key 模式需要在配置里显式关闭 OAuth 或选择 API Key 认证方式。检查工具的配置文件确认认证方式选的是 API Key。报错六工具方法被调用但返回空原因Param的 name 没解析出来。要么开启-parameters编译参数要么在Param(name location, description ...)里显式写 name。我建议直接开-parameters省事。报错七Solon 启动后 SpringBoot 的 Bean 注入失败原因Solon.start在PostConstruct里执行此时 Spring 容器还没完全就绪。解决把init方法的 Bean 收集逻辑和Solon.start分开确保ListIMcpServerEndpoint已经注入完成。上面配置类里init方法返回this就是为了保证 Bean 初始化顺序。报错八端口冲突Port 8080 was already in use。改application.yml的server.port同时改客户端连接的地址。别只改一边。排查顺序建议先看启动日志有没有Solon started再看/mcp/demo1/sse能不能连上最后看工具调用返回。三步定位基本能覆盖 90% 的问题。6. 继续验证与接入入口服务端跑通后下一步是把它接到真实的模型调用链里。如果你只是验证工具注册用curl或 Java 客户端就够了。如果你想看模型如何自主选择工具、组织回答就需要一个模型入口。TaoToken 的模型对话入口在 https://taotoken.net/models 你可以在这里选一个响应快的模型做验证。API Key 在 https://taotoken.net/api-keys 创建。接入文档在 https://taotoken.net/doc 里面有 OpenAI 兼容调用的完整说明。如果你打算把 MCP 服务端用于长期编码或 Agent 场景可以看 Coding Planhttps://taotoken.net/coding-plan 。它面向持续调用做了优化适合把 MCP 工具链嵌进日常开发流程。最后给一个实用技巧MCP 服务端的工具方法尽量保持「无状态、幂等、返回快」。模型调用工具时会有超时限制如果工具方法里做了重查询或远程调用很容易超时导致模型侧报错。我踩过的坑是工具方法里查了一次慢 SQL结果模型等不到返回就断了。后来把查询改成缓存问题消失。验证顺序再强调一遍先curl打 SSE 确认端点活再发tools/call确认工具注册最后接模型确认自然语言调用。三步都过最小可用 MCP 服务就跑通了。
阅读完成 · 觉得有帮助?