拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Spring AI MCP Server实战:Java生态AI工具调用与Docker部署指南

Spring AI MCP Server实战:Java生态AI工具调用与Docker部署指南

如果你是个 Java 开发者,最近一定在各种技术社区里高频看到 Spring AI MCP Server 这个词。我自己的感觉是,从 2024 年底模型上下文协议(MCP)被提出,到 Spring AI 在 2025 年的几个版本里把 MCP 客户端和服务端支持做得越来越成熟,Java 生态接入 AI 的方式已经彻底变了。以前我们要自己写 HTTP 接口、拼 Prompt、解析 JSON,现在把业务能力封装成 MCP Tool,模型就能直接调用,语义和链路都干净很多。

这篇文章我不会只讲概念,我会把从环境准备、代码编写、客户端接入,到 Docker 一键部署的完整过程都过一遍。整个过程基于我最近在真实项目里从零落地的经验,代码和脚本都是可以直接抄的。不管你是刚接触 Spring AI 的新手,还是已经在做企业内部 AI 应用的老手,只要按着步骤走,基本都能把 Spring AI MCP Server 跑起来,并理解它背后真正解决的是什么问题。

1. 先搞懂 MCP 在解决什么问题

1.1 AI 应用集成的“USB-C”接口

MCP 全称是 Model Context Protocol,也就是模型上下文协议。它的出现背景很直白:AI 模型本身不会主动访问你的数据库、文件系统或者内部 API,想让模型完成具体业务操作,就必须给它接上工具和数据。2025 年以前,每家 AI 框架都有一套自己的接入方式,OpenAI 有 Function Calling,LangChain 有自定义 Tool,Spring AI 早期也有自己的 @Tool 注解。结果就是一套业务能力想服务多个模型框架,得写多套适配代码,维护成本非常高。

MCP 做的事情就是把这些接口统一起来。它规定了一个标准协议,让模型应用(比如一个基于 Spring AI 的 Agent)和外部能力提供方(比如一个提供天气查询、订单查询、数据库操作的服务)通过固定的方式通信。这个协议设计得像 USB-C 接口:只要都支持这个标准,插上就能用,不用管另一方内部怎么实现。Spring AI 从 1.0 开始就同时实现了 MCP 的客户端和服务端,这也是 Spring AI MCP Server 这个词越来越火的核心原因。

1.2 MCP 架构里的三个角色

我在第一次接触 MCP 时,最容易搞混的是角色之间的关系。实际上一个完整的 MCP 链路里有三个角色:

  • Host(宿主进程):运行着 AI 模型和 Prompt 编排逻辑的应用,通常就是我们说的 AI Agent 或者聊天机器人的后端服务。
  • MCP Client(客户端代理):在 Host 内部运行,负责按照协议去连接外部 MCP Server,把工具列表拉回来,转发调用请求。
  • MCP Server(服务端):独立部署的进程,封装一个个业务能力,对外暴露统一的 MCP 端点,比如 HTTP + SSE 或者 stdio 方式。

用一个生活化的类比:Host 就像一家餐厅的厨房,MCP Client 是传菜员,MCP Server 则是各个供应商的仓库。菜单就是工具列表,厨房需要什么食材就跟仓库说一声,仓库按标准打包送过来,而不是每家供应商都用自己的送货车。

1.3 MCP 体系中的三大原语

MCP 协议里有三个核心原语,理解了它们就理解了整个协议的设计:

  • Tools(工具):模型在推理时能够主动调用的函数,比如“查询订单状态”“获取股票价格”。工具是 MCP 里最常用、最关键的原语,也是我们在 Spring AI 里主要封装的内容。
  • Resources(资源):对外提供的数据或文本内容,比如一份文档、一张表结构说明。资源可以被模型作为上下文读取,但它不像工具那样带输入输出参数。
  • Prompts(提示词模板):服务端预置的提示词模板,方便 Host 直接复用,比如“生成一份 MySQL 慢查询分析报告”这样一整套 Prompt 和参数组合。

在实际的 Spring AI MCP Server 开发里,我们 90% 的精力都在写 Tools,剩下的场景才会用到 Resources 和 Prompts。所以这篇文章后面的实现部分,会重点展示如何把一个方法变成模型能调用的 Tool。

1.4 为什么 Java 开发者要在这个时间点切入

很多人会问,既然 LangChain 或者 Python 生态做 AI 更成熟,为什么 Java 还要做这件事?我个人的体会是,企业内部的核心业务系统绝大多数都是 Java 写的,Spring Boot 几乎是行业标配。AI 应用要发挥作用,一定要连上已有业务能力,比如查询工单、变更库存、发送通知,这些服务本来就在 Java 体系里。

过去我们把业务能力暴露给 AI 有两种做法:一种是为模型侧单独写接口,很啰嗦;另一种是干脆让 AI 直接调数据库,安全风险很大。MCP 让这些业务系统自己封装成标准的 Tool,AI 服务通过网络来调用,权限、参数校验、日志审计都在原有服务里完成。这是一个非常符合 Java 后端工程习惯的模式。而且 Spring AI 从 1.0 GA 到 2.x 版本,接口稳定了很多,踩坑成本已经大幅降低。

2. 环境准备与版本选型

2.1 在动手前先检查这些环境

开始写代码前,先确认本机环境。我这次使用的组合是 JDK 17 + Maven 3.9 + Spring Boot 3.4.5 + Spring AI 1.0.0 GA。这个组合目前看是比较稳的。JDK 版本建议至少 17,因为 Spring Boot 3.x 是强制要求 17 以上的,如果你还在用 JDK 8,建议先用 SDKMAN 或者直接装一个 17 的独立目录,平时开发和构建时切换使用。

用下面的命令快速检查环境:

java -version mvn -version docker --version

如果 Maven 没有安装,或者版本低于 3.6,建议先升级。Maven 版本太老的话,拉取 Spring AI 的部分依赖会出现奇怪的解析错误,这是我在实践中踩过的第一个坑。Docker 方面,如果你只跑到本机联调,可以不装,但如果要看第四部分的一键部署,就必须准备一台能跑 Docker 的 Linux 服务器,或者本地 Docker Desktop。

2.2 Spring AI 版本怎么选:1.0.x 还是 2.x

这是最近社区里问得很多的问题,尤其是 Spring AI Alibaba 一度传出各种说法。先回答一句比较直接的:Spring AI Alibaba 并没有停更,仍在正常迭代,国内开发者用阿里云百炼平台的 qwen 系列模型最方便,接入方式也在持续完善。至于版本选择,我的建议是参考下面的原则:

  • 如果你希望稳定,优先用 Spring AI 1.0.0 GA 或 1.0.x 的最新补丁版。这个版本对应 Spring Boot 3.4.x,API 已经冻结,可以安全用于生产。mcp-server 和 mcp-client 两个模块也都是独立打包的,不会引入太多历史包袱。
  • 如果你想尝试 Agent 编排和更前沿的能力,可以用 Spring AI 2.x,但要注意 2.x 的部分 API 和 1.0 不兼容,升级成本是存在的。而且 2.x 对 Spring Boot 的版本要求更高,一般要配合 Spring Boot 3.5 或以上的基线。

我这次落地选择了 Spring AI 1.0.0 GA,原因很现实:团队里还有其他微服务依赖 Spring Boot 3.4.x,统一基线容易维护。工具调用(Tool Calling)和 MCP 是 1.0 里的核心能力,已经足够稳定。

这里给出一张我参考过的版本对应表:

Spring AI 版本适合的 Spring Boot 版本成熟度建议使用场景
0.8.x3.2.x实验仅学习,不建议新项目
1.0.0 GA3.4.x稳定生产可用,推荐
1.0.x 后续补丁3.4.x稳定生产推荐,跟进修复
2.0.x3.5.x较新探索新功能,谨慎生产

2.3 创建项目骨架

我习惯直接用 Spring Initializr 生成基础工程,也可以手动建 Maven 项目。关键是 pom.xml 里的依赖组合要看准。我的 pom 里核心依赖是这样配的:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.5</version> <relativePath/> </parent> <properties> <java.version>17</java.version> <spring-ai.version>1.0.0</spring-ai.version> </properties> <dependencies> <!-- Web 基础 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI 模型接入,以阿里云百炼为例 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0.2</version> </dependency> <!-- MCP Server 端:通过 WebMvc + SSE 暴露 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-server-webmvc</artifactId> </dependency> <!-- MCP Client 端:如果本服务也需要作为客户端去调其他 MCP Server --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> </dependencies>

有一点需要特别解释:spring-ai-alibaba-starter 里已经带了 Spring AI 的 model 相关依赖,并且通过阿里云百炼平台接入 qwen 系列模型时,配置项是spring.ai.dashscope.api-key,不是老的spring.ai.openai.api-key。如果你用的是通义或百炼,直接用这个 starter 是最省事的。如果不想用阿里云,可以用spring-ai-starter-model-openai或spring-ai-starter-model-anthropic,结构上是类似的。

依赖引入之后,还需要在 Spring Boot 启动类上加上重要注解吗?不需要,Spring AI 的 MCP 支持是约定大于配置,加了 starter 之后自动配置就会生效,我们只需要在 properties 里告诉它“暴露什么端点”以及“连接哪些服务”。

3. 核心代码实现:写一个能被 AI 调用的 Tool

3.1 第一步:定义一个业务工具类

先明确我们要做什么。为了演示完整链路,我实现一个极简但常见的工具:查询当前服务器的本地日期,并顺便返回星期几。这个工具本身很简单,但是它能很好展示 Spring AI 如何把一个 Java 方法变成一个模型可理解、可调用的函数。

我在项目里新建了一个包com.example.mcpserver.tools,内部类如下:

package com.example.mcpserver.tools; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Locale; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; @Component public class DateTool { @Tool(description = "获取服务器当前日期和星期几") public CurrentDate getCurrentDate( @ToolParam(description = "日期格式,例如 yyyy-MM-dd 或 MMMM d, yyyy") String pattern) { LocalDate today = LocalDate.now(); String formatted = today.format(DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH)); String dayOfWeek = today.getDayOfWeek().getDisplayName( java.time.format.TextStyle.FULL, Locale.ENGLISH); return new CurrentDate(today.toString(), dayOfWeek, formatted); } public record CurrentDate( String isoDate, String dayOfWeek, String formattedDate) { } }

这里有两个关键点:第一,方法上标注了@Tool注解,注解里的 description 会作为工具描述传给模型,模型正是靠这个描述决定什么时候调用工具,所以描述要写清楚“这个工具能干嘛”。第二,参数和返回值都尽量使用明确的 Java 类型,我用的是嵌套 record 类型,Spring AI 会基于它自动生成 JSON Schema,让模型知道该传什么参数、会收到什么结果。

为什么不要用 Map 当返回值?这是我踩过坑的。用 Map 的话,工具 Schema 会退化成“一个对象”这种模糊结构,模型经常猜错字段名,尤其在 qwen 这类模型上表现不稳定。换成 record 之后字段名、类型都固定了,调用成功率会明显上升。

3.2 第二步:把 Tool 注册到 MCP Server

有了工具类还不够,还要把它注册到 MCP Server 里。Spring AI 支持两种注册方式:一种是直接创建一个ToolCallbackProvider的 Bean,另一种是使用@MethodToolCallbackProvider来批量注册某个类里所有@Tool方法。

我比较推荐用ToolCallbackProvider的方式,注册代码长这样:

package com.example.mcpserver.config; import com.example.mcpserver.tools.DateTool; import java.util.List; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.MethodToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class McpServerConfig { @Bean public ToolCallbackProvider dateToolCallbackProvider(DateTool dateTool) { // 把 DateTool 实例中的所有 @Tool 方法都暴露为 MCP Tool return MethodToolCallbackProvider.builder() .toolObjects(dateTool) .build(); } }

如果以后有多个工具,把它们都加到toolObjects(...)里就行了。这里有一个容易被忽略的细节:ToolCallbackProvider是 Spring AI 中所有 Tool 的统一抽象,它同时服务了两种场景。第一种是你直接在一个 ChatClient 里使用这些工具;第二种是你把工具通过 MCP Server 暴露给远程的 AI 应用调用。我们在服务端用 MCP Server 暴露时,框架会自动把该 Provider 中的工具转换成 MCP 的工具列表。

3.3 第三步:启动服务端暴露 SSE 端点

注册完工具之后,下一步就是启动 MCP Server 端。Spring AI MCP 默认提供了两套服务端实现:spring-ai-mcp-server-webmvc和spring-ai-mcp-server-webflux。因为大部分 Java 后端项目用的都是 Spring MVC,所以我选的是前者。它基于 Servlet 容器,通过 SSE(Server-Sent Events)提供 MCP 端点。

启动类不需要改,但需要在application.yml里加配置:

spring: ai: mcp: server: name: order-ai-mcp-server version: 1.0.0 sse-endpoint: /mcp enabled: true

这个配置的含义是:把 MCP 服务端点暴露在应用的/mcp路径上,MCP 客户端通过 SSE 连上来时,会先获取能力列表,再逐个调用。直接启动这个服务,默认端口是 8080,如果只跑服务端,可以顺手给 Spring Boot 配置一个server.servlet.context-path或者用 8081 避免冲突。

启动后,可以用浏览器或者 curl 检查端点是否存活。注意 SSE 端点不是普通 GET 接口,直接请求/mcp会一直挂着,这是正常的,因为它期望客户端按 MCP 协议的消息格式发送请求。用下面的命令简单确认服务启动成功即可:

curl http://localhost:8080/order-ai-mcp-server

正常情况下会返回 404 或者其他非 5xx 状态,只要不是连接拒绝,说明端口起来了。

3.4 第四步:客户端接入,让模型真正能调用工具

服务端准备好了,但真正要让 AI 模型调用这个工具,还需要一个 Host 端。这一步很多教程会跳过,导致很多人写完 Server 不知道怎么测。我这里提供两种验证方式,任选其一。

方式一:在同一个 Spring Boot 工程里同时配置一个 ChatClient,用一个测试接口验证工具调用。这种方式最快速,适合本地验证。关键配置如下:

spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus mcp: client: enabled: true webmvc: connections: - name: local-server url: http://localhost:8080/mcp

然后在代码里注入ChatClient,写一个测试接口:

package com.example.mcpserver.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/ai/date") public String askDate(@RequestParam(defaultValue = "今天星期几?") String message) { return chatClient.prompt(message).call().content(); } }

调用流程是:我的 HTTP 请求发给 ChatController,ChatClient把消息发给百炼平台的 qwen-plus,qwen 模型发现需要查找日期,就会通过 MCP Client 连接到本服务的/mcp端点,调用DateTool.getCurrentDate,拿到结果后再组合成自然语言返回给用户。整个链路在一次 HTTP 响应内完成。

方式二:使用独立的 Host 服务,比如另一个 Spring Boot 工程,只做 MCP Client,连接独立的 MCP Server。这个更接近生产环境,但本地调试时链路较长,我建议先把方式一跑通。

3.5 完整链路验证:一次真实的调用过程

我实际跑通的请求是这样的:启动服务后,控制台打成http://localhost:8080/ai/date?message=今天星期几,返回结果类似:

今天是 2025-06-04,星期三。

为了确认模型真的通过 MCP 调用了工具,而不是自己瞎编的日期,我开启了客户端日志:

logging: level: org.springframework.ai.mcp.client: DEBUG org.springframework.ai.tool: DEBUG

在日志里能看到类似下面这样的记录:

Sending tool call: name=DateTool.getCurrentDate, arguments={"pattern":"yyyy-MM-dd"} Tool execution result: CurrentDate[isoDate=2025-06-04, dayOfWeek=Wednesday, formattedDate=2025-06-04]

看到这行日志,就证明整个 Spring AI MCP Server 的链路完全通了。有时候模型会要求同时调用多个工具,日志里会有多次 tool call 记录,这也是正常现象。

4. 一键部署:从本机到生产环境

4.1 为什么要用 Docker 部署 MCP Server

我们在真实项目中很快发现,本机跑通只是第一步,真正麻烦的是让 MCP Server 稳定运行在服务器上。如果直接把 Java 进程扔到服务器上,环境差异会让问题变得不可控,比如服务器 JDK 版本不对、Maven 没装、端口被占用、日志没轮转。Docker 化之后,构建物变成一个标准镜像,在任何有 Docker 环境的机器上都能跑出一样的行为。这也是“一键部署”落地的核心保障。

4.2 编写一个干净的多阶段 Dockerfile

我采用的 Dockerfile 是多阶段构建模式:第一个阶段用 Maven 镜像编译打包,第二个阶段只保留 JRE 运行环境。这样最终镜像体积能控制在 300MB 左右,而不是带着整套 Maven 依赖跑。

# 第一阶段:构建 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests -B # 第二阶段:运行 FROM eclipse-temurin:17-jre WORKDIR /app COPY --from=builder /app/target/mcp-server-*.jar app.jar EXPOSE 8080 ENV JAVA_OPTS="-Xms256m -Xmx512m" ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

这里有一个细节:mvn dependency:go-offline这一步会把所有依赖提前拉取并缓存到镜像层,之后每次修改源码重新构建时,只有真正变化的依赖才会重新下载,构建速度会快很多。如果你把整个源码 COPY 进去再运行 Maven 命令,没有利用好依赖缓存,每次构建都会非常慢。

4.3 docker-compose 编排与环境变量注入

实际部署时我不会直接docker run,而是写一个docker-compose.yml,把环境变量、端口映射、健康检查都管理起来。密钥不要写死在镜像里,用环境变量的方式注入:

version: "3.8" services: mcp-server: image: registry.example.com/ai/mcp-server:1.0.0 container_name: mcp-server ports: - "8080:8080" environment: - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY} - TZ=Asia/Shanghai healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 30s timeout: 5s retries: 3

如果项目里有 Spring Boot Actuator,建议在 pom 里加上依赖,并暴露 health 端点:

management: endpoints: web: exposure: include: health

这样 Docker 的 healthcheck 才能正常探测服务是否就绪。

4.4 编写 deploy.sh 一键部署脚本

接下来是“一键部署”的核心了。我通常会在服务器上放一个deploy.sh,内容包含构建镜像、停止旧容器、启动新容器、健康检查四步。脚本设计成一个幂等操作,重复执行不会出问题:

#!/bin/bash set -e IMAGE_NAME="registry.example.com/ai/mcp-server" TAG="1.0.0" CONTAINER_NAME="mcp-server" echo "==> 1. 构建 Docker 镜像" docker build -t ${IMAGE_NAME}:${TAG} . echo "==> 2. 停止并删除旧容器(如果存在)" if [ "$(docker ps -aq -f name=${CONTAINER_NAME})" ]; then docker stop ${CONTAINER_NAME} docker rm ${CONTAINER_NAME} fi echo "==> 3. 启动新容器" docker run -d \ --name ${CONTAINER_NAME} \ -p 8080:8080 \ --restart unless-stopped \ -e DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY} \ -e TZ=Asia/Shanghai \ ${IMAGE_NAME}:${TAG} echo "==> 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 sleep 2 done echo "健康检查超时,服务可能启动失败" docker logs ${CONTAINER_NAME} --tail 50 exit 1

这个脚本第 4 步很重要。如果不做健康检查就退出,后面接 CI/CD 时很可能把还没启动完成的实例当作成功。我在实践中把探测超时设置在 30 次乘 2 秒,大约是 60 秒。Java 应用首次启动时需要加载 Spring 上下文,60 秒通常够用,但如果你的服务里有很多 Bean 或者要连数据库,建议把循环次数加大到 60,也就是约 120 秒。

4.5 第一次部署的现场记录

我在一台 2 核 4G 的云服务器上试跑这套流程时,整个过程大约花费 3 分钟。前面 Maven 构建阶段占了大部分时间,因为需要拉取依赖,后续每次改代码重新部署,如果依赖没有大变化,构建时间可以缩短到 1 分钟内。

部署完成后,我在服务器上执行了一次远端调用测试:

curl -H "Content-Type: application/json" \ -d '{"message":"现在几号"}' \ http://服务器IP:8080/ai/date

返回结果正常。随后我用同一台服务器上的另一个 Spring AI Host 服务去连接部署好的 MCP Server,测试外部连接,也能正常拿到工具列表。这就验证了 MCP Server 不只是“本地能跑”,而是真正可以作为基础设施暴露给任意模型应用使用。

4.6 关于可观测性的几个建议

一旦 MCP Server 进入生产环境,光有一个健康检查是不够的。我建议在部署时至少加上下面几项:

  • 日志保留策略:使用 JSON 格式日志,方便采集到 Elasticsearch 或 Loki。
  • 关键指标:记录每次工具调用的耗时、成功率,以及模型调用工具的次数。Spring AI 默认没有完整埋点,我是在工具方法里手动加了一个简单的计数和耗时打印。
  • 超时配置:模型等待工具返回时间可能很长,建议在 MCP 服务端配置合理超时时间。Spring AI 相关模块的配置项是spring.ai.mcp.server.timeout,单位为秒,默认值对高延迟场景可能不够。

对于大多数中小团队,做到这三点已经足够。不必在一开始就上重型的链路追踪系统,等调用量大了再逐步加。

5. 实战中的坑:踩过才懂

5.1 MCP 协议版本对齐问题

这是我最开始被卡住最久的地方。Spring AI 1.0.0 GA 使用的 MCP 协议版本和客户端依赖的协议版本如果不匹配,客户端会报Unsupported protocol version或者握手失败。原因是 Spring AI 的 mcp-server 和 mcp-client 是不同 starter,各自可能引入不同版本的 MCP SDK。

解决办法有两个:第一,所有用到的 Spring AI 相关依赖版本都放到同一套 BOM 管理下,尤其注意spring-ai-mcp-server-webmvc和spring-ai-starter-mcp-client要使用同一版本;第二,显式设置协议版本,比如在服务端和客户端都配置mcp.server.version为同一个支持的协议版本。这里要说一句,用 Spring AI 官方 BOM 是最省心的方式,我在 pom 里的 spring-ai.version 统一设为 1.0.0 后,没有再出现过这类问题。

5.2 工具被调用,但模型输出的结果不准确

如果模型明明调用了工具,返回里也带了isoDate=2025-06-04,但最终给你的自然语言回答还是“根据今天的日期,应该是 2025 年 6 月 5 日”,这种不一致多半是模型本身的指令遵循能力较弱,或者系统提示词引导不够。我遇到这种情况时,会在系统提示词里明确加上一句:“当你有工具结果时,必须以工具返回的数据为准,不得自行推算日期。”这句简单的话就能明显减少错误输出。

另外,选择的模型最好要支持工具调用。比如 qwen-plus、qwen-max 系列都支持,部分轻量模型对工具调用的支持比较弱,建议在接入前先查看模型文档确认是否支持 function calling 或 tool calling。

5.3 连接阿里云百炼时的 Key 与网络配置

接入百炼平台时,最容易出的问题是配置项搞错。我见过有人把spring.ai.dashscope.api-key写成spring.ai.openai.api-key,结果请求报 401。还有网络问题:如果你的服务器在中国大陆,通常不需要额外处理网络;但如果你的服务器在海外,就需要考虑目标平台 API 的网络连通性。这里的处理方式属于常规运维范畴,按各家云厂商的网络配置说明来做即可,不展开讨论。

一个更隐蔽的坑是 API Key 里带特殊字符,比如sk-xxx:yyy这种带有冒号的字符,在 yml 里如果不加引号,会被解析成不正确的字符串。正确写法:

spring: ai: dashscope: api-key: "${DASHSCOPE_API_KEY}"

用环境变量引用后,就不存在特殊字符解析问题。

5.4 同一个工程里跑 Server 和 Client 时端口冲突

如果你还是和我一样,先用本地双角色(既是 Server 又是 Client)来验证,记得确认不要自连。为了调试,可以让 MCP Server 跑在 8080 端口,而 MCP Client 连接http://localhost:8080/mcp,请求入口ChatController也暴露在 8080。这样做有一个潜在问题:Spring MVC 在接收/ai/date请求的时候,如果 MCP Client 的 SSE 连接刚好占用线程,可能在高并发下出现线程池不足。本机验证没问题,生产环境一定要把 Server 和 Client 拆成独立服务。

5.5 常见问题速查表

我把最近被问得最多的几个问题整理成一个表,方便你排查:

症状可能原因解决办法
连接 MCP 失败,报握手错误协议版本不匹配统一 Spring AI BOM 版本,显式设置协议版本
调用模型返回 401API Key 配置错误或为空检查spring.ai.dashscope.api-key配置
工具列表为空ToolCallbackProvider 未注册成功检查 Bean 是否注入,扫描包路径是否覆盖
模型回答的日期是编的模型不支持工具调用,或提示词未约束换 qwen-plus 及以上模型,加系统提示词
部署后健康检查失败内存不足或端口被占用调大 Java 堆内存,检查宿主机端口
首次构建很慢未利用依赖缓存在 Dockerfile 中先拷贝 pom.xml 并执行 go-offline

表格里的这几项基本覆盖了从开发到部署最常见的故障。每次遇到异常,先看服务端日志,再看客户端日志,大多数问题能在 Spring AI 自身的 DEBUG 日志里找到线索。

5.6 关于 Spring AI Alibaba 与生态现状的一点看法

最后关于 Spring AI Alibaba 相关的版本问题,我再多说几句。近期总能看到“Spring AI Alibaba 停更了吗”类似的疑问。从我实际使用的体验看,阿里云百炼的 starter 在 1.0.0 之后仍然有版本更新,而且 Spring AI 官方本身也在把阿里云 DashScope 作为集成示例维护。技术选型时不要被零散的消息干扰,判断依据只有一个:你当前用的版本是否能满足需求、社区是否还在活跃维护。从这一点看,Spring AI Alibaba 目前依然是 Java 生态里接入国产模型最顺滑的方案之一。

我在实际项目中遇到的版本情况是,如果跟随 Spring AI 官方 BOM 版本,再引入对应版本的 spring-ai-alibaba-starter,两者配合是比较可靠的。不要把各个依赖的版本各自为政地升级,升级时最好一起升级并跑一遍完整的工具调用链路,避免模型平台兼容性问题。

写到这里,整个 Spring AI MCP Server 的落地过程就算完整理清楚了。从协议本身的历史背景,到环境选型、代码实现、Docker 部署,再到我踩过的那些坑,基本覆盖了一个 Java 开发者从零到生产会经历的全部环节。我个人最大的感受是,MCP 让 AI 应用和业务系统之间的集成方式第一次有了标准答案,而 Spring AI 又让这套标准在 Java 生态里落地得足够自然。后面如果再往深了做,可以考虑接入更复杂的工具集、引入多 MCP Server 组合,甚至把已有的 Agent 编排和 MCP Tool 调度串在一起,但无论怎么扩展,今天这套服务端封装和部署的思路都是一样的。作为 Java 开发者,在一大片 Python 主导的 AI 内容里看到 Spring 生态能给出这么干脆的解决方案,确实值得动手试一次。

返回列表