5分钟读懂Spring-AI-Tool机制-从-Tool注解到MCP工具桥接全链路
前言
“我的 @Tool 方法怎么没被识别?”
“MCP Server 的工具是怎么桥接到本地 ChatClient 的?”
“ToolCallback 和 ToolCallbackProvider 到底是啥关系?”
这是很多刚接触 Spring AI Tool 机制开发者共同的困惑。
官方文档教你怎么用,但很少告诉你底层是怎么跑起来的。结果就是遇到问题不知道从哪里下手排查。
今天这篇文章,我会用最通俗的语言,带你搞懂 Spring AI Tool 的底层运行机制。读完你会明白:
- 一个
@Tool注解是如何变成 AI 可调用工具的 - 几种常见 Provider 各自的职责和使用场景
- 工具从注册到被 LLM 调用的完整链路
版本说明(截至 2026-03-22):
Spring AI 最新稳定版是 1.1.3,最新预览版是 2.0.0-M3(发布于 2026-03-17)。1.1.2当然也属于稳定版,只是它不是截至 2026-03-22 的最新稳定版。
本文主体以当前官方 Reference / API 文档为准;涉及 2.0.0-M3 的变化,我会单独标注,避免把“稳定版现状”和“预览版变化”混在一起。如果你当前项目使用的是
spring-ai-alibaba:1.1.2.0,要注意它传递进来的org.springframework.ai依赖仍然是1.1.2,不是1.1.3。所以像AugmentedToolCallback这类在1.1.3可见的 API,你的 IDE 里可能暂时看不到。
一、核心接口:ToolCallback 与 ToolCallbackProvider
先说结论:ToolCallback 是单个工具,ToolCallbackProvider 是工具集合的提供者。
1.1 ToolCallback:单个工具的抽象
ToolCallback 是 Spring AI 工具体系的最小单元,代表一个可以被 AI 模型调用的工具。
public interface ToolCallback {
// 工具定义:名称、描述、输入参数的 JSON Schema
ToolDefinition getToolDefinition();
// 工具元数据:比如 returnDirect 控制结果是否直接返回
ToolMetadata getToolMetadata();
// 执行工具,入参是 JSON 字符串
String call(String toolInput);
// 带上下文执行
String call(String toolInput, ToolContext toolContext);
}
它的职责很简单:
- 告诉 AI 这个工具叫什么、干什么、需要什么参数
- 接收 AI 传来的 JSON 参数,执行逻辑,返回结果
两个最常见的本地实现:
MethodToolCallback— 包装 Java 方法(配合@Tool注解使用)FunctionToolCallback— 包装函数式接口(Function、Supplier、Consumer、BiFunction等)
1.2 ToolCallbackProvider:工具工厂
ToolCallbackProvider 负责批量提供工具。
public interface ToolCallbackProvider {
// 返回所有工具回调
ToolCallback[] getToolCallbacks();
}
它的职责: 从不同来源收集工具,统一暴露给 ChatClient 注册。
二、五种 Provider:四类来源 + 一个增强器
当前官方 API 中,ToolCallbackProvider 已知有 5 个实现。其中前四类负责“从哪里来”,最后一类负责“在已有工具外再包一层增强”。
| Provider | 工具来源 | 特点 |
|---|---|---|
| StaticToolCallbackProvider | 静态数组 | 最简单,构造后不可变,手动创建固定工具集 |
| MethodToolCallbackProvider | @Tool 注解方法 |
自动扫描对象中的 @Tool 方法并生成 ToolCallback |
| SyncMcpToolCallbackProvider | MCP 同步客户端 | 连接远程 MCP Server,自动发现并桥接工具 |
| AsyncMcpToolCallbackProvider | MCP 异步客户端 | 与 Sync 类似,但使用异步客户端;同样实现 getToolCallbacks(),另外提供响应式辅助能力 |
| AugmentedToolCallbackProvider | 已有工具的增强包装 | 给现有工具追加输入 Schema 扩展字段,例如额外的结构化元数据 |
三、MethodToolCallbackProvider:本地注解扫描
这是最常用的方式,通过 @Tool 注解自动生成工具。
@Service
public class WeatherService {
@Tool(name = "getWeather", description = "获取指定城市的天气")
public String getWeather(@JsonProperty("city") String city) {
return city + ": 晴, 25°C";
}
@Tool(name = "query_weather_by_city_date", description = "根据城市和日期查询天气")
public String queryByCityAndDate(
@JsonProperty("city") String city,
@JsonProperty("date") String date) {
return city + "在" + date + ": 多云, 22°C";
}
}
注册方式:
@Configuration
public class ToolConfig {
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService) // 传入 Service 对象
.build();
}
}
工作原理:
MethodToolCallbackProvider 通过反射扫描 weatherService 的所有方法,找到带有 @Tool 注解的方法,为每个方法生成一个 MethodToolCallback。
上面这个例子会注册 2 个工具:
getWeather— 根据城市查天气query_weather_by_city_date— 根据城市和日期查天气
四、多个 Service 怎么注册?
toolObjects() 接收可变参数,可以传多个对象:
@Bean
public ToolCallbackProvider allTools(
WeatherService weatherService,
OrderService orderService,
TradeService tradeService,
GoodsService goodsService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService, orderService, tradeService, goodsService)
.build();
}
方式二:多个 Bean(Spring AI 自动合并)
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
@Bean
public ToolCallbackProvider orderTools(OrderService orderService) {
return MethodToolCallbackProvider.builder()
.toolObjects(orderService)
.build();
}
如果你走的是 Spring Boot 自动配置的 MCP Server / MCP Client 场景,框架会收集这些 ToolCallbackProvider Bean 并合并使用,效果一样。
如果你是手动构建 ChatClient,那就仍然需要自己把这些 Provider 返回的 ToolCallback[] 显式传给 defaultToolCallbacks(...)。
五、SyncMcpToolCallbackProvider:远程工具桥接
这是 MCP 开发的核心,把远程 MCP Server 的工具包装成本地 ToolCallback。
@Component
public class McpClientService {
@Autowired
private SyncMcpToolCallbackProvider toolCallbackProvider;
@PostConstruct
public void init() {
// 获取所有远程 MCP server 的工具
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();
// 注册到 ChatClient
this.chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallbacks)
.build();
}
}
它做了什么:
application.yml 配置的 MCP servers
↓
Spring AI 自动装配 SyncMcpToolCallbackProvider
↓
读取配置,连接各个 MCP Server(启动进程或建立 HTTP 连接)
↓
调用 MCP 的 listTools 接口获取工具列表
↓
把每个远程工具包装成本地 ToolCallback
↓
注册到 ChatClient,LLM 可以调用
效果:
chatClient.prompt()
.user("北京天气怎么样?")
.call()
.content();
// LLM 自动调用远程 mcp-server 的 getWeather 工具
// 返回: "北京: 晴, 25°C"
Sync vs Async:
SyncMcpToolCallbackProvider— 使用McpSyncClient,getToolCallbacks()返回ToolCallback[]AsyncMcpToolCallbackProvider— 使用McpAsyncClient,同样实现getToolCallbacks()返回ToolCallback[];另外提供asyncToolCallbacks(List<McpAsyncClient>)返回Flux<ToolCallback>
注意: 两者都能连接多个 MCP Server,选择取决于你的应用是同步还是响应式。详见后文第八章。
六、MCP Server 与 Client:各司其职
很多初学者会混淆:MCP Server 和 Client 都用 Provider,它们会不会冲突?
答案是不会。它们是互补关系,各负责一端:
6.1 MCP Server 端:现在有两条主路
方案 A:@McpTool 注解 + annotation scanner
↓
直接暴露为 MCP Tool(这是当前 Boot Starter 文档重点推荐的方式)
方案 B:Spring AI ToolCallback 路径
@Service + @Tool 方法 / ToolCallback Bean / ToolCallbackProvider Bean
↓
通过 tool-callback-converter 自动转换为 MCP Tool 规格
↓
通过 MCP 协议(stdio / SSE / Streamable-HTTP)暴露给客户端
6.2 MCP Client 端:消费工具
连接到 MCP Server
↓
通过 MCP 协议获取工具列表(listTools)
↓
SyncMcpToolCallbackProvider(远程工具 → 本地 ToolCallback)
↓
注册到 ChatClient,让 LLM 能调用
6.3 一张图看懂两端关系

6.4 简单类比
| 角色 | 类比 | 使用的类 |
|---|---|---|
| MCP Server | 餐厅(暴露菜品) | @McpTool 或 ToolCallbackProvider |
| MCP Client | 外卖平台(聚合餐厅) | SyncMcpToolCallbackProvider |
- 餐厅既可以直接用
@McpTool出菜单,也可以先产出ToolCallback,再由 Server Starter 自动转换成 MCP Tool - 外卖平台通过
SyncMcpToolCallbackProvider把各个餐厅的"菜单"变成用户可点的"商品"(本地ToolCallback)
6.5 如果 Server 端不用 MethodToolCallbackProvider 会怎样?
这里要加一个前提:只有当你选择的是 @Tool → MethodToolCallbackProvider → MCP Tool 这条路径时,这个问题才成立。
// 没有 MethodToolCallbackProvider
public class WeatherService {
@Tool(name = "getWeather", description = "...")
public String getWeather(String city) { ... }
}
结果:仅靠 @Tool 注解本身,不会自动变成 MCP Tool。如果你既没有配 MethodToolCallbackProvider,也没有把它包装成其他 ToolCallback Bean / ToolCallbackProvider Bean,那么客户端 listTools 拿到的就是空列表。
但如果你走的是 @McpTool + annotation scanner 这条路,那就不需要 MethodToolCallbackProvider。
七、完整调用链:从注册到执行
以下流程图是以本文主线的 @Tool / ToolCallback → ChatClient → tool calling 为例,把所有环节串起来看:
┌─────────────────────────────────────────────────────────────────┐
│ 注册阶段 │
└─────────────────────────────────────────────────────────────────┘
@Service + @Tool 注解方法 application.yml MCP 配置
│ │
▼ ▼
MethodToolCallbackProvider SyncMcpToolCallbackProvider
│ │
└──────────────────┬───────────────────────────┘
▼
ToolCallback[]
│
▼
ChatClient.builder()
.defaultToolCallbacks()
│
▼
ChatClient 初始化完成
┌─────────────────────────────────────────────────────────────────┐
│ 调用阶段 │
└─────────────────────────────────────────────────────────────────┘
用户: "北京天气怎么样?"
│
▼
ChatModel 发送请求(携带工具定义:name + description + inputSchema)
│
▼
AI 模型决策:需要调用 getWeather 工具
│
▼
AI 返回工具调用请求(工具名 + JSON 参数)
│
▼
ToolCallingManager 接收
│
▼
ToolCallbackResolver 找到对应的 ToolCallback
│
▼
ToolCallback.call('{"city":"北京"}')
│
▼
返回: "北京: 晴, 25°C"
│
▼
ToolResponseMessage 送回 AI 模型
│
▼
AI 生成最终回答:"北京今天天气晴朗,气温 25°C"
八、五种 ToolCallback 各有什么用?
前面讲了 Provider,现在看看它们生成的 ToolCallback 有什么区别:
8.1 MethodToolCallback
用途: 包装 @Tool 注解的 Java 方法
@Tool(name = "getWeather", description = "获取天气")
public String getWeather(String city) {
return city + ": 晴, 25°C";
}
// ↓ 被包装成
MethodToolCallback
执行时: 直接反射调用 Java 方法
8.2 FunctionToolCallback
用途: 通过函数式接口封装已有代码功能
// 调用已有的 Service 方法
FunctionToolCallback.builder("calculatePrice", (Function<String, BigDecimal>) input -> {
return orderService.calculatePrice(input);
})
.build();
执行时: 调用 Function.apply()
8.3 SyncMcpToolCallback vs AsyncMcpToolCallback
这是最容易被混淆的一对。
两者都用于桥接远程 MCP Server 的工具,区别只在于底层使用的 MCP 客户端类型:
| 对比项 | SyncMcpToolCallback | AsyncMcpToolCallback |
|---|---|---|
| 底层客户端 | McpSyncClient |
McpAsyncClient |
| 面向调用方的同步入口 | getToolCallbacks() |
getToolCallbacks() |
| 生成的 Provider | SyncMcpToolCallbackProvider |
AsyncMcpToolCallbackProvider |
| 是否支持多 MCP Server | ✅ 是 | ✅ 是 |
| 额外的响应式能力 | 无 | asyncToolCallbacks(List<McpAsyncClient>) -> Flux<ToolCallback> |
关键点:两者都能连接多个 MCP Server,不需要为了多 Server 而选择 Async!
API 视角
SyncMcpToolCallbackProvider:
public ToolCallback[] getToolCallbacks()
AsyncMcpToolCallbackProvider:
public ToolCallback[] getToolCallbacks()
public static Flux<ToolCallback> asyncToolCallbacks(List<McpAsyncClient> mcpClients)
也就是说:对外暴露的统一接口仍然是 ToolCallback[];Async 版本只是额外提供了响应式流式组装能力。
8.4 AugmentedToolCallback
这是 当前 1.1.3 API 文档里已经出现、但很多文章还没写到的一个实现。
注意: 如果你的项目还停留在
1.1.0或1.1.2,IDE 里很可能看不到AugmentedToolCallback/AugmentedToolCallbackProvider。
我本地实际检查过 Maven 缓存:这些类出现在spring-ai-model:1.1.3中,但1.1.0/1.1.2里没有。
用途: 给现有工具追加输入 Schema 扩展字段,而不改原始工具方法签名。
典型场景:
- 给工具追加结构化元数据字段
- 统一记录工具调用时的额外参数
- 在不改原工具入参的情况下扩展工具协议
底层对应的 Provider 是 AugmentedToolCallbackProvider,相关低层工具是 ToolInputSchemaAugmenter。
8.5 我该用哪个?
| 场景 | 推荐 |
|---|---|
| Spring MVC + 普通场景 | SyncMcpToolCallbackProvider |
| WebFlux 响应式应用 | AsyncMcpToolCallbackProvider |
| 需要连接多个 MCP Server | 两者都可以 ✅ |
| 需要在现有工具外追加结构化字段 | AugmentedToolCallbackProvider |
结论:如果你用的是 Spring MVC,直接选 SyncMcpToolCallbackProvider 即可,无需换架构。
8.6 FunctionCallback 已废弃,怎么迁移?
Spring AI 早期使用 FunctionCallback,后来统一迁移到 ToolCallback API。FunctionCallback 已废弃,建议迁移。
为什么废弃?
早期各大 AI 厂商叫法不统一,后来统一叫 “tool calling”:
- OpenAI: function calling → tool calling
- Anthropic: tool use → tool calling
Spring AI 对齐后废弃了 FunctionCallback,统一使用 ToolCallback。
迁移方式一:@Tool 注解(推荐)
旧代码(FunctionCallback):
@Component
public class WeatherFunction {
public String getWeather(String city) {
return city + ": 晴, 25°C";
}
}
// 注册
FunctionCallback callback = FunctionCallback.builder()
.function("getWeather", weatherFunction)
.description("获取指定城市的天气")
.inputType(WeatherRequest.class)
.build();
新代码(@Tool 注解):
@Service
public class WeatherService {
@Tool(name = "getWeather", description = "获取指定城市的天气")
public String getWeather(@JsonProperty("city") String city) {
return city + ": 晴, 25°C";
}
}
// 注册 - 自动扫描
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
迁移方式二:FunctionToolCallback(包装现有接口)
如果你不想改原有代码,可以用包装方式:
// 原有的 Service,不需要改
@Service
public class OrderService {
public BigDecimal calculatePrice(String orderId) {
// 原有逻辑
return new BigDecimal("99.99");
}
}
// 用 FunctionToolCallback 包装
@Configuration
public class ToolConfig {
@Bean
public ToolCallback orderPriceTool(OrderService orderService) {
return FunctionToolCallback.builder("calculatePrice", orderService::calculatePrice)
.description("根据订单ID计算价格")
.inputType(String.class)
.build();
}
}
API 对照表
| 旧 API | 新 API |
|---|---|
FunctionCallback |
ToolCallback |
FunctionCallback.builder().function() |
FunctionToolCallback.builder(name, function) |
FunctionCallback.builder().method() |
MethodToolCallback.builder() |
FunctionCallingOptions |
ToolCallingChatOptions |
ChatClient.builder().defaultFunctions() |
ChatClient.builder().defaultTools() |
FunctionCallingOptions.builder().functionCallbacks() |
ToolCallingChatOptions.builder().toolCallbacks() |
九、类关系图速记
ToolCallbackProvider (接口)
│
├── StaticToolCallbackProvider ← 静态工具数组
│
├── MethodToolCallbackProvider ← 本地 @Tool 注解方法
│
├── SyncMcpToolCallbackProvider ← MCP 同步客户端
│
├── AsyncMcpToolCallbackProvider ← MCP 异步客户端
│
└── AugmentedToolCallbackProvider ← 现有工具增强器
ToolCallback (接口)
│
├── MethodToolCallback ← 包装 Java 方法
│
├── FunctionToolCallback ← 包装函数式接口
│
├── SyncMcpToolCallback ← 包装 MCP 同步工具
│
├── AsyncMcpToolCallback ← 包装 MCP 异步工具
│
└── AugmentedToolCallback ← 包装增强后的工具
十、常见问题
Q1: Bean 方法名(如 weatherTools)有什么影响?
A: 没有影响,这只是 Spring Bean 的名称。真正有意义的是 @Tool(name = "getWeather") 里的 name,那才是暴露给 LLM 的工具名称。
Q2: 同名工具会冲突吗?
A: 默认情况下通常不会直接冲突。 当前 MCP Client Boot Starter 默认使用 DefaultMcpToolNamePrefixGenerator,会在跨连接出现重名时自动加前缀,保证工具名唯一。
只有当你显式关闭前缀生成(例如使用 McpToolNamePrefixGenerator.noPrefix())且多个 MCP Server 暴露了同名工具时,才会因为重名抛出 IllegalStateException。
Q3: 工具执行异常会怎样?
A: 默认配置 spring.ai.tools.throw-exception-on-error=false,异常会作为错误消息返回给模型;设置为 true 则会直接抛出异常。
十一、最新版本现状(截至 2026-03-22)
11.1 先分清“稳定版”和“预览版”
-
最新稳定版:
1.1.3 -
最新预览版:
2.0.0-M3
发布日期是 2026-03-17。如果你在生产环境写文章,建议主体仍以 1.1.3 的官方 Reference 为准;如果你在评估 Spring Boot 4 / Jackson 3 / Spring AI 2.x,再单独补充 M3 变化。
11.2 这些是“当前仍然成立”的方向
-
FunctionCallback→ToolCallback的迁移仍然是官方推荐路径
但这不是 2.0.0-M3 才新增的变化,而是之前就已经开始的统一迁移方向。官方现在单独维护了一篇迁移指南。 -
Tool Argument Augmentation 已进入当前 tools 文档
当前文档里对应的官方命名是 Tool Argument Augmentation。如果你使用的是 1.1.3,核心类是:AugmentedToolCallbackProviderAugmentedToolCallbackToolInputSchemaAugmenter
-
Dynamic Tool Discovery 已有官方指南
ToolSearchToolCallAdvisor确实可以做动态工具发现,官方指南也给出了 34% - 64% 的 token 节省数据。
但要注意:它来自org.springaicommunity:tool-search-tool,不是 Spring AI core 自己内置的核心模块。
11.3 2.0.0-M3 真正需要关注的 Breaking Changes
-
MCP Annotations 迁入 Spring AI 核心
包名从org.springaicommunity.mcp迁到org.springframework.ai.mcp.annotation。 -
Spring 专用 MCP transport 实现迁入 Spring AI 项目
原先在 MCP Java SDK 里的 Spring transport 实现,迁到了org.springframework.ai.mcp相关模块。 -
Jackson 2 → Jackson 3
这是实打实的破坏性变更,尤其影响依赖和包名。 -
ToolContext不再自动包含对话历史
这是 M3 release notes 明确点名的一项 breaking change,升级时要特别注意。
11.4 本文的核对依据
- Spring AI Reference
1.1.3 - Spring AI current API Javadoc
- Spring AI
2.0.0-M3release notes - Spring AI 2.0 Dynamic Tool Search guide
总结
用一句话回顾:
ToolCallback 是单个工具的封装,ToolCallbackProvider 是工具集合的工厂。MethodToolCallbackProvider 处理本地 @Tool 方法,Sync/AsyncMcpToolCallbackProvider 桥接远程 MCP 工具,AugmentedToolCallbackProvider 负责给现有工具做增强;而在 MCP Server 端,当前还可以直接用 @McpTool 这条注解路线。
理解了这套机制,你就能:
- 清楚知道自己的工具为什么没被识别
- 明白 MCP 工具是如何接入的
- 遇到问题时知道从哪个环节排查
欢迎关注公众号 FishTech Notes,一块交流使用心得!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)