Spring AI MCP服务器Boot Starter实战:用@Tool暴露业务能力

发布时间:2026/10/5 7:35:11
Spring AI MCP服务器Boot Starter实战:用@Tool暴露业务能力 1. 从一行依赖说起MCP 服务器 Boot Starter 到底解决了什么问题Spring AI 和 MCP 这两个词放在一起很多人第一反应是又一个新概念要学了。但如果你真正动手写过一次 AI 应用集成就会明白 MCPModel Context Protocol模型上下文协议的出现不是锦上添花而是把过去那套每个模型对接一套私有协议的混乱局面彻底收拾干净了。MCP 解决的核心问题特别朴素模型需要访问外部数据和工具但总不能每接一个数据源就写一套定制适配器维护成本直接失控。Spring AI 的 MCP 服务器 Boot Starter 就是在这个背景下被推到台前的。它的定位非常清晰让你用 Spring Boot 那一套熟得不能再熟的方式快速把某个能力暴露成 MCP 服务器供 MCP 客户端比如支持 MCP 的 AI Agent、IDE 插件等调用。只要引入spring-ai-starter-mcp-server-webmvc或spring-ai-starter-mcp-server-webflux依赖把工具方法写出来再加上几行配置一个 MCP 服务器就起来了。整个过程不开新框架、不换语言、不搞独立部署还是你熟悉的应用工程只是多了一个 MCP 协议栈的角色。这个东西适合谁我总结下来是三类人。第一类是后端 Java 工程师想把公司内部已有的业务能力查询订单、权限校验、数据仓库访问等暴露给 AI Agent 使用又不想专门维护一个 Python 或 Node 的 MCP 服务。第二类是搞 AI 应用编排的开发者需要自建 MCP 服务器来对接内部知识库、数据库、第三方 API同时对数据类型和鉴权有强诉求。第三类则是在做技术预研的架构师评估 Spring AI 生态能不能作为企业 AI 能力落地的底座。说得再直白一点只要你的技术栈是 Java、Spring Boot又想让 AI 模型能调用你手里的能力这个 Starter 就是目前最顺手的路径。2. MCP 服务器 Boot Starter 的核心机制拆解2.1 MCP 协议在 Spring AI 里的具体落点要理解 Boot Starter先得理解 MCP 协议本身在 Spring AI 生态里扮演什么角色。MCP 协议定义了客户端Client与服务器Server之间的通信规范核心是工具Tool的发现、调用和结果返回。Spring AI 的 MCP 支持有点特殊——它既做了 MCP 客户端适配让你的 Spring AI Agent 能调用外部 MCP 工具也做了 MCP 服务器封装让你的 Spring Boot 应用成为可以被外部 AI 客户端调用的 MCP 服务器。这个 Boot Starter 管的就是后者。从通信层面看Spring AI 实现了两套 MCP 传输方案基于 Servlet 的 WebMVC 和基于响应式 WebFlux。选 WebMVC 代表你走的是传统同步编程模型选 WebFlux 则意味着底层走 Netty。具体怎么选后面我会给出一张决策对照表。但要先记住一个核心逻辑MCP 服务器的工具就是 Spring Bean 里的公开方法Spring AI 通过Tool注解和方法签名来自动生成 MCP 工具描述客户端完成发现后就能直接调用。这里有一个关键点很多人容易忽略MCP 协议里的工具本质上是给模型看的说明书 调用入口。模型本身不算代码它是通过阅读工具的 JSON Schema 描述来理解这个工具能干什么、参数是什么然后决定要不要调用。所以Tool注解里那行描述文字description写得清不清楚直接决定了模型会不会正确触发这个工具。项目里最容易犯的毛病就是方法写得没问题参数类型也对唯独 description 写得太敷衍结果模型在场景里反复试错就是不调用。后面我会拿真实案例展开讲。2.2 为什么选择用 Spring Boot 承载 MCP 服务器在过去很长一段时间里MCP 服务器的参考实现大多集中在 TypeScript 和 Python 上Java 这边相对冷门。用 Spring Boot 承载 MCP 服务器有个天然优势几乎不需要引入额外的基础设施思维。你的应用本来就有 Controller、Service、数据源、事务管理、安全框架现在只是多了一层 MCP 协议映射。工具方法仍然是你熟悉的 Service 类方法只不过被暴露为 MCP 工具。另一个容易被低估的优势是 Spring 的依赖注入和管理能力。打个比方MCP 工具如果是手那 Spring 容器就是给手供血的血液循环系统。A 工具要查订单库B 工具要读 Redis 缓存C 工具要调外部 HTTP 接口这些依赖关系在 Spring 里本来就已经被管理得明明白白。如果换成脚本语言写 MCP 服务器这些胶水逻辑全得自己手工组装。尤其在企业内部服务之间互相调用、鉴权、超时、限流这些横切逻辑Spring 的 Filter、Interceptor、AOP 都能直接复用。还有一点在选型时必须考虑到团队的技术惯性。如果团队主力是 Java 后端让他们用 Python 重写一套 MCP 服务学习成本和维护成本都会被放大。用 Spring AI 的 Boot Starter至少工程结构、打包方式、部署流程都不会让团队觉得陌生。我见过不少团队在评估阶段纠结到底是上 Dify 还是自研 Spring AI其实完全可以考虑组合方案——外部的编排平台负责流程内部的业务能力用 MCP 服务器暴露出去。这个方向在 RuoYi-Vue-Pro 这类后台管理框架合并 MCP 功能的趋势里也能看到影子。2.3 Boot Starter 与原生 MCP Server 的差异对比很多人会问既然 MCP 协议是标准化的那我直接用官方的 MCP Java SDK 写行不行当然行但 Boot Starter 的价值在于把先配置后启动这套流程高度自动化了。我列一个对比表方便理解对比维度官方 MCP Java SDK 手写Spring AI MCP Server Starter工具注册方式手动创建 ToolSpecification 列表Tool注解自动扫描生成传输层配置手动初始化 HTTP 端点或 stdio 通道Starter 自动配置按依赖自动选择 WebMVC/WebFluxSpring 集成需要自行桥接 Bean 调用工具方法天然承载于 Spring Bean配置管理自行读取环境变量和配置类复用 Spring Boot 的 application.yml 体系部署形态可打包独立 Jar仍然是 Spring Boot 应用打包部署照旧调试成本日志和端点需自行处理可复用 actuator、日志体系链路一目了然一句话总结SDK 是零件Starter 是整车。你要去的目的地暴露 MCP 工具一样但 Boot Starter 让你少折腾很多安装环节。3. 实操落地从零到一搭建一个可用的 MCP 服务器3.1 工程初始化与依赖选型要点我假设你已经有基础的 Spring Boot 工程Spring Boot 版本建议 3.3.x 以上Java 建议 17 或 21Spring AI 版本直接用当前稳定版例如 1.0.0 GA 之后的版本线。下面是 Maven 依赖的核心配置直接放进pom.xmldependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version${spring-ai.version}/version /dependency这里有个选择要先做WebMVC 还是 WebFlux。如果你当前工程中已经有大量同步 JDBC 访问、事务管理、Servlet API 相关代码选 WebMVC 是最稳的。如果你的工程本身就是响应式堆栈WebFlux R2DBC那就选spring-ai-starter-mcp-server-webflux避免在同一个项目里混用两套编程模型。我见过最典型的翻车现场是有人在 WebMVC 工程里强行引入 WebFlux 版 Starter结果启动时因为响应式和 Servlet 容器冲突直接报错。记住不是越新越好是越匹配越好。还有一点要提前规划好MCP 服务器的暴露方式。MCP 协议在官方定义里支持 stdio标准输入输出适合本地子进程和 HTTP基于 Streamable HTTP 传输适合远程服务。Spring AI 的 Starter 走的是 HTTP 路线默认路径是/mcp。如果你是要给本地 IDE 的 MCP 客户端用那直接起这个服务在客户端配置里填写 HTTP 地址即可如果是给同机部署的 Agent 当子进程用记得调整传输方式或者用配套的 Java 客户端包。多数人用的是 HTTP 远程方案所以下面都以 HTTP 为例。3.2 用 Tool 注解暴露第一个 MCP 工具依赖配好后最核心的工作就是写工具类。我直接给一个已经跑通的示例结构很清晰一个普通的Service类里面放一个Tool方法。Service public class WeatherToolService { Tool(description 根据城市名称查询当前天气情况输入城市名返回天气描述和温度) public String getWeather(String city) { // 这里在真实场景中可以调用内部天气服务或第三方 API if (北京.equals(city)) { return 北京晴25℃东南风2级; } else if (上海.equals(city)) { return 上海多云28℃东风3级; } return city 暂无数据; } }就这么简单。不要觉得不可思议MCP 工具暴露的本质就是这么朴素——一个带注解的 Spring Bean 方法。Starter 启动时会把工具类里所有带Tool注解的方法扫描出来通过 MCP 协议自动把工具名方法名、描述description 字段、参数 Schema方法参数推导发布出去。description字段的重要性我刚才已经强调过这里再补充一个细节这个描述不仅会被模型用来理解工具意图还会直接影响工具名在客户端列表里呈现的排序和可辨识度。建议描述语句里带上场景 输入要求 典型示例。比如上面的例子输入城市名四个字看起来简单但实际能在模型决策时降低不少误调用概率。如果描述写天气查询三个字模型面对帮我看看上海天气如何这种请求时仍然能正确映射到工具但面对明天上海冷不冷这类变体时可能就会犹豫。描述越明确模型的映射准确率越高。参数这块呢Tool方法支持基础类型、String、复杂对象、ToolParam注解。当你需要为某个参数补充描述时用ToolParamTool(description 查询用户订单列表) public ListOrderInfo queryUserOrders( ToolParam(description 用户唯一标识通常为手机号或用户ID) String userId, ToolParam(description 查询起始时间格式 yyyy-MM-dd, required false) String startDate) { // 业务逻辑 }required false表示这个参数模型可以不传。这在真实场景里非常有用——不是每个请求都会带上全部过滤条件多给模型一个宽容的参数约束工具的可用性会高不少。3.3 配置项逐项解读与推荐设置Spring AI 的 MCP Server Starter 在application.yml里有一批可配置项。我挑实际项目中作用最大的一批讲配置结构如下spring: ai: mcp: server: name: my-mcp-server version: 1.0.0 base-path: /mcp sync: true tool-callback-timeout: 60s tool-execution-timeout: 30s每个配置项作用拆开讲。name是 MCP 服务器名称会出现在客户端的服务列表里。建议和业务模块名对齐比如order-mcp-server、user-mcp-server后期多个 MCP 服务器并存时一目了然。version是协议版本标识建议跟随应用版本号走方便排查客户端缓存的服务定义是否过期。base-path是 MCP 端点的访问路径前缀默认/mcp。如果你公司对 URL 路径有统一规范比如统一加/api前缀这个配置会派上用场。sync这个参数值得多说两句。它控制服务器在执行工具调用时是否采用同步模式。默认 true即客户端发出工具调用请求后服务器执行完方法才返回结果。如果设置为 falseSpring AI 会使用异步模式把工具调用作为一个 Future 后台执行调用结果通过流式响应继续返回这适用于耗时较长的工具比如调用外部 AI 模型的工具本身。大部分内部工具方法都是秒级返回保持默认即可。tool-callback-timeout和tool-execution-timeout分别是两阶段的超时控制。前者是客户端通知服务器我要调用工具这个握手环节的超时时间后者是工具方法实际执行的超时时间。如果你的工具方法涉及外部 API 调用或数据库批量操作建议把tool-execution-timeout调大到 60 秒以上否则长任务会被直接掐断。3.4 在 Boot Starter 里安全地接入外部 API真实项目中MCP 工具不可能只返回写死的假数据大概率要调用外部服务。以对接大模型 API 为例比如使用 qwen3.7 这类模型接口你的 MCP 工具方法内部可以注入 Spring 的RestTemplate或WebClient来发起 HTTP 请求。谨慎起见贴一个通过RestClient调用外部 API 的典型写法Service public class AiQwenToolService { private final RestClient restClient; public AiQwenToolService(RestClient.Builder restClientBuilder) { this.restClient restClientBuilder .baseUrl(https://api.example.com/v1) .defaultHeader(Authorization, Bearer System.getenv(AI_API_KEY)) .build(); } Tool(description 调用大模型生成文本输入提示词 prompt返回生成结果) public String generateText(String prompt) { MapString, Object requestBody Map.of( model, qwen3.7, messages, List.of(Map.of(role, user, content, prompt)), temperature, 0.7 ); ResponseEntityMap response restClient.post() .uri(/chat/completions) .body(requestBody) .retrieve() .toEntity(Map.class); // 解析返回结果... return parseContent(response.getBody()); } }这里有三条实战经验值得记住。第一API Key 绝对不要硬编码在代码或 yml 里必须走环境变量或配置中心。MCP 服务器一旦暴露到公司内网甚至公网密钥泄露的代价很高。第二每个工具方法最好做输入校验尤其是 prompt、text 这类自由文本参数长度限制和敏感词过滤都要兜底。第三考虑对高频工具做本地缓存或结果缓存因为模型 Agent 经常会对同一个问题反复调用工具缓存能显著降低外部 API 的成本压力。3.5 数据源接入让 MCP 工具具备数据库查询能力MCP 工具最常见的用途就是让 AI 能查数据。比如内部运营人员通过 AI Agent 直接问上个月华东区销售额是多少Agent 自动调用 MCP 工具工具内部执行 SQL返回结果。这种能力用 Boot Starter 实现核心代码仍然简单得让人意外Service public class SalesQueryToolService { private final JdbcTemplate jdbcTemplate; public SalesQueryToolService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Tool(description 查询指定区域、指定月份的销售额汇总输入区域名称和月份(格式YYYY-MM)返回销售额) public String querySales(String region, String month) { String sql SELECT COALESCE(SUM(amount), 0) FROM sales WHERE region ? AND month ?; BigDecimal total jdbcTemplate.queryForObject(sql, BigDecimal.class, region, month); return 区域 region 在 month 的销售额为 total 元; } }注意JdbcTemplate是 Spring Boot 自动配置好的 Bean你只需要在构造器里声明依赖剩下的交给容器。工具方法内部其实就是普通的 Spring JDBC 编程没有任何魔法。但我必须提醒一点MCP 工具开放数据库查询能力相当于把能执行 SQL 的入口暴露给模型。模型本身没有安全意识如果工具描述让它根据用户问题自由生成 SQL 并执行结果会非常危险。正确做法是参数里只接收区域月份这类有明确枚举和格式要求的字段SQL 逻辑完全由你自己写死模型只负责传递参数。永远不要把 raw SQL 或完整查询条件作为工具参数暴露出去。这句经验值一条命。4. 实战中的疑难杂症与排查技巧4.1 客户端连不上 MCP 服务器先查这五处我在实际联调中遇到过很多客户端报错Failed to connect to MCP server的情况排查顺序基本固定。第一确认服务端口与路径。Spring Boot 应用默认端口 8080MCP 端点路径默认/mcp那么客户端应该连接http://localhost:8080/mcp。如果端口被占用改了 8081客户端配置没同步改自然连不上。第二确认 Spring AI 的 MCP 自动配置是否真的生效。启动日志里搜McpServer关键词如果没有任何相关日志很可能是 Starter 版本与 Spring Boot 版本不兼容导致自动配置没加载。第三确认引入了正确的传输模块。如果你用的是 WebMVC Starter而不能同时引入 WebFluxSpring Boot 启动时可能会因为容器冲突报错。反过来也一样。第四检查防火墙与网络策略。如果 MCP 服务器部署在服务器上而客户端在本地 IDE那8080端口必须在防火墙白名单里而且服务器不能只监听127.0.0.1而要监听0.0.0.0。这个问题最常见也最容易被忽略。第五检查 JSON 序列化问题。MCP 客户端拿到的工具 Schema 是 JSON 格式如果你的Tool方法参数里有LocalDateTime、BigDecimal这类特殊类型且没有配置对应的序列化器客户端解析 Schema 时会报错。建议工具方法参数尽量用基础类型、String、简单 POJO。4.2 工具调用超时的两个隐藏诱因刚讲到tool-execution-timeout配置项实际项目里工具超时不一定是因为配置不够。我遇到过两个隐藏诱因写出来给大家避坑。第一个是线程池耗尽。WebMVC 模式下如果同时有大量 HTTP 请求打进来工具方法在默认的 Tomcat 线程池里执行线程池打满后后续请求进入等待队列表现为工具调用迟迟不返回而不是直接报超时。这时候排查thread dump能看到大量线程处于WAITING状态。解决方向是把工具方法内部的外部调用改为异步化或者单独为耗时工具创建一个独立的线程池通过Async注解隔离。第二个是外部 API 没有设置连接超时。如果你的工具方法内部用HttpClient调用第三方接口而只设置了读超时却没设置连接超时当外部服务 IP 不可达时连接迟迟建不起来这个时间会直接计入工具执行时间最终导致 MCP 层超时。经验做法是连接超时 3 秒读超时 30 秒两个都设不要只设一个。4.3 模型不调用工具问题大概率出在描述上这是最磨人的问题MCP 服务器状态正常Schema 也能在客户端里看到但模型就是不调用某个特定工具。我的排查经验是——问题八成出在工具描述与用户问题的语义匹配上。举个例子我做一个订单查询工具description 写的是查询订单详情结果用户问我的快递到哪了模型死活不调用这个工具。后来我把描述改成查询订单详情包括订单状态、物流信息、快递单号用于回答物流进度相关问题模型立刻就能匹配上。原理很简单模型调用工具的过程实质上是语义匹配描述里没有出现与用户问题相关的关键词模型就没法建立起联系。另一个常见场景是参数描述不清晰。比如工具接受一个status参数但没写可选值已发货已完成这些词模型看不懂调用时传了错误枚举值工具执行返回空结果模型就会放弃使用工具。建议所有参数描述都明确枚举范围和默认值宁可啰嗦一点不要惜字如金。4.4 从 Dify 工作流迁移到 Spring AI 的衔接思路热词里提到dify 工作流转成 spring ai java 代码这个方向最近问的人特别多。坦率说Dify 工作流本质是可视化编排Spring AI 是代码化编排两者不能自动等价转换但可以用一种衔接思路来平滑迁移。Dify 工作流里的工具节点往往是对外 API 的调用。你在 Dify 里配置的工具到了 Spring AI 这边就直接变成Tool方法。Dify 里的 LLM 节点对应 Spring AI 里的ChatClient。Dify 里的条件分支对应 Java 代码里的if-else或规则引擎。所以迁移时建议分成三个步骤第一步把 Dify 里的外部工具清单整理成方法清单第二步把每个方法用 Boot Starter 暴露为 MCP 工具第三步用ChatClient编写 Agent 逻辑让它通过 MCP 客户端自动发现并调用这些工具。这个方案的优点是中间过程每一层都是标准化的。即便以后不再用 Spring AI 做编排这些 MCP 工具仍然可以被其他支持 MCP 的 AI 客户端复用。工具能力与编排逻辑彻底解耦这在工程上是一个非常稳的设计。5. 几个值得认真对待的选型观察5.1 多 MCP 服务器架构的组织方式当公司内部有多个团队各自通过 Boot Starter 暴露 MCP 服务器后很快会面临服务治理问题。比如订单团队起了一个order-mcp-server用户团队起了user-mcp-serverAI 侧如何知道该连哪个一种务实的做法是注册中心模式把各 MCP 服务器的 HTTP 地址、名称、负责人维护在一个统一的配置平台或 Nacos 配置里AI 应用启动时读取注册列表按需建立 MCP 客户端连接。Spring AI 的 MCP 客户端 Starter 支持定义多个服务器连接只需在配置里列出各自的地址即可。这一步的花费很小但为未来扩展预留了空间。5.2 Spring AI 2.0 与百炼 Qwen3.7 的兼容性观察热词里提到spring ai 2.0 连接百炼 qwen3.7这说明很多人正在把 Spring AI 与国产大模型服务对接。从实践经验看Spring AI 的模型接入层是高度可插拔的ChatClient可以对接到不同模型的 Endpoint。百炼平台提供的模型接口遵循 OpenAI 风格协议Spring AI 对这类兼容接口的适配比较成熟只要配置好base-url、api-key、model名称即可正常使用。如果你用 Boot Starter 构建 MCP 服务器再配合 Spring AI 的客户端调用百炼 qwen3.7可以实现一个完整的闭环用户在对话里提出需求qwen3.7 判断需要调用哪个工具通过 MCP 协议访问企业内部服务拿到结果后组织自然语言回答。这个链路正好是当前 AI Agent 落地的主流形态。配置层面没有玄学就是两个 Starter 叠加使用一个管客户端一个管服务器端互不干扰。5.3 关于 spring-ai-alibaba 的维护与生态选择热词里提到spring ai alibaba停更了吗这里说一点观察。技术生态的项目是否停更其实不应该成为选型的唯一依据。判断一个技术方案落地是否靠谱要看三件事协议是否标准MCP 是标准协议、核心依赖是否稳定Spring AI 主干线非常活跃、替代路径是否存在就算某个封装层停更你仍然可以退回原生 MCP SDK 自己维护一层薄封装。MCP 的价值恰恰在于协议标准化——上层组件怎么变底层对接逻辑都不需要推翻重来。所以我的建议是不要太纠结某个 Alibaba 封装模块的更新状态重点看自己团队对 Spring AI 主干的能力掌握。6. 把这些经验落到你自己的项目里到这里Spring AI 的 MCP 服务器 Boot Starter 从原理、实操到排障已经基本走了一遍。我个人实际操作中的体会是这个 Starter 把 MCP 服务器的搭建成本压得非常低真正的复杂度从来不在框架而在业务边界的定义——哪些方法应该暴露成工具工具的入参和描述怎么设计如何保证模型不会拿到它不该拿的数据。这些决策没有标准答案只能靠对业务的理解去权衡。最后再分享一个我自己的习惯每暴露一个 MCP 工具我都会在本地用 MCP 客户端实测一遍不光测正常流程还要测错误输入。比如工具要求传数字我故意传字符串工具要求传合法枚举我故意传一个不存在的值看服务器返回什么错误码。AI 模型可能制造出你意想不到的入参提前把边界测好上线后能省下大量的排查时间。这比任何框架技巧都更实用。