ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring AI + MCP Server落地实战:Java服务内嵌AI工具调用

Spring AI + MCP Server落地实战:Java服务内嵌AI工具调用 1. 项目落地与整体方案思路1.1 为什么要把 MCP Server 塞进 Java 服务里先说结论这几个月我一直在折腾 Spring AI 和 MCPModel Context Protocol的融合最近终于把一个基于 Java 的 MCP Server 正式跑进了生产环境配合阿里云百炼的 Qwen 系列模型实现了真正的“自然语言查订单、查库存、调内部接口”。这篇东西不是概念科普是完整的落地记录从依赖到代码再到一键部署踩过的坑我都标出来了。MCP 这套协议本质上是给大语言模型开了一扇门让模型不再只是“会聊天”而是能主动调用工具、读写数据源、操作外部系统。市面上大多数 MCP 项目都以 Python 和 Node.js 为主Java 阵营的教程少得可怜。但实际情况是国内大量企业的核心业务系统都是 Java 写的Spring Boot 遍地都是如果能让 AI 直接以 MCP 方式打到这些 Java 服务上那才是真正的“AI 落地”。Spring AI 官方从 1.0.0 版本开始就内置了 MCP 支持最新的 2.0.1 版本对模型接入、工具调用、Agent 编排的抽象又做了一轮重构。我这次用的是 Spring Boot 3.4 Spring AI 2.0.1把 MCP Server 和业务服务放在同一个进程里对外提供接入能力对内把 AI 变成了业务系统的“操作员”。1.2 这套方案的适用范围与核心价值这套骨架适合三类人第一类是后端 Java 工程师想给自己的 Spring Boot 服务加上 AI 工具调用能力又不想引入 Python 那套 MCP 生态第二类是想做智能客服、订单助手、运维机器人的团队需要 AI 直接读取业务库和调用内部服务第三类是研究 Spring AI Agent 的开发者想搞清楚 MCP Client 和 MCP Server 到底怎么配合。方案的核心价值有这么几条首先是开发成本低MCP Server 并不是一个独立的大系统而是 Spring AI 提供的一组 Starter你只需要在业务服务里加依赖、写几个注解方法服务启动后它就自动具备 MCP 协议能力其次是打通了“对话到操作”的闭环用户说“帮我查一下订单 SH20240088 的状态”模型会解析意图、选择工具、生成参数、调用方法、把结果翻译回自然语言整套链路在一个 JVM 进程里完成最后是可运维用 Docker 一键部署健康检查、日志采集、端口管理都按标准套路走。我个人的判断是MCP 在 Java 侧的普及度远低于 Python但这恰恰是机会因为真正握着业务数据和系统权限的就是 Java 服务。把 MCP Server 内嵌到 Spring Boot 里等于让 AI 直接坐在业务系统的驾驶座上。2. 环境准备与依赖配置2.1 版本选型与 JDK 环境注意事项先把版本讲清楚这一块最容易翻车。Spring AI 的 MCP 支持在不同版本里的 API 差别很大老教程基本不能直接抄。我用的是这套组合JDK 17要求最低 1717 以下编译都过不了、Spring Boot 3.4.5、Spring AI 2.0.1、Maven 3.9。Spring AI 2.0.1 是当前比较稳的版本支持把 Qwen、DeepSeek、Ollama 等模型通过 OpenAI 兼容协议接入同时 MCP 的 Server 端和 Client 端都是一等公民。JDK 建议直接配 17 或 21不要用 Java 8。原因很实际Spring Boot 3.x 就是基于 Jakarta EE 写的Java 8 跑不了另外 MCP SDK 内部用了大量 Java 17 的语法特性比如record、sealed interface如果你还在 Java 8 时代劝你先升级再往下看。环境变量方面Windows 用户设置JAVA_HOME后记得把%JAVA_HOME%\bin加进PATHLinux/macOS 用户用export JAVA_HOME/path/to/jdk17和export PATH$JAVA_HOME/bin:$PATH。检查是否配置成功就打开终端敲java -version看到 openjdk 17 的版本号就行。另外务必要装 Maven并且配置好阿里云镜像仓库下载依赖的速度完全不一样。# 验证 JDK 版本必须 17 java -version # 验证 Maven mvn -version # Maven 阿里云镜像配置settings.xml 的 mirrors 标签 mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror2.2 Maven 依赖清单与版本锁定Maven 这一块建议直接抄我的 pom 配置。先说必须引入的核心坐标Spring Boot 父 POM、Spring AI BOM、MCP Server Starter、MCP Client Starter还有可选的 Web Starter用来提供 SSE 传输端口。为什么需要同时引入 Server 和 Client因为这套方案里你的 Spring Boot 服务既是 MCP Server对外暴露工具给别的 AI 客户端调也可以作为 MCP Client去调用其他 MCP 服务。当前项目里我主要用了 Server 端把订单查询、库存查询这些方法暴露出去Client 端则配置了项目本身连大模型服务的 OpenAI 兼容端点。如果你只需要其中一种能力删掉另一个 starter 就行。依赖版本我强烈建议通过 Spring AI BOM 锁定不要自己手写版本号否则很容易出现McpServerProperties类找不到、方法签名对不上这类问题。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.5/version relativePath/ /parent properties java.version17/java.version spring-ai.version2.0.1/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies !-- Spring Web用于提供 SSE/HTTP 传输通道 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MCP Server 端核心 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependency !-- 可选MCP Client 端核心 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency !-- OpenAI 兼容协议的模型接入 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency !-- 如果你用百炼或其他 DashScope 服务也可以引入对应 starter -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-qwen/artifactId /dependency !-- 配置处理和校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies注意Spring AI BOM 的scopeimport/scope不能少。如果不写Spring AI 的依赖不会统一接管版本一旦某个子依赖版本不兼容后面查起来非常痛苦。2.3 application.yml 核心配置逐项解读配置是这套方案里最容易出错的部分我把每一行都解释清楚。核心配置分三块MCP Server 本身的配置、模型接入配置、MCP Client 配置。先看 MCP Server 这块spring: application: name: ai-order-center ai: mcp: server: name: order-center-mcp-server version: 1.0.0 transport: http enabled: true # 模型接入配置OpenAI 兼容协议方式走百炼 Qwen openai: base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen3-7b temperature: 0.2这里逐一说明spring.ai.mcp.server.name是 MCP 服务的标识客户端在工具列表里能看到它是谁transport支持http和stdio两种。stdio走标准输入输出适合子进程方式启动的本地 MCP Serverhttp走 HTTP 协议支持 SSE 通道适合部署成独立服务由远程客户端连接。生产环境我强烈建议用http因为客户端不需要和 Server 在同一个进程里运维和故障隔离都方便。模型配置里base-url指向阿里云百炼的 OpenAI 兼容入口api-key通过环境变量注入而不是硬编码在配置文件里。qwen3-7b是目前性能够用、成本又不高的一个选择。如果你手头有 DeepSeek API把base-url换成 DeepSeek 的兼容地址也一样Spring AI 这套抽象对模型服务商是不敏感的。2.4 依赖安装时最容易踩的版本坑我第一次引入 Spring AI 2.0.1 时Maven 直接报Cannot resolve org.springframework.ai:spring-ai-starter-mcp-server:2.0.1。原因很简单Spring AI 的正式版不在 Maven Central而是发布在 Spring 的私有仓库。所以光配阿里云镜像还不够必须在 pom.xml 里补上 Spring 仓库不然一堆依赖都拉不下来。repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url snapshots enabledfalse/enabled /snapshots /repository repository idspring-snapshots/id nameSpring Snapshots/name urlhttps://repo.spring.io/snapshot/url releases enabledfalse/enabled /releases /repository /repositories这里有个取舍问题如果只配 Spring 仓库国内下载速度会很慢如果只配阿里云Spring AI 的包又拉不到。我的做法是阿里云镜像放在mirrors里作为全局加速同时保留 Spring 官方仓库用于解析私有构件Maven 会先查镜像仓库镜像没有再去 Spring 仓库拉速度基本可接受。另一个高频坑是spring-ai-starter-model-openai和spring-ai-starter-model-qwen同时引入时自动配置类会冲突出现HttpClient之类的装配异常。我的建议是如果你走 OpenAI 兼容协议接入 Qwen只留model-openai就够了不要画蛇添足。3. 核心代码实现与 MCP 工具开发3.1 用 Tool 注解把 Java 方法变成 AI 工具Spring AI 的 MCP Server 最爽的一点是你不需要实现任何 MCP 协议的客户端代码只要在一个 Spring Bean 的方法上标注Tool这个方法就会被自动注册成 MCP 工具大模型通过协议就能调用它。我写了一个OrderQueryService模拟企业里的订单中心服务暴露两个工具一个按订单号查订单信息一个查某个客户最近订单。真实业务里你把这里的逻辑换成查数据库、调远程接口、写操作日志都行。package com.example.aiorder; import com.example.aiorder.model.OrderInfo; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; Service public class OrderQueryService { // 模拟订单数据实际项目中替换为数据库查询 private static final MapString, OrderInfo ORDER_STORE new ConcurrentHashMap(); static { ORDER_STORE.put(SH20240088, new OrderInfo( SH20240088, 张三, 手机支架, 29.9, PAID, LocalDateTime.now().minusDays(2))); ORDER_STORE.put(SH20240121, new OrderInfo( SH20240121, 李四, 机械键盘, 199.0, SHIPPED, LocalDateTime.now().minusHours(6))); } Tool(description 根据订单号查询订单当前状态包括订单号、购买人、商品、金额、状态和下单时间) public OrderInfo queryOrder( ToolParam(description 订单号例如 SH20240088) String orderNo) { OrderInfo order ORDER_STORE.get(orderNo); if (order null) { throw new IllegalArgumentException(订单不存在: orderNo); } return order; } Tool(description 查询指定客户最近 N 天的订单列表返回多条订单记录) public ListOrderInfo listRecentOrders( ToolParam(description 客户姓名例如张三) String customerName, ToolParam(description 查询天数默认 7 天) Integer days) { return ORDER_STORE.values().stream() .filter(o - o.customerName().equals(customerName)) .filter(o - o.createdAt().isAfter(LocalDateTime.now().minusDays(days))) .toList(); } }代码里有几个细节值得注意。首先是ToolParam上的description一定要填好因为大模型就是靠这个描述来理解参数含义的描述写得越清楚模型生成参数就越准确。其次是方法返回值最好是一个结构化的对象或者 JSON 字符串不要返回一个需要二次解释的复杂对象Spring AI 会负责把返回值序列化成 JSON 给模型但你返回的字段命名最好语义清晰。第三是工具方法不要依赖请求上下文里的临时状态最好是无状态的因为你没法控制模型在哪个上下文里调这个方法。3.2 工具注册的底层逻辑与扫描机制很多人会问我写了一个带Tool的 BeanSpring AI 怎么知道要把它暴露出去这里面的机制并不神秘Spring AI 内部有一个ToolAnnotationMethodCallback在 Spring 容器初始化阶段它会扫描所有 Bean 中带有Tool注解的方法把这些方法的名称、描述、参数 schema 注册成 MCP 工具定义然后在服务启动时通过 MCP 协议暴露出去。默认的扫描范围是整个 Spring 容器所以你不用手动配置扫描包只要保证带Tool的类被 Spring 管理了就行。但要注意一点不是所有方法都适合暴露出去。包含敏感操作、内部管理功能的方法不要加Tool因为一旦注册凡是能连上你这个 MCP Server 的模型都能调它权限边界完全靠你自己控制。如果你有多个工具类可以在application.yml里用spring.ai.mcp.server.tool-callback或者通过编程方式定制注册逻辑。不过对于大多数场景注解扫描就够用了不必过度设计。3.3 Controller 层接入 Spring AI 的 ChatClient工具写好了接下来要做的就是把大模型接进来让用户通过自然语言触发这些工具。Spring AI 2.x 里最核心的入口是ChatClient。我封装了一个对话接口用户 POST 一句话服务端调用 Qwen 模型模型感知到注册的 MCP 工具后自动决定是否调用、调用哪个、传什么参数。package com.example.aiorder.controller; import com.example.aiorder.service.OrderQueryService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Map; RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder, OrderQueryService orderQueryService) { this.chatClient builder .defaultSystem(你是企业订单助手的 AI 客服请通过工具查询订单信息 回答要简洁准确。如果工具查询失败请明确告知用户原因。) .defaultAdvisors(new SimpleLoggerAdvisor()) .build(); } PostMapping(/api/chat) public String chat(RequestBody MapString, String body) { String userMessage body.getOrDefault(message, ); UserMessage message new UserMessage(userMessage); ChatResponse response chatClient.prompt() .user(userMessage) .call(); return response.getResult().getOutput().getText(); } }这里有一个很关键的细节ChatClient.Builder会自动把项目里配置的 MCP 工具合并到模型调用上下文中吗答案是Spring AI 的自动配置确实会做这件事。当你的应用同时具备 MCP Server 端注册的Tool和 ChatClient 时Spring AI 会在每次模型调用时把工具列表传给模型模型决定是否调用。而 MCP Client 端连接的外部工具则通过 advisor 来注入。建议在生产环境打开SimpleLoggerAdvisor它能把用户输入、模型输出、工具调用过程完整记录下来调试时非常管用。我在排查问题时就是靠它的日志确认了“模型是否选了某个工具、传参是否合理”。3.4 SSE 传输端点的暴露与调试配置了transport: http后Spring AI 会自动暴露 MCP 协议相关端点。默认路径是/mcp/v1/sse客户端连接到这个端点后服务端通过 SSE 推送工具列表和调用结果。我在本地调试时喜欢用直接的命令行验证——通过 curl 先拉一下工具列表确认工具注册生效。# 查看 MCP Server 工具列表实际可能因框架版本略有差异但思路不变 curl -N http://localhost:8080/mcp/v1/sse # 也可以用 SSE 客户端工具调试看到 initialize 和 tools/list 事件如果你发现 curl 连上之后没有输出工具列表先检查日志里有没有“Registered tools”相关的启动信息。如果没有大概率是Tool注解没有被扫描到或者 MCP Server starter 没有生效。4. 一键部署脚本设计与生产环境落地4.1 Dockerfile 的多阶段构建细节部署这块我选择 Docker 多阶段构建。为什么不用 Spring Boot 自带的 executable jar最直接的原因是镜像体积和启动速度。多阶段构建可以把编译环境和运行环境分开最终镜像只保留 JRE 和打好的 jar能控制在 200MB 左右而完整 JDK 镜像动辄 500MB对生产环境的拉取和启动都是负担。# 第一阶段Maven 构建使用带 JDK 和 Maven 的镜像 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app # 先拷贝 pom.xml利用 Docker layer 缓存依赖 COPY pom.xml . RUN mvn dependency:go-offline -B # 拷贝源码并打包 COPY src ./src RUN mvn clean package -DskipTests # 第二阶段只保留 JRE 运行环境 FROM eclipse-temurin:17-jre WORKDIR /app # 创建非 root 用户提升容器安全性 RUN groupadd -r appuser useradd -r -g appuser appuser COPY --frombuilder /app/target/ai-order-center-1.0.0.jar ./app.jar USER appuser EXPOSE 8080 ENTRYPOINT [java, -XX:MaxRAMPercentage75.0, -Djava.security.egdfile:/dev/./urandom, -jar, /app/app.jar]有几个细节值得展开。mvn dependency:go-offline -B这一步的作用是预下载所有依赖后续构建源码时不需要再联网解析依赖这是 Docker 层缓存的经典玩法只要 pom.xml 没变依赖层就不会重新构建CI 时间能省一大截。-XX:MaxRAMPercentage75.0是给 JVM 设置堆内存上限的让它根据容器内存限制自动缩放而不是写死一个-Xmx512m。在 Kubernetes 环境里这个参数特别关键因为 Pod 的内存限制会变化固定堆值很容易导致容器被 OOM Kill。-Djava.security.egdfile:/dev/./urandom是加快 JVM 启动随机数初始化的容器环境里不设置这个Tomcat 启动可能慢好几秒。4.2 一键启动脚本与健康检查除了 Dockerfile我写了一个deploy.sh脚本封装了构建、启动、检查、看日志、停服务这一整套操作。脚本本身不复杂但把容易出错的地方都做了防护。#!/usr/bin/env bash set -euo pipefail IMAGE_NAMEregistry.example.com/ai-order-center:1.0.0 CONTAINER_NAMEai-order-center PORT_MAPPING8080:8080 echo 1/4 构建 Docker 镜像 docker build -t ${IMAGE_NAME} . echo 2/4 删除旧容器如果存在 if docker ps -a --format {{.Names}} | grep -q ^${CONTAINER_NAME}$; then docker rm -f ${CONTAINER_NAME} echo 已删除旧容器: ${CONTAINER_NAME} else echo 无旧容器跳过删除步骤 fi echo 3/4 启动新容器 docker run -d \ --name ${CONTAINER_NAME} \ -p ${PORT_MAPPING} \ -e DASHSCOPE_API_KEY${DASHSCOPE_API_KEY} \ --restart unless-stopped \ ${IMAGE_NAME} echo 4/4 等待健康检查通过 for i in {1..30}; do status$(curl -s -o /dev/null -w %{http_code} http://localhost:8080/actuator/health || true) if [ $status 200 ]; then echo 容器已就绪健康检查通过 exit 0 fi echo 等待服务就绪... ${i}/30 当前状态码: ${status:-unknown} sleep 2 done echo 启动超时请检查日志 docker logs ${CONTAINER_NAME} exit 1脚本里值得学习的地方set -euo pipefail让脚本在出现错误时立即退出避免批处理时“假装成功”导致误判--restart unless-stopped保证容器意外退出后能自动重启DASHSCOPE_API_KEY从宿主机环境变量传入而不是写死在脚本里避免密钥泄露。健康检查等待逻辑也做了 30 次轮询每次间隔 2 秒给足 Spring Boot 启动时间。要让/actuator/health生效别忘了在 pom.xml 里加spring-boot-starter-actuator依赖并且在 application.yml 里开启management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always4.3 生产环境的额外配置建议如果只是本机演示上面的内容已经够用了。但既然叫“正式落地”我觉得还是应该把生产环境相关的几个点都列出来免得你部署完才发现缺东西。第一个是日志。容器里建议把 Spring Boot 日志直接输出到 stdout由 Docker/Kubernetes 统一收集不要写文件。这样docker logs就能看到所有信息在 K8s 里也能配合 EFK 或 Loki 做日志检索。对应配置是logging: level: root: INFO org.springframework.ai: DEBUG org.springframework.ai.mcp: DEBUGorg.springframework.ai和org.springframework.ai.mcp的日志级别建议在联调时打开 DEBUG上线后再降回 INFO。MCP 的日志能看到协议层的握手、工具注册、调用请求推送定位问题非常高效。第二个是模型参数。温度temperature我调到 0.2这类工具调用场景需要确定性温度太高模型容易在参数选择上“自由发挥”。如果你发现模型经常不按工具定义传参可以考虑把 temperature 继续降到 0或者换一个工具调用能力更强的模型。目前 Qwen 系列模型对工具调用的支持已经挺成熟语义理解力强在函数选择上比早期版本稳定不少。第三个是限流和鉴权。MCP 端点是你暴露给模型调用的如果没有鉴权任何人连上都能调用你注册的工具。Spring AI 的 MCP Server starter 本身不提供鉴权你需要自己用 Spring Security 或者网关层做控制。至少在mcp/v1/sse路径上做一层认证比如 Header 里校验MCP-API-Key。4.4 从 Dify 工作流迁移到 Java 代码的思路最近很多人问 Dify 工作流怎么转成 Spring AI Java 代码。我在接触这套方案时也对比过Dify 的优势是可视化编排缺点是你没法把它嵌进你的 Java 系统里数据还是要通过 API 来回传输。而 Spring AI MCP 的玩法是把 Agent 和工具调用全部收进 JVM数据处理链路短还能直接复用业务代码里的事务、缓存、权限体系。这两者的转换关系很简单Dify 里的一个“工具节点”在 Spring AI 里就是一个带Tool的方法Dify 里的“大模型节点”对应ChatClient的调用Dify 里的“变量”就是方法的参数和返回值。你只要把 Dify 里的 workflow 图拆成“用户输入 → 意图识别 → 工具调用 → 结果聚合”这几个环节用代码翻译一遍并不复杂。我实际做了一次类似的迁移把原来用 Dify 编排的订单查询流程重写成了基于 Spring AI 的ChatClientTool方案。代码量反而少了很多因为少了一层 HTTP 跳转运行时延低了大概 100ms 左右。调试和可观测性也更好直接在 IDE 里打断点就能看到模型每一步调了什么工具。5. 常见问题与排查技巧实录5.1 工具注册成功但模型不调用这是我最常被问到的问题之一启动日志里明明看到Registered toolsMCP 探测也能看到工具列表但模型就是不调工具回答时只是一本正经地胡说八道。排查思路顺序建议是这样的先确认模型是否真的收到了工具列表。打开 DEBUG 日志找到 ChatClient 发送给模型的请求体看tools字段里有没有你注册的工具定义。如果没有多半是 Spring AI 自动装配时没有把本项目的工具回调合并进 ChatClient你需要检查是否有多个ToolCallbackProvider实例冲突。如果工具列表确实发过去了但模型不调那就看系统提示词。很多模型对工具的调用意图判断取决于用户消息的语义匹配度用户如果问得太模糊模型可能选择直接回答而不调工具。我在系统提示词里写了类似“请优先通过工具查询后再作答”显著提升了工具触发率。还有模型对参数的理解受工具描述影响很大description写得越具体越容易被正确触发。5.2 MCP 连接握手失败SSE 端点无法访问SSE 连接失败排查时先把问题分两层网络层和协议层。网络层的关键是确认客户端能访问到服务器的 8080 端口并且路径写对了。Spring AI 2.x 的默认路径是http://{host}:8080/mcp/v1/sse但注意如果你配置了spring.mvc.servlet.path或全局路径前缀这个问题会变得更加隐蔽。像我项目里把 Spring MVC 的路径前缀改成了/api结果 MCP 端点就变成了/api/mcp/v1/sse。排查这类问题时直接用 curl 打一下端点看返回是不是text/event-stream格式。协议层的问题通常发生在 initialize 阶段。如果日志里出现Unsupported transport或者Invalid message type先确认 Server 和 Client 的 MCP SDK 版本一致。我遇到过 Server 用 MCP SDK 2.0、Client 用 MCP SDK 1.x 的情况握手中protocolVersion协商不了直接报错。解决方式是让两边的 spring-ai 版本对齐或者配置spring.ai.mcp.server.version和 client 端协商。5.3 大模型返回 JSON 解析错误用 Qwen 这类模型时偶发返回内容包含多余的换行、前后补充了自然语言导致 JSON 解析失败。Spring AI 内部对模型输出的解析其实是比较宽容的但如果服务端收到了不是有效 JSON 的结果问题多半出在模型配置上。我建议两步走第一把响应的responseFormat配置成json_object让模型强制输出 JSON。Spring AI 的 OpenAI 兼容配置里可以设置spring.ai.openai.chat.options.response-format百炼的这个协议也支持。第二对工具调用结果加一层兜底解析捕获解析异常后把原始字符串返回给模型让模型自己纠正。这里有个经验值如果一次调用中模型连续几次无法生成正确参数别急着改代码先试试换模型版本。Qwen 3 系列不同规格的参数大小对复杂工具选择的表现差异明显7B 模型在简单场景够用但工具数量超过 10 个以后准确性会下降可以考虑切换到更大的模型或者用qwen3-30b-a3b-instruct这类中间规格。5.4 常见问题速查表现象可能原因处理方式Maven 拉不到 spring-ai 依赖未配置 Spring 官方仓库在 pom.xml 添加 spring-milestones / snapshots 仓库启动时McpServerProperties不存在Spring AI 版本不兼容统一通过 Spring AI BOM 管理版本Tool方法未注册类未被 Spring 管理检查是否加了Service/Component注解模型不调用工具系统提示词缺少引导在 defaultSystem 中加入“优先调用工具”描述SSE 端点 404路径前缀冲突检查spring.mvc.servlet.path配置工具调用结果混乱参数描述不清晰为ToolParam补全 description容器启动后无法访问健康检查未通过查看容器日志确认端口映射和依赖服务就绪模型返回非 JSONresponse format 未强制配置 json_object 响应格式Docker 构建慢未利用依赖层缓存先只拷贝 pom.xml 再执行 dependency:go-offline5.5 关于安全边界的补充嵌入式 MCP Server 的风险一向容易被低估你把业务方法暴露给大模型就等于把原本面向人的系统接口开放给了程序自动调用。这带来两个直接问题一是接口被不可预期的参数多次调用可能导致业务数据被大量读取二是某些操作型工具如果权限校验不到位可能被恶意构造的对话触发。我在代码里做了两层防护。第一是参数校验所有Tool方法入口处都做白名单校验和越权检查不接受“超范围”参数。第二是敏感工具不暴露凡是包含删除、改价、导出这类高风险操作的方法一概不加Tool需要人工审批的环节保留在原有的业务系统里。你可以把这些工具类单独放在一个包下用Profile(ai)标记AI 能力默认关闭需要时再显式开启。个人实操中的小体会项目收尾之际把这段实测经验分享给你Spring AI MCP Server 的整合确实能让 Java 服务快速获得 AI 工具调用能力但它的核心难点从来不是“把服务跑起来”而是你愿意暴露哪些工具、怎么描述好这些工具、怎么防止 AI 乱操作。我花在优化工具描述上的时间远多于写代码的时间。一个描述精准的工具能省下大量调模型和改 prompt 的时间。部署层面Docker 多阶段构建加健康检查这套组合稳定跑了几周没有出现过一次启动失败说明这套做法足够可靠。如果你接下来还有余力建议往 Spring AI Agent 的方向继续延伸把多个 MCP Server 组合成更复杂的自动化任务那才是真正把 AI 嵌进业务的核心玩法。
返回列表