前言

“我的 @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);
}

它的职责很简单:

  1. 告诉 AI 这个工具叫什么、干什么、需要什么参数
  2. 接收 AI 传来的 JSON 参数,执行逻辑,返回结果

两个最常见的本地实现:

  • MethodToolCallback — 包装 Java 方法(配合 @Tool 注解使用)
  • FunctionToolCallback — 包装函数式接口(FunctionSupplierConsumerBiFunction 等)

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 — 使用 McpSyncClientgetToolCallbacks() 返回 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 餐厅(暴露菜品) @McpToolToolCallbackProvider
MCP Client 外卖平台(聚合餐厅) SyncMcpToolCallbackProvider
  • 餐厅既可以直接用 @McpTool 出菜单,也可以先产出 ToolCallback,再由 Server Starter 自动转换成 MCP Tool
  • 外卖平台通过 SyncMcpToolCallbackProvider 把各个餐厅的"菜单"变成用户可点的"商品"(本地 ToolCallback

6.5 如果 Server 端不用 MethodToolCallbackProvider 会怎样?

这里要加一个前提:只有当你选择的是 @ToolMethodToolCallbackProvider → 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.01.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.1.3

  2. 最新预览版:2.0.0-M3
    发布日期是 2026-03-17。如果你在生产环境写文章,建议主体仍以 1.1.3 的官方 Reference 为准;如果你在评估 Spring Boot 4 / Jackson 3 / Spring AI 2.x,再单独补充 M3 变化。

11.2 这些是“当前仍然成立”的方向

  1. FunctionCallbackToolCallback 的迁移仍然是官方推荐路径
    但这不是 2.0.0-M3 才新增的变化,而是之前就已经开始的统一迁移方向。官方现在单独维护了一篇迁移指南。

  2. Tool Argument Augmentation 已进入当前 tools 文档
    当前文档里对应的官方命名是 Tool Argument Augmentation。如果你使用的是 1.1.3,核心类是:

    • AugmentedToolCallbackProvider
    • AugmentedToolCallback
    • ToolInputSchemaAugmenter
  3. Dynamic Tool Discovery 已有官方指南
    ToolSearchToolCallAdvisor 确实可以做动态工具发现,官方指南也给出了 34% - 64% 的 token 节省数据。
    但要注意:它来自 org.springaicommunity:tool-search-tool,不是 Spring AI core 自己内置的核心模块。

11.3 2.0.0-M3 真正需要关注的 Breaking Changes

  1. MCP Annotations 迁入 Spring AI 核心
    包名从 org.springaicommunity.mcp 迁到 org.springframework.ai.mcp.annotation

  2. Spring 专用 MCP transport 实现迁入 Spring AI 项目
    原先在 MCP Java SDK 里的 Spring transport 实现,迁到了 org.springframework.ai.mcp 相关模块。

  3. Jackson 2 → Jackson 3
    这是实打实的破坏性变更,尤其影响依赖和包名。

  4. ToolContext 不再自动包含对话历史
    这是 M3 release notes 明确点名的一项 breaking change,升级时要特别注意。

11.4 本文的核对依据

  • Spring AI Reference 1.1.3
  • Spring AI current API Javadoc
  • Spring AI 2.0.0-M3 release notes
  • Spring AI 2.0 Dynamic Tool Search guide

总结

用一句话回顾:

ToolCallback 是单个工具的封装,ToolCallbackProvider 是工具集合的工厂。MethodToolCallbackProvider 处理本地 @Tool 方法,Sync/AsyncMcpToolCallbackProvider 桥接远程 MCP 工具,AugmentedToolCallbackProvider 负责给现有工具做增强;而在 MCP Server 端,当前还可以直接用 @McpTool 这条注解路线。

理解了这套机制,你就能:

  • 清楚知道自己的工具为什么没被识别
  • 明白 MCP 工具是如何接入的
  • 遇到问题时知道从哪个环节排查

欢迎关注公众号 FishTech Notes,一块交流使用心得!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐