最近 Java 圈子聊 MCP 的人越来越多,尤其是 Spring AI 正式把 MCP 放进官方生态以后,很多后端同学终于感觉“这事跟我有关了”。我前阵子正好用 Spring AI 完整落地了一个 MCP 客户端,把自己的业务功能包成了一个 MCP Server,再让大模型通过工具调用把整个流程串起来,踩了不少坑,也把手里的示例代码精简成了可以直接抄作业的版本。这篇就按零基础到进阶的顺序,把这个链路讲透,包括 MCP 和 Spring AI 的关系、依赖怎么选、客户端怎么写、自定义 Server 怎么做、以及我实际调试中遇到的各种问题。别管你是刚入门的 Java 新人,还是已经在生产线上的后端开发,照着做基本都能跑起来。
网上的 MCP 教程大多围绕 Python 和 Node,Java 相关的零散资料确实少。但 Spring AI 对 MCP 的支持其实很成熟,从 1.0 GA 版本开始,MCP 客户端、MCP Server、工具自动注册这些核心能力都已经内建,配置好依赖后,本质就是写几个配置类和注解的事。
1. 先把 MCP 和 Spring AI 这两件事讲透
1.1 为什么 2025 年后所有 Java 后端都在聊 MCP
MCP 的全称是 Model Context Protocol,也就是模型上下文协议,最早由 Anthropic 在 2024 年底开源,目的很简单:把 AI 模型和外部数据、工具之间做一个统一的标准接口。你可以抽象地理解成,它给大模型开了一堆“插座”,内置支持文件系统、数据库、搜索、网页、代码仓库等等。不同行业的人看到的东西完全不同:前端看到的是 Chrome DevTools MCP,运维看到的是服务器监控 MCP,安全测试的人看到的是 Burp Suite MCP,甚至还有 Blender 建模 MCP、Figma 设计稿 MCP。大家的核心诉求是一致的:让 AI 不再只会聊天,而是能真的去访问数据、操作工具、完成复杂的业务流程。
那为什么 Java 开发者要特别关心?因为企业核心系统绝大多数是 Java 写的。像审批流、订单中心、财务对账这类系统,数据都在关系型数据库里,逻辑都在 Spring Boot 服务里。如果想让 AI 助手帮用户查订单、算库存、生成报表,就得让大模型安全地拿到这些能力。MCP 正好提供了统一通道,而 Spring AI 又把这个协议内建成了 Java 生态的一员。你可以直接用 Java 写工具,用一个 @Tool 注解暴露给大模型,然后用 Spring Boot 那一套熟悉的依赖注入、配置绑定、自动装配来管理工作区,这种感觉跟以前写 Web 服务没有本质区别。
1.2 MCP 的架构比你想的更简单
MCP 采用的是一个典型的客户端-服务器架构,中间通过 JSON-RPC 2.0 消息通信,传输方式常见的有 stdio 和 HTTP/SSE 两种,Spring AI 从 1.0.6 版本开始完整支持了 WebFlux/WebMVC 下的 SSE 和 Streamable HTTP。整个链路里有三个角色:
- MCP Host:宿主程序,比如 Claude Desktop、IDE,或者我们自己的 Spring Boot 应用。
- MCP Client:在 Host 内部与某个 Server 建立 1 对 1 会话的协议客户端,负责发送 tools/list、tools/call 这些请求。
- MCP Server:提供具体能力的轻量级程序,里面注册了各种 tool、resource、prompt。
简单类比一下,MCP Server 就像餐厅的菜单,工具就是菜单上的一道道菜,模型是顾客,客户端是服务员。顾客不需要知道后厨怎么运作,只需要看菜单点菜,服务员负责把菜端上来。MCP 协议规范了“菜单长什么样”“怎么点菜”“怎么上菜”,剩下的业务细节都由 Server 实现。
MCP 本身主要定义了三种核心能力:工具,模型主动调用、完成动作,比如查天气、创建订单;资源,把数据作为上下文片段加载,比如把某个 Markdown 文档内容塞给模型;提示词模板,可复用的 Prompt 片段,比如一段生成 SQL 的标准模板。现在实际工程里大家用得最多的就是 Tool,Spring AI 也是优先围绕 Tool 做了最成熟的自动注册机制。
1.3 Spring AI 在 MCP 生态中的三板斧
Spring AI 这块闭源到开源后,对 MCP 的支持非常明确,过去一年里能明显看到官方在逐步完善三个能力。
第一是 MCP Client 的自动配置。你只要引入spring-ai-starter-mcp-client,配置好 server 列表,Spring AI 会帮你创建 MCP 会话,把远端 Server 暴露的所有工具自动注册成ToolCallback,再把这些工具装进ChatClient的工具上下文。写完代码你可能手都没碰一下协议,工具就已经可以被模型调用了。
第二是 MCP Server 搭建。通过spring-ai-starter-mcp-server,配合@Tool注解,你可以把一个 Spring Bean 的方法直接暴露为 MCP 工具。Spring AI 会根据方法签名自动生成 JSON Schema 的输入参数定义,并处理 stdio 或 WebMVC 传输。自己写一个 Server,比想象中简单得多。
第三是工具注册的中枢机制。Spring AI 有一个ToolCallbackProvider的抽象,专门管理由 @Tool 包装出来的ToolCallback。官方内置了MethodToolCallbackProvider,会扫描某个类里所有标记了 @Tool 的方法,把它们包装成统一的回调对象。MCP Server 启动时,会在启动类里用这个 Provider 把业务工具集合交给它。整体设计就是对内屏蔽协议细节,对外统一暴露工具列表。
2. 环境与依赖:一次把版本选对
2.1 JDK 和构建工具
目前 Spring Boot 3.4.x 要求最低 Java 17,如果你准备尝鲜 Spring Boot 3.5 或者更高版本,Java 21 更稳妥。我自己用的是 JDK 17 跑生产环境,本地开发用了 JDK 21,两者在大多数 Spring AI 示例里都能正常编译运行。构建工具选 Maven 和 Gradle 都可以,不过 Maven 生态的资料更全,遇到依赖问题也好搜,这里就用 Maven。
环境准备清单:
- JDK 17+
- Maven 3.9+
- IDE(IDEA 或 Eclipse,推荐 IDEA)
- OpenAI API 的 Key,或者兼容接口的 Key(后面说通义千问的接入)
强调一个点:Spring AI 的依赖是通过官方 BOM 牵引进来的,所以必须在dependencyManagement里加入spring-ai-bom,否则版本号对不上会出现 NoClassDefFoundError 或者 XML schema 解析失败。
2.2 Spring Boot / Spring AI 版本搭配
版本搭配是一个特别容易踩坑的环节。Spring AI 的版本号和 Spring Boot 不是一一对应的,官方文档一般会给出兼容矩阵。在 1.0.x GA 正式发布后,2.0 的里程碑版本也已经在社区活跃。对生产项目,我建议至少关注两个选择:
| 场景 | 推荐版本组合 | 备注 |
|---|---|---|
| 稳定生产首选 | Spring Boot 3.4.x + Spring AI 1.0.x | 文档全,资料多,mcp 相关 API 最稳 |
| 尝鲜新特性 | Spring Boot 3.5.x + Spring AI 1.1.x/2.0.x | 注意部分类路径变化 |
| 国内阿里云场景 | Spring Boot 3.4.x + Spring AI Alibaba 1.0.x | DASHSCOPE 接入体验好,也支持 MCP |
我实际生产里用的是 Spring Boot 3.4.5 + Spring AI 1.0.0,运行得很稳。后来试了一下 Spring AI 2.0 的快照版,变化点主要体现在模块拆分和部分 API 包名调整,建议新项目可以直接考虑,等它正式发布后,再迁移到 2.0 也不晚。
2.3 从 start.spring.io 开始创建项目
去 start.spring.io,选 Maven、Java 17,依赖先不用勾选,因为 Spring AI 需要通过 BOM 管理,页面里还不一定列得全。直接生成一个空 Web 项目,然后手动改pom.xml就行。
pom.xml核心依赖写出来是这样的:
<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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- OpenAI 模型接入,也可以用 spring-ai-starter-model-ollama 之类替代 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <!-- MCP 客户端 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies>如果你的模型提供方不是 OpenAI 官方,比如通义千问、Kimi、DeepSeek 这类兼容 OpenAI 接口的服务,依然可以用spring-ai-starter-model-openai,只需要在配置里替换 base-url 和 api-key。
2.4 必须要引入的依赖清单
除了上面的核心依赖,有几个依赖是排查时最容易漏的:
spring-ai-starter-mcp-client负责 MCP 客户端自动配置,不引入这个就无法自动注册工具。spring-ai-starter-mcp-server负责服务端自动配置,写自定义 Server 时必须加。- 如果使用 stdio 方式启动 npx 子进程,需要额外的进程管理依赖?其实是 Spring AI 内部已经封装好了,不需要额外引进程库。
- 如果使用 MCP Server 的 WebMVC 端点,则还需要
spring-ai-mcp-server-webmvc,但一般 starter 里会传递依赖。 - 如果使用 SSE 传输,Spring Boot 的
spring-boot-starter-webflux是需要的。
再说一个很多人忽略的点。Spring AI 1.0.x 正式版要求 Spring Boot 版本不低于 3.4,如果项目还是 Spring Boot 3.3.x,容易出现McpClient找不到类等问题,最先应该查 Boot 版本和 Spring AI 版本是不是都对上了。
3. 第一个 MCP 客户端:把现成的 MCP Server 拉进你的对话
3.1 使用官方 Everything Server 当靶场
直接手写逻辑之前,最好先找一个现成的 MCP Server 来验证链路是否打通。官方提供了@modelcontextprotocol/server-everything,里面自带 echo、add、longContext 这类工具,非常适合当“靶场”测试。它默认运行在 npx 环境里,Spring AI 客户端配置好 stdio 传输,就可以直接通过子进程拉起一个 Node 进程来当 MCP Server。
这里有个比较关键的点:如果你本机没有 Node 或者 npx 版本太低,整个 stdio 传输就会一直失败,表现为 MCP 客户端连接异常,或者日志里没有任何工具被注册。先用npx --version确认环境没问题,再继续。
3.2 客户端的核心配置
在application.yml里写客户端配置:
server: port: 8080 spring: application: name: mcp-client-demo ai: openai: base-url: https://api.openai.com api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini mcp: client: name: mcp-client-demo version: 1.0.0 type: SYNC request-timeout: 30s servers: - connection: stdio command: npx args: - -y - @modelcontextprotocol/server-everythingtype: SYNC表示使用同步客户端。对大多数场景来说,同步更直观,调试也简单;异步客户端占用资源更少,但链路里多了一层 CompletableFuture,定位问题时没那么快。request-timeout别设太短,50 秒以上比较稳,有些工具执行本身就需要几秒,时间短了容易触发超时误报。
如果要连接远程 MCP Server,配置大概是把connection改成http或websocket类型,配好 URL 和认证字段。生产环境千万不要在 YAML 里写死 Token,用环境变量 ${MCP_SERVER_TOKEN} 的方式注入,否则代码一旦上传仓库,Key 就等于泄露了。
3.3 先写一个能复制的 Demo
直接写一个 CommandLineRunner,一启动就让大模型问一个需要工具才能回答的问题。比如问 everything server 里的 echo 工具:“请使用 echo 工具回复 hello-world,并返回工具的执行结果。”
package com.example.mcpdemo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.client.ChatClient; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class McpClientApplication { private static final Logger log = LoggerFactory.getLogger(McpClientApplication.class); public static void main(String[] args) { SpringApplication.run(McpClientApplication.class, args); } @Bean public CommandLineRunner demo(ChatClient.Builder chatClientBuilder) { return args -> { ChatClient chatClient = chatClientBuilder.build(); String response = chatClient.prompt() .user("帮我调用 echo 工具,内容为 'hello from spring ai',然后原样告诉我结果") .call() .content(); log.info("最终的模型回复:{}", response); }; } }这里需要注意,ChatClient.Builder会自动收集容器里所有的ToolCallback,而 MCP 客户端自动配置会把远端工具的ToolCallback注册进容器。所以整个链路其实和普通函数调用一致,模型觉得需要某个工具时就会发起调用。
3.4 跑起来之后发生了什么
把项目启动后,日志里会看到 MCP 客户端连接、工具注册的日志,然后模型开始做工具调用。整个流程大致是这样的:
- 用户发送“调用 echo 工具”的 Prompt。
- Spring AI 把工具定义和用户的 Prompt 一起发给模型。
- 模型在响应里带上工具调用指令,表示要调用
echo。 - Spring AI 拦截这个指令,调用 MCP Client 向 MCP Server 发送
tools/call请求。 - MCP Server 执行
echo,返回结果。 - Spring AI 把结果回传给模型。
- 模型根据结果组织最终自然语言回复。
整个过程可能来回两轮,日志中能看到工具调用相关信息,包括返回的 content 块。第一次跑通这个流程以后,你就已经完整经历了 MCP 的核心链路,后面写自己的 Server 基本就是把这套流程再自己掌控一遍。
4. 动手实现自己的 MCP Server
4.1 用 @Tool 注解定义业务工具
自己实现 MCP Server 的核心是定义工具,Spring AI 的机制非常简单:在 Spring Bean 方法上标记@Tool注解,指定描述信息,方法参数就是工具的输入字段。比如写一个天气查询工具:
package com.example.mcpserver.service; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Service; @Service public class WeatherToolService { @Tool(description = "根据城市名称查询实时的天气情况,返回天气描述、温度和湿度") public String getWeather(String city) { // 这里实际应该调第三方天气API,为了演示返回模拟数据 return String.format("城市:%s,天气:多云转晴,温度:22℃,湿度:45%%", city); } }就这么简单。一个超普通的 Spring Service,方法上加了@Tool,它就变成了一个 MCP 工具。Spring AI 会根据方法名生成工具名getWeather,根据参数名生成 JSON Schema 的必填字段。这里有一个常见陷阱:如果方法参数没有加@ToolParam(required = false),默认情况下参数是必填的,模型一定会传这个参数才发起工具调用。
还可以为参数加上描述信息,让模型更清楚该传什么:
@Tool(description = "根据城市查询天气") public String getWeather(@ToolParam(description = "城市名称,比如 北京、上海") String city) { // ... }4.2 MCP Server 的自动配置
要构建 MCP Server,需要一个独立的 Spring Boot 工程,或者把 server 代码放在同一个工程的另一个模块里。首先必须在依赖中加入:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server</artifactId> </dependency>然后配置传输方式。如果只作为本地 stdio Server 运行,配置文件这样写:
spring: main: web-application-type: none ai: mcp: server: name: weather-mcp-server version: 1.0.0 transport: stdio再写一个启动类,把工具注册进去:
package com.example.mcpserver; import com.example.mcpserver.service.WeatherToolService; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } @Bean public ToolCallbackProvider weatherTools(WeatherToolService service) { return MethodToolCallbackProvider.builder() .toolObjects(service) .build(); } }初始化完成之后,工具就被暴露了。如果传输方式是 stdio,本地客户端通过命令行进程连接;如果改成 WebMVC 传输,Spring AI 会自动暴露一个 HTTP 端点,比如/mcp,外部客户端可以直接通过 HTTP POST 访问。
这里我必须强调一个观念:一个 Spring Boot 应用既可以作为客户端调用远端 Server,也可以作为 Server 暴露工具,两者不冲突。实际项目中经常会遇到mcp-client模块和mcp-server模块分别启动,中间通过本地端口或 npx 协议互相调用。
4.3 自定义工具背后的 JSON-RPC 链路
工具注册好了以后,我们可以手动通过 HTTP 端点验证一下。比如传输方式为 WebMVC 时,直接向/mcp发送一个 JSON-RPC 请求来查看工具列表:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }返回值会包含工具名、描述和 JSON Schema 的输入参数定义:
{ "jsonrpc": "2.0", "id": 1, "result": { "tools": [ { "name": "getWeather", "description": "根据城市名称查询实时的天气情况,返回天气描述、温度和湿度", "inputSchema": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称" } }, "required": ["city"] } } ] } }调用工具时发送:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "getWeather", "arguments": { "city": "北京" } } }为什么要理解这条链路?因为生产排错时直接对着 JSON-RPC 请求比对着日志更直观。工具没被注册、参数类型不对、传输协议不匹配,通过这一个请求就能立刻看出来。自己具备手测 MCP 端点的能力以后,遇到了问题就不会两眼一抹黑。
5. 进阶:把 MCP 工具装进 Agent / RAG / SQL 实战
5.1 Agent 的基本形态与多工具协同
MCP 的价值发挥最大化,通常是配合一个 Agent 形态的程序。简单理解,Agent 就是让大模型拥有“思考 - 调用工具 - 观察结果 - 再思考”的循环能力。Spring AI 提供了ChatClient的多轮对话能力和工具调用机制,配合 MCP 的多个工具,就能构建出一个很实用的 Agent 雏形。
比如一个客服 Agent,注册了订单查询、库存查询、物流查询三个 MCP 工具。用户问“帮我看看订单 12345 什么时候到货”,大模型先查询电商系统里订单状态,再查询物流系统信息,最后汇总成自然语言回复用户。整个过程用户只发了一条消息,但模型内部已经按需依次调用了多个工具。
我在实际项目里是这么做的:定义一个动态工具列表,在运行时通过ToolCallbackProvider从 MCP Server 注册工具,然后在ChatClient里配置defaultTools,让模型能访问这些回调。同时配合MessageWindowChatMemory保存多轮记忆,实现简单的多轮 Agent 对话:
@Configuration public class AgentConfig { @Bean public ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider toolCallbackProvider) { return builder .defaultTools(toolCallbackProvider) .defaultSystem("你是一个智能客服助手,可以查询订单、物流和库存信息") .build(); } @Bean public ChatMemory chatMemory() { return MessageWindowChatMemory.builder() .maxMessages(20) .build(); } }5.2 用 MCP 把 RAG 检索暴露给大模型
RAG(检索增强生成)是现在大模型应用里最常见的架构,流程基本是:把文档切块、向量化、存进向量数据库,用户提问时先检索最相关内容,再拼到 Prompt 里让模型回答。Spring AI 对 RAG 的支持很成熟,内置了VectorStore接口和多种向量库实现。
把 RAG 检索能力封装成 MCP 工具的思路有两层。如果你是消费方,可以用一个 MCP Server 来暴露“内部文档搜索”工具,大模型需要业务资料时直接调用;如果你是提供方,也可以把自己内部的检索能力打包成 MCP Server,给多个业务系统共享。
举个例子,在企业内部做一个知识库问答系统,我们可以在 Server 端写一个工具:
@Tool(description = "检索企业内部的运维知识库,返回与问题最相关的文档片段") public String searchKnowledgeBase(String question, int topK) { // 内部调用 VectorStore 的 similaritySearch 方法 List<Document> documents = vectorStore.similaritySearch( SearchRequest.builder() .query(question) .topK(topK) .build() ); return documents.stream() .map(Document::getText) .collect(Collectors.joining("\n---\n")); }这样一个工具,模型就具备了对内部资料的精准访问能力,而不用每次都把整个文档库塞进上下文,在控制成本的同时,也降低了幻觉概率。个人做了一些测试后发现,RAG 场景中 MCP 的收益主要在统一入口和权限控制上:多个系统可以通过同一个工具入口检索,同时可以在工具层做数据权限过滤。
5.3 数据库 NL2SQL 的 MCP 封装
最近网上 “NL2SQL” 相关的词很热,也就是把自然语言直接翻译成 SQL 查询数据库。这块如果直接在 Prompt 里给模型放任意的 SQL 执行权限,风险爆炸。更稳妥的做法就是封装成一个 MCP 工具,对内只暴露受控的查询能力,对外只允许特定操作。
我们在实际的 Java 项目里可以写一个 SQL 查询 MCP Server,内置几个工具:
listTables:列出当前数据库所有表名。getTableSchema:获取某张表的字段结构。runSelectQuery:执行只读 SQL 查询,并返回格式化结果。
然后引导模型的使用方式:先让它列出表,再看表结构,最后生成合法的查询 SQL 并执行。服务端做一层校验,通过正则或者 SQL 解析器确认语句确实只是 SELECT,否则直接拒绝。
@Tool(description = "执行只读 SQL 查询,返回结果集") public String runSelectQuery(String sql) { if (!isReadOnlySql(sql)) { return "错误:只允许执行 SELECT 查询"; } List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql); return new ObjectMapper().writeValueAsString(rows); }这套用法在生产里很有价值。原来业务方想要一张报表,需要找数据开发写 SQL、捞数、导出、再整理,现在通过一个只读查询 MCP Server,运营同学直接用自然语言问“上个月华东区域的销售总额”,大模型自动完成全程,安全控制在工具层而不是模型层,链路是可控的。
5.4 Spring AI Alibaba 与 DashScope 的集成建议
国内场景绕不开通义千问、DashScope 这一套。Spring AI Alibaba 是阿里在 Spring AI 基础上做的适配,核心优势是提供了 DashScope 模型的 Spring Boot Starter,对中文场景、企业内网部署和阿里云生态都比较友好。
如果你主要面向国内业务,依赖可以直接换成:
<dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> </dependency>配置方面,base-url 指向https://dashscope.aliyuncs.com/,api-key 使用 DashScope 的 Key,模型名用qwen-plus或qwen-turbo。Spring AI Alibaba 1.0.x 基于 Spring AI 生态,所以 MCP 客户端机制和原版基本一致,也可以直接配合 MCP 使用。区别主要体现在模型依赖的内置工具、模型名称映射,以及部分阿里云产品的一体化整合上,比如它可以直接把阿里云数据库、阿里云 OSS 等定义为开箱即用的 MCP 工具。
这里有个经验:如果一个项目既要求用 OpenAI 官方模型,又要求支持国内模型,最好的做法是把模型通过ChatClient做抽象隔离,MCP 工具层保持不变,到部署环境时通过配置切换模型供应商。Spring AI 在设计上支持这种模式,MCP 工具不关心底层是哪个模型的。
6. 避开这些坑:排查经验与源码级观察
6.1 高频问题排查速查表
| 问题现象 | 最可能原因 | 解决思路 |
|---|---|---|
| 启动后日志里没有 MCP 工具注册 | 缺spring-ai-starter-mcp-client依赖 | 检查依赖是否引入,并确认 spring-ai BOM 版本 |
| stdio 方式连接 npx 一直失败 | 本机没装 Node 或 npx,或者 npx 命令路径不对 | 终端先执行npx -y @modelcontextprotocol/server-everything验证 |
| 模型不调用工具,直接答非所问 | 工具描述不清晰,或模型在选择工具时没识别到工具 | 优化 @Tool description,让用途和输入参数描述更明确 |
| tools/call 请求返回超时 | request-timeout 设置太短 | 把spring.ai.mcp.client.request-timeout调到 30s 以上 |
| 工具参数总是缺少某字段 | 参数未标注 @ToolParam 或方法签名不太明确 | 给每个参数补充 description 和默认值 |
| 自定义 Server 启动后没暴露任何工具 | ToolCallbackProvider 没装配 | 确认启动类里注入了 MethodToolCallbackProvider Bean |
| 连接远程 MCP Server 认证失败 | Token 直接写死在 YAML 或环境变量没注入 | 改用环境变量方式加载,并检查服务端认证策略 |
| 工具返回了 JSON 但因为格式问题模型理解困难 | 返回值太复杂,字段名不够表意 | 尽量返回结构清晰、带明确字段名的 JSON,减少无关嵌套 |
6.2 Debug 的正确姿势
我踩过最多坑的就是 MCP 链路的问题定位,因为整个链路隔着模型调用、客户端、协议传输、服务端等多层逻辑。一旦出现问题,我建议按下面的顺序排查。
先看模型层。把 MCP 工具临时停用,直接用ChatClient调一条普通 Prompt,确认模型接口本身没问题。如果模型都连不上,工具侧再完美也没用。
再看工具注册层。启动时设置logging.level.org.springframework.ai=DEBUG,观察日志里ToolCallbacks 是否被注册,以及每个工具的名称和参数定义。Spring AI 启动阶段会打印被注册到我这个chatClient的工具列表,一眼就能看出来。
然后直接测协议层。如果 Server 是 HTTP 方式,用 Postman 或 curl 发tools/list请求,验证 Server 端是否正常工作。这一步能快速区分是 Server 问题还是客户端问题。
最后看客户端调用层。在tools/call的 Completions 回调处打日志,观察传入的参数和返回结果,确认序列化格式有没有问题。
6.3 从源码理解自动配置触发条件
工具没注册这个现象,光看配置容易好几次都发现不了。如果从源码层面理解触发条件,定位速度快得多。
Spring AI 客户端自动配置核心类是McpClientAutoConfiguration,会对配置里每个 server 创建一个McpClient,随后通过McpToolUtils和ToolCallbackProvider把工具注册进 Spring 容器。这个自动配置只有一个触发条件:classpath 下存在spring-ai-starter-mcp-client,并且配置中有spring.ai.mcp.client.servers列表。换个说法,你没有配置 servers,自动配置就算执行了,也没东西可注册。
Server 端自动配置核心类是McpServerAutoConfiguration,它需要扫描到容器里的ToolCallbackProvider。Spring Boot 启动时,如果没有注册ToolCallbackProvider的 Bean,那 Server 暴露的工具就是空列表。很多人以为方法上加 @Tool 就能自动暴露,其实还差这最后一步:要把包含该方法对象的 provider 上报给 McpServer 自动配置。
提示:如果你的 Service 类上有 @Tool 方法,但始终没有暴露,请检查启动类中是否有一个
MethodToolCallbackProvider或ToolCallbackProvider类型的 Bean。这是最常见的“工具找不到”根因。
6.4 关于版本升级和长期维护的一点建议
Spring AI 更新速度非常快,几乎每个月都有新版本,2.0 系列也在路上。我经历过的版本升级启发是:不要盲目追新,尤其是生产环境,尽量固定在某个 1.0.x 的 patch 版本。如果看到新版本提供关键修复再升,升级前主要关注三点:
spring.ai.mcp.client.servers配置的字段名是否变更。McpClient、ToolCallbackProvider包路径有没有移动。@Tool注解是否新增了必需属性。
我在一次升级中,遇到工具名称从短名称变成了“类名#方法名”的形式,导致模型调用不匹配,后来把版本固定回去才解除问题。这类情况在 MCP 协议迭代期大概率还会遇到,保持配置依附于版本、依赖仓库锁定版本号是更稳妥的做法。
还有一点,MCP Server 的暴露范围要控制好。如果工具里包含了删除操作、有权限敏感的数据查询,务必在@Tool(description = ...)里写清楚触发条件和限制,同时在工具内部做身份鉴权和参数校验。模型是不可靠的执行者,防线要放在工具层,不要指望模型的道德约束。这是我在真实项目里付出过代价后拿到的经验。
最后分享一个我在实操里觉得最有实效的小技巧。第一次跑 MCP 的时候,先用云端现成的官方 Everything Server 试验,不写业务工具验证整个链路;链路通了以后,再写自己的 @Tool。这样一旦出现问题,可以明确区分是“协议链路问题”还是“业务工具问题”,不会在一堆代码里瞎找半天。如果你现在正准备开始 Spring AI 的项目,不妨先用十五分钟把上面的客户端 Demo 跑通,你就能看到 AI 模型调用工具的全过程,方向对了,后面的路自然就顺了。