SpringBoot3 集成 SolonMCP 开发 MCP 服务:从依赖配置到接口验证

发布时间:2026/10/2 20:23:56

SpringBoot3 集成 SolonMCP 开发 MCP 服务:从依赖配置到接口验证 1. SpringBoot3 项目里为什么还要引入 SolonMCP如果你手上已经有一个跑得好好的 SpringBoot3 后端现在产品或者架构那边提了个需求把现有的几个业务方法暴露成 MCP 工具让 Claude Code、Cline 这类客户端能直接调用。第一反应可能是上 Spring AI但真动手会发现两件事一是 Spring AI 的 MCP 模块版本迭代快二是把已有 Service 改造成工具类要动不少注解和配置。这时候 SolonMCPsolon-ai-mcp就是一个很轻的替代路径——它本身是 Solon AI 的扩展但支持内嵌到 SpringBoot3 里用注解把普通 Bean 标成 MCP 端点不需要你把整个项目迁到 Solon 体系。MCP 是什么用一句话说它是一个让大模型客户端按标准协议发现并调用你服务端能力的通道。你写一个getWeather方法客户端就能在工具列表里看到它传参调用拿到返回值。适合谁适合那些已经有 Java 后端、想快速给 AI 编码工具或 Agent 暴露内部接口的团队尤其是你不想为了一个 MCP 能力去重构整个依赖树的情况。我试过在 SpringBoot3.2 JDK17 的环境里把 SolonMCP 嵌进去整体感受是依赖只多两个包配置类一个工具类按普通 Spring 组件写启动后通过 SSE 端点验证注册结果。下面按依赖配置、初始化、工具方法、验证请求、排错、联调通道的顺序走一遍每一步都给可复制的代码。需要先明确一点SolonMCP 在 SpringBoot3 下和 SpringBoot2 的差别主要就是 servlet 容器那层依赖包名不同因为 Jakarta EE 改名了。SpringBoot3 用solon-web-servlet-jakartaSpringBoot2 用旧的solon-web-servlet-javax。其余 API 基本一致所以 SpringBoot2 的经验可以平移过来。2. 前置准备依赖、目录与 SolonMCP 初始化配置2.1 pom.xml 里加什么依赖SpringBoot3 项目引入 SolonMCP核心是两个依赖solon-ai-mcp提供 MCP 服务端能力solon-web-servlet-jakarta负责把 Solon 的 Servlet 过滤器挂到 SpringBoot3 的 Jakarta 容器上。版本号建议用当前稳定版下面给一个可复制的片段properties java.version17/java.version solon.version3.0.5/solon.version /properties dependencies !-- SpringBoot3 Web 基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- SolonMCP 核心提供 McpServerEndpoint、ToolMapping 等注解 -- 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这里有个容易踩的坑如果你从 SpringBoot2 的项目复制依赖很可能带进来的是solon-web-servlet-javax在 SpringBoot3 下启动会报ClassNotFoundException: javax.servlet.Filter。因为 SpringBoot3 全面切到jakarta.servlet所以必须换成 jakarta 版本。这也是 SpringBoot3 和 SpringBoot2 使用 SolonMCP 唯一的依赖差异。2.2 目录结构建议为了让 SpringBoot 的组件扫描和 Solon 的端点收集不打架建议单独开一个包放 MCP 相关类src/main/java/com/example/demo/ ├── HelloApp.java // 启动类 └── mcpserver/ ├── IMcpServerEndpoint.java // 标记接口 ├── McpServerConfig.java // 初始化配置 └── tool/ └── McpServerTool.java // 具体工具端点2.3 标记接口与初始化配置先定义一个空接口作用只是让 Spring 能按类型收集所有 MCP 端点组件package com.example.demo.mcpserver; public interface IMcpServerEndpoint { }然后是配置类它做三件事启动 Solon 生命周期、把收集到的端点转成McpServerEndpointProvider、注册 Solon 的 Servlet 过滤器到/mcp/*package com.example.demo.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) { // 注意SpringBoot 下必须用 Spring 的 AnnotationUtils // 否则拿不到 McpServerEndpoint 注解 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用的是org.springframework.core.annotation.AnnotationUtils不是 Solon 自带的那个。因为 Spring 的代理机制会给 Bean 生成子类直接getClass().getAnnotation()可能拿不到注解必须用 Spring 的工具去查父类。这个点我在第一次写的时候没注意结果端点一直注册不上日志里也没有报错排查了半天。3. 可复制配置MCP 工具方法、SSE 端点与 mcpserver.yml3.1 工具端点类怎么写工具类就是一个普通 Spring 组件实现刚才的标记接口加上McpServerEndpoint注解。注解里的name是端点名sseEndpoint是客户端连接的 SSE 路径package com.example.demo.mcpserver.tool; import com.example.demo.mcpserver.IMcpServerEndpoint; import org.noear.solon.ai.chat.message.ChatMessage; import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint; import org.noear.solon.ai.mcp.server.annotation.Param; import org.noear.solon.ai.mcp.server.annotation.PromptMapping; import org.noear.solon.ai.mcp.server.annotation.ResourceMapping; import org.noear.solon.ai.mcp.server.annotation.ToolMapping; import org.springframework.stereotype.Component; 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这个注解别用错Solon 里也有同名的Component如果你 import 成了org.noear.solon.annotation.ComponentSpring 扫描不到端点就不会注册。建议在 IDE 里确认 import 是org.springframework.stereotype.Component。3.2 编译参数与参数名Param注解里写了 description但参数名location、user_id要能被反射拿到需要在编译时保留参数名。Maven 里加build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration parameterstrue/parameters /configuration /plugin /plugins /build如果不加-parameters客户端调用时可能报参数缺失或者你得在Param里显式写name location。两种方式都行但加编译参数更省事。3.3 mcpserver.yml 配置Solon 启动时会读--cfgmcpserver.yml这个文件放在src/main/resources下。最小配置如下solon: app: name: demo-mcp-server # 关闭 Solon 自带的 HTTP 服务只借用它的 MCP 能力 # 因为 Web 容器由 SpringBoot 提供 server: port: 0把 Solon 自己的 server 端口设为 0 或者干脆不启用避免和 SpringBoot 的 8080 冲突。MCP 的请求实际是通过 SpringBoot 的 Servlet 容器进来的Solon 只负责处理/mcp/*路径下的协议逻辑。3.4 启动类启动类保持标准 SpringBoot 写法即可package com.example.demo; 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); } }启动后SpringBoot 监听 8080Solon 的过滤器挂在/mcp/*客户端连http://localhost:8080/mcp/demo1/sse就能拿到工具列表。4. 验证请求确认工具注册与调用是否生效4.1 用 curl 看 SSE 端点启动应用后先确认 SSE 端点能连上。MCP 的 SSE 是长连接用 curl 加-N禁用缓冲curl -N http://localhost:8080/mcp/demo1/sse正常的话会看到类似event: endpoint和data: /mcp/demo1/message?sessionIdxxx的输出说明端点已注册客户端可以拿这个 sessionId 去发消息。4.2 写一个 Java 客户端验证工具调用更直观的方式是写个测试类用McpClientProvider连上去调工具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); // 资源读取 String version toolProvider.readResourceAsText(config://app-version).getContent(); System.out.println(资源返回: version); } }跑起来应该输出工具返回: 晴14度 资源返回: v3.2.0如果callToolAsText返回空或者抛异常先检查getWeather方法上的ToolMapping是否被扫描到再看McpServerConfig里的init方法有没有真的执行——可以在里面加一行日志打印serverEndpoints.size()。4.3 把 MCP 客户端当 LLM 工具集用SolonMCP 还支持把 MCP 客户端直接挂到 ChatModel 上让模型自己决定调哪个工具。本地测试可以用 Ollamaimport 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(http://127.0.0.1:11434/api/chat) .provider(ollama) .model(qwen2.5:1.5b) .defaultToolsAdd(toolProvider) .build(); ChatResponse resp chatModel.prompt(杭州今天的天气怎么样).call(); System.out.println(resp.getMessage()); } }模型会先调getWeather拿到「晴14度」再组织成自然语言回答。这一步验证的是工具注册和模型调用链都通了。5. 本篇常见错排查401、local proxy failed 与 reading choices5.1 启动报 ClassNotFoundException: javax.servlet.Filter这是 SpringBoot3 下最常见的错原因就是依赖引成了solon-web-servlet-javax。检查 pom确保是artifactIdsolon-web-servlet-jakarta/artifactIdSpringBoot3 的spring-boot-starter-web带的是jakarta.servlet-api两者命名空间必须一致。5.2 端点注册不上客户端连 /mcp/demo1/sse 返回 404先看McpServerConfig里的init方法有没有被调用。如果ListIMcpServerEndpoint是空的说明 Spring 没扫描到McpServerTool。检查两点一是Component的 import 是不是 Spring 的二是McpServerTool所在的包是否在SpringBootApplication的扫描范围内。还有一种情况是AnnotationUtils用错了包。必须用org.springframework.core.annotation.AnnotationUtils用 Solon 的AnnotationUtils在 Spring 代理下拿不到注解anno为 null 就直接 continue 了端点静默丢失。5.3 客户端调用报 401 或 local proxy failed如果你把 MCP 服务端地址指向了远程统一通道比如 TaoToken 的 API 通道出现 401 通常是 Key 没带或者带错位置。MCP 客户端配置里要同时给全三件套Base URL、API Key、Model ID。以 Cline 或 Claude Code 这类客户端为例配置片段长这样{ mcpServers: { demo1: { url: https://taotoken.net/api/mcp/demo1/sse, headers: { Authorization: Bearer sk-你的Key } } } }local proxy failed一般是客户端本地代理层连不上目标地址先确认url能不能在浏览器或 curl 里通再确认 Key 有没有过期。如果是 Codex 的auth.json方式字段名要对齐{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: claude-sonnet-4-5 }5.4 报 reading choices 或返回体解析失败reading choices这类错通常出现在把 MCP 端点和 OpenAI 兼容接口混用的时候。MCP 的 SSE 返回的是事件流不是choices结构。如果你用 OpenAI SDK 去请求 MCP 的/sse路径解析器会找不到choices字段而报错。正确做法是MCP 客户端用McpClientProvider或支持 MCP 协议的客户端只有走 LLM 对话接口时才用 OpenAI 兼容格式两者路径不同。5.5 OAuth 相关报错部分客户端在连远程 MCP 时会尝试 OAuth 流程如果服务端没开对应能力会报OAuth discovery failed之类。这种情况下在客户端配置里显式指定用 Bearer Token 认证跳过 OAuth 发现。TaoToken 的 API 通道用统一 Key 即可不需要额外 OAuth 配置。6. 把服务端地址切到 TaoToken 统一通道联调本地验证通过后下一步通常是把 MCP 服务端暴露到能被外部客户端访问的地址。如果你不想自己维护公网入口和 Key 管理可以把请求通道切到 TaoToken 的统一 Key/API 通道。它的 API 入口是https://taotoken.net/api官网是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end。具体做法分两步。第一步在 TaoToken 控制台创建一个 API Key路径是 console 里的 API Keys 页面。第二步把客户端配置里的url从http://localhost:8080/mcp/demo1/sse换成统一通道地址并在 headers 里带上 Key。这样你的 SpringBoot3 服务端不用改代码只是客户端连接的目标变了。联调时建议先用模型对话页面确认 Key 本身可用再切到 MCP 客户端。如果对话能通、MCP 报 401那问题就在 MCP 客户端的 header 配置而不是 Key 本身。长期跑编码 Agent 或需要稳定调用额度的场景可以看下 Coding Plan 的额度方案比按次调用更适合高频工具调用。最后留一个实操建议MCP 工具方法的返回值尽量保持简单字符串或小 JSON别在工具里做重业务查询。因为客户端调用工具是同步等待的一个慢查询会拖住整个对话轮次。把重逻辑放到异步任务里工具方法只负责触发和返回任务 ID这样联调时不容易超时。
延伸阅读

更多相关文章

2026/10/2 20:23:56

工厂可视化电子看板多屏数据不同步:根因排查与同步机制设计

车间里挂着八块电子看板,计划员、班长、质检各看各的,最怕的就是两块屏幕上同一工序的产量数字对不上。去年我在客户现场调一个可视化电子看板项目,前后折腾了大半个月,问题恰恰就出在“多块大屏数据不同步”上。 这类项目的技术…

2026/10/2 20:18:56

TXT批量转SHP全攻略:坐标转换与字段保留一次搞定

前阵子同事抱着一堆TXT文件来找我,说几百个监测点坐标要批量转换成SHP叠加底图分析。我看了眼文件名就明白怎么回事:外业设备导出的文本,表头五花八门,有的带度分秒,有的已经是十进制度,坐标系有没有写全全…

2026/10/2 20:18:56

从零搭建AI工程:环境配置、数据工程到模型部署的完整指南

从零开始搭一套自己的AI工程,我前后折腾了大半年。这里的"AI工程"(ai-engineering from scratch)不是指装个现成的模型跑一跑,而是从环境配置、数据收集、模型训练到部署维护,完整走通一条端到端的链路。这个…

2026/10/2 21:08:58

又见循环移位

题面https://codeforces.com/contest/2266/problem/D 题解https://www.luogu.com.cn/article/pk0sh4ce思路Code: void solve() {int n; cin>>n;vector<int>a(n1,0);for(int i1;i<n;i){cin>>a[i];a[i]-i;}sort(a.begin()1,a.end());a.erase(unique(a.begin…

2026/10/2 21:08:58

快乐8走势分析系统:从数据采集到回测的完整统计管线

简介&#xff1a;这是一套面向快乐8&#xff08;KL8&#xff09;彩票走势分析的个人学习型工具&#xff0c;采用Python工程化结构&#xff0c;可直接本地部署、开箱即用&#xff0c;适合具备基础Python能力、希望用数据化方式复盘历史开奖的爱好者。资源包共75个文件&#xff0…

2026/10/2 21:08:58

社交网络分析实验:从图论建模到PageRank与社区发现实战

简介&#xff1a;面向哈尔滨工业大学计算机课程实验的社交网络分析资料包&#xff0c;适用于计算机相关专业学生的课程设计与实验报告撰写&#xff1b;资料围绕社交网络分析展开&#xff0c;涵盖数据预处理、图论基础、算法编程实现、数据可视化、社区检测及中心性分析等关键知…

2026/10/2 21:03:58

两位五通电磁阀,三位五通电磁阀

目录两位五通电磁阀&#xff1a;先导式电磁阀和直动式电磁阀的区别&#xff1a;单电控和双电控区别&#xff1a;三位五通电磁阀&#xff1a;分类&#xff1a;中封&#xff0c;中泄&#xff0c;中压总结&#xff1a;两位五通电磁阀&#xff1a; 分类&#xff1a;直动式和先导式…

2026/10/2 8:16:46

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/10/2 18:20:53

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集&#xff1a;Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/1 10:48:55

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/2 0:02:57

PWN入门:从栈溢出原理到ROP链实战

1. 这不是“学PWN”&#xff0c;是重新理解你每天敲的每一行C代码我第一次在CTF赛场上写出能控制程序流的exp时&#xff0c;手抖得连gdb的c命令都输错三次。那道题只有23行C代码&#xff0c;一个gets()调用&#xff0c;一个printf()&#xff0c;一个return——它甚至没开NX&…

2026/10/2 0:02:57

Windows下cudaMallocHost显存占用之谜:WDDM与TCC模式差异及优化方案

1. 一个反直觉的显存占用现象第一次在 Windows 上看到cudaMallocHost把显存吃掉的时候&#xff0c;我的反应是打开任务管理器反复确认了三遍。明明调用的是主机端锁页内存分配&#xff0c;按 CUDA 文档的说法&#xff0c;这块内存应该落在系统 RAM 里&#xff0c;跟 GPU 的显存…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑