MCP,全称是 Model Context Protocol(模型上下文协议),是一种标准化协议,使 AI 模型能够以结构化的方式与外部工具和资源进行交互。该协议支持多种传输机制,以在不同环境中提供灵活性。可以把它想象成一个让 AI 模型更顺畅工作的桥梁和助手。

Tool Calling

在讲解 MCP 之前,我们首先来讲解工具调用(Tool Calling)。

工具调用(也称为函数调用)是人工智能应用程序中的一种常见模式,允许模型与一组 API 或工具交互,以增强其功能。

工具调用的作用

  • 信息检索(Information Retrieval)。此类工具可用于从外部源(如数据库、web 服务、文件系统或 web 搜索引擎)检索信息。目标是增强模型的知识,使其能够回答无法回答的问题。因此,它们可用于检索增强生成(RAG)场景。例如,一个工具可用于检索给定位置的当前天气,检索最新的新闻文章,或查询数据库中的特定记录。

  • 采取行动(Taking Action)。此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。目标是自动化那些需要人工干预或显式编程的任务。例如,一个工具可用于为与聊天机器人交互的客户预订航班,填写网页上的表单,或在代码生成场景中基于自动测试实现 Java 类。

尽管我们通常将工具调用称为模型功能,但实际上是由客户端应用程序提供工具调用逻辑。模型只能请求工具调用并提供输入参数,而应用程序负责根据输入参数执行工具调用并返回结果。该模型永远无法访问作为工具提供的任何 API,这是一个关键的安全考虑因素。

SpringAI 提供了方便的 API 来定义工具、解析来自模型的工具调用请求以及执行工具调用。以下部分概述了 SpringAI 中的工具调用功能。

快速开始

让我们看看如何在 SpringAI 中开始使用工具调用。我们将实现两个简单的工具:

  • 用于信息检索。

  • 用于采取行动。

信息检索工具将用于获取用户所在时区的当前日期和时间。操作工具将用于在指定时间内设置警报。

信息检索

人工智能模型无法访问实时信息。任何假设知道当前日期或天气预报等信息的问题都无法由模型回答。但是,我们可以提供一个可以检索此信息的工具,并在需要访问实时信息时让模型调用此工具。

让我们在 DateTimeToolsService 类中实现一个工具,以获取用户所在时区的当前时间。这个工具不会有任何争论。Spring Framework 中的 LocaleContextHolder 可以提供用户的时区。该工具将被定义为带有 @tool 注释的方法。为了帮助模型理解是否以及何时调用此工具,我们将详细描述这些工具的功能。

@Service
public class DateTimeToolsService {

    @Tool(description = "获取用户时区中的当前时间")
    public String getCurrentDateTime() {
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }
}

接下来,让我们还需要将该工具提供给大模型。在这个例子中,我们将使用 ChatClient 与模型进行交互。我们将通过 tools() 方法传递 DateTimeToolsService 的实例,为模型提供该工具。当模型需要知道当前日期和时间时,它将请求调用该工具。在内部,ChatClient 将调用工具并将结果返回给模型,然后模型将使用工具调用结果生成对原始问题的最终响应。

@GetMapping(value = "/stream1", produces = "text/html;charset=utf-8")
public Flux<String> stream1(@RequestParam("question") String question) {
    return zhipuChatClient.prompt()
            .tools(dateTimeToolsService)
            .user(question)
            .stream()
            .content();
}

注意:这里我们使用的是局部方式进行注册,其他方式我们会在后边详细提到。 输出将类似于:

image-20251111110430142

你还可以这样问:

image-20251111110541644

如果你问一些其他的问题:

image-20251111110631857

会发现大模型会直接进行回答,并没有使用工具。

你可以再次尝试问同样的问题。这一次,不要为模型提供工具。输出将类似于:

image-20251111110919816

如果没有这个工具,模型就不知道如何回答这个问题,因为它没有能力确定当前的日期和时间。

采取行动

人工智能模型可用于生成实现某些目标的计划。例如,模型可以生成预订丹麦之旅的计划。但是,模型无法执行该计划。这就是工具发挥作用的地方:它们可以用来执行模型生成的计划。

在前面的示例中,我们使用了一个工具来确定当前日期和时间。在这个例子中,我们将定义第二个工具,用于在特定时间设置警报。目标是从现在开始设置10分钟的警报,因此我们需要为模型提供这两种工具来完成这项任务。

我们将把新工具添加到与以前相同的 DateTimeToolsService 类中。新工具将采用一个参数,即 ISO-8601 格式的时间。然后,该工具将向控制台打印一条消息,指示已在给定时间内设置报警。与之前一样,该工具被定义为一个用 @tool 注释的方法,我们还使用它来提供详细的描述,以帮助模型理解何时以及如何使用该工具。

@Tool(description = "以ISO-8601格式为给定时间设置用户警报")
public void setAlarm(String time) {
    LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
    log.info("报警设置 {}", alarmTime);
}

接下来,让我们将这两个工具都提供给模型。我们将使用 ChatClient 与模型进行交互。我们将通过 tools() 方法传递 DateTimeToolsService 的实例,为模型提供工具。当我们要求在10分钟后设置闹钟时,模型首先需要知道当前的日期和时间。然后,它将使用当前日期和时间来计算报警时间。最后,它将使用报警工具设置报警。在内部,ChatClient 将处理来自模型的任何工具调用请求,并向其发送任何工具调用执行结果,以便模型能够生成最终响应。

@GetMapping(value = "/stream2", produces = "text/html;charset=utf-8")
public Flux<String> stream2(@RequestParam("question") String question) {
    return zhipuChatClient.prompt()
            .tools(dateTimeToolsService)
            .user(question)
            .stream()
            .content();
}

同样的,这里仍然使用局部方式进行注册。

在应用程序日志中,您可以检查是否在正确的时间设置了警报。

image-20251111112058466

注意:这里只是将它输出到控制台中。

概述

SpringAI 通过一组灵活的抽象来支持工具调用,允许您以一致的方式定义、解析和执行工具。下来我们概述了 SpringAI 中工具调用的主要概念和组件。

image-20251111112829933
  1. 当我们希望向模型提供工具时,会在聊天请求中包含其定义。每个工具定义包括名称、描述以及输入参数的模式。
  2. 当模型决定调用工具时,它会发送一个响应,其中包含工具名称和根据定义模式建模的输入参数。
  3. 该应用程序负责使用工具名称来识别并执行带有指定输入参数的工具。
  4. 工具调用的结果由应用程序处理。
  5. 应用程序将工具调用结果返回给模型。
  6. 该模型利用工具调用结果作为额外上下文生成最终响应。

工具是调用工具的基本单元,通过 ToolCallback 接口进行建模。SpringAI 内置支持从方法和函数中指定 ToolCallback,但您始终可以定义自己的 ToolCallback 实现以支持更多使用场景。

ChatModel 实现透明地将工具调用请求分派给相应的 ToolCallback 实现,并将工具调用结果发送回模型,模型最终将生成最终响应。他们使用 ToolCallingManager 接口来执行此操作,该接口负责管理工具执行生命周期。

ChatClientChatModel 都接受 ToolCallback 对象列表,以使工具可供模型和最终执行它们的 ToolCallingManager 使用。

除了直接传递 ToolCallback 对象外,您还可以传递一个工具名称列表,该列表将使用 ToolCallback Resolver 接口动态解析。

以下部分将详细介绍所有这些概念和 API,包括如何定制和扩展它们以支持更多用例。

方法即工具

SpringAI 通过两种方式为从方法中指定工具(即 ToolCallback)提供内置支持:

  • 通过声明方式,使用 @Tool 注释。
  • 通过编程方式,使用实现 MethodToolCallback 接口。

声明方式

您可以通过使用 @tool 对方法进行注释,将其转化为工具。

@Tool(description = "获取用户时区中的当前时间")
public String getCurrentDateTime() {
    return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}

@Tool 注释允许您提供有关工具的关键信息:

  • name:工具的名称。如果没有提供,将使用方法名称。AI 模型在调用工具时使用此名称来标识工具。因此,不允许在同一类中有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。
  • description:工具的描述,模型可以使用它来了解何时以及如何调用工具。如果没有提供,方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的以及如何使用它至关重要。如果不能提供一个好的描述,可能会导致模型在应该使用的时候不使用工具,或者使用不当。
  • returnDirect:工具结果是应该直接返回给客户端还是传递回模型。
  • resultConverterToolCallResultConverter 实现,用于将工具调用的结果转换为 String 对象,以发送回 AI 模型。

该方法可以是静态的或实例的,并且可以具有任何可见性。包含该方法的类可以是顶级类或嵌套类,它也可以具有任何可见性。

您可以为大多数类型(基元、POJO、枚举、列表、数组、映射等)的方法定义任意数量的参数(包括无参数)。同样,该方法可以返回大多数类型,包括 void。如果该方法返回值,则返回类型必须是可序列化类型,因为结果将被序列化并发送回模型。

SpringAI 将自动为 @Tool 注释方法的输入参数生成 JSON 模式。模型使用模式来了解如何调用工具和准备工具请求。@ToolParam 注释可用于提供有关输入参数的其他信息,例如描述参数是必需的还是可选的。默认情况下,所有输入参数都被认为是必需的。

@Tool(description = "以ISO-8601格式为给定时间设置用户警报")
void setAlarm(@ToolParam(description = "ISO-8601格式的时间") String time) {
    LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
    log.info("报警设置 {}", alarmTime);
}

@ToolParam 注释允许您提供有关工具参数的关键信息:

  • description:参数的描述,模型可以使用它来更好地理解如何使用它。例如,参数应该采用什么格式,允许使用什么值等等。
  • required:参数是必需的还是可选的。默认情况下,所有参数都被认为是必需的。

如果一个参数被注释为 @Nullable,则它将被视为可选的,除非使用 @ToolParam 注释明确标记为必需的。

ChatClient 添加工具

当使用声明方式时,您可以在调用 ChatClient 时将工具类实例传递给 tools() 方法。此类工具仅适用于添加到其中的特定聊天请求。

zhipuChatClient.prompt()
                .tools(dateTimeToolsService)
                .user(question)
                .stream()
                .content();

ChatClient 将从工具类实例中的每个 @Tool 注释方法生成一个 ToolCallback,并将其传递给模型。如果您更喜欢自己生成 ToolCallback,可以使用 ToolCallback 实用程序类。

ToolCallback[] dateTimeTools = ToolCallbacks.from(dateTimeToolsService);
zhipuChatClient.prompt()
        .toolCallbacks(dateTimeTools)
        .user(question)
        .stream()
        .content();

ChatClient 添加默认工具

使用声明性规范方法时,您可以向 ChatClient 添加默认工具。生成器通过将工具类实例传递给 defaultTools() 方法。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。

@Bean("zhiPuChatClient")
public ChatClient chatClient() {
    return ChatClient.builder(zhiPuAiChatModel)
            .defaultTools(new DateTimeToolsService())
            .build();
}

[!WARNING]

默认工具在由同一 ChatClient 构建的所有 ChatClient 实例执行的所有聊天请求中共享。建设者。它们对于在不同聊天请求中常用的工具很有用,但如果不小心使用,它们也可能是危险的,有可能在不应该的时候提供它们。

编程方式

还是刚才的方法,我们去掉了 @Tool 注解。

public String getCurrentDateTime2() {
    return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}

通过以编程方式构建 MethodToolCallback,可以将方法转换为工具。

Method method = ReflectionUtils.findMethod(DateTimeToolsService.class, "getCurrentDateTime2");

ToolDefinition toolDefinition = ToolDefinitions.builder(method)
        .description("获取用户时区中的当前时间")
        .name("getCurrentDateTime2")
        .build();

MethodToolCallback methodToolCallback = MethodToolCallback.builder()
        .toolDefinition(toolDefinition)
        .toolMethod(method)
        .toolObject(dateTimeToolsService)  // 必须是DateTimeToolsService类的实例
        .build();

return zhipuChatClient.prompt()
        .toolCallbacks(methodToolCallback)
        .user(question)
        .stream()
        .content();

在具体使用的时候引入即可。

方法工具回调。允许您构建 MethodToolCallback 实例并提供有关该工具的关键信息:

  • toolDefinition:定义工具名称、描述和输入模式的 toolDefinition 实例。您可以使用工具定义构建它。生成器类。必需的。
  • toolMetadata:定义其他设置的 toolMetadata 实例,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。您可以使用 ToolMetadata 构建它。
  • toolMethod:表示工具方法的 Method 实例。必需的。
  • toolObject:包含工具方法的对象实例。如果该方法是静态的,则可以省略此参数。
  • toolCallResultConverter:用于将工具调用的结果转换为 String 对象以发送回 AI 模型的 toolCallResultConverter 实例。如果没有提供,将使用默认转换器(DefaultToolCallResultConverter)。

工具定义。允许您构建 ToolDefinition 实例并定义工具名称、描述和输入模式:

  • name:工具的名称。如果没有提供,将使用方法名称。AI 模型在调用工具时使用此名称来标识工具。因此,不允许在同一类中有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。
  • description:工具的描述,模型可以使用它来了解何时以及如何调用工具。如果没有提供,方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的以及如何使用它至关重要。如果不能提供一个好的描述,可能会导致模型在应该使用的时候不使用工具,或者使用不当。
  • inputSchema:工具输入参数的 JSON 模式。如果没有提供,将根据方法参数自动生成模式。您可以使用 @ToolParam 注释提供有关输入参数的其他信息,例如描述或参数是必需的还是可选的。默认情况下,所有输入参数都被认为是必需的。

ChatClient 添加工具

使用编程规范方法时,可以将 MethodToolCallback 实例传递给 ChatClienttoolcallback() 方法。该工具仅适用于其添加到的特定聊天请求。

zhipuChatClient.prompt()
        .toolCallbacks(methodToolCallback)
        .user(question)
        .stream()
        .content();

ChatClient 添加默认工具

使用程序化规范方法时,您可以向 ChatClient 添加默认工具。通过将 MethodToolCallback 实例传递给 defaultToolcallback() 方法来构建生成器。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。

ChatClient.builder(zhiPuAiChatModel)
       .defaultToolCallbacks(methodToolCallback)
       .build();

函数即工具

SpringAI 提供了从函数中指定工具的内置支持,可以使用 FunctionToolCallback 实现进行编程,也可以在运行时动态解析 @Bean

函数工具回调

我们首先定义了一个函数类,来获取到指定位置的气温。

public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
    public WeatherResponse apply(WeatherRequest request) {
        return new WeatherResponse(30.0, Unit.C);
    }
}

public enum Unit { C, F }
public record WeatherRequest(String location, Unit unit) {}
public record WeatherResponse(double temp, Unit unit) {}

通过以编程方式构建 FunctionToolCallback,可以将函数类型(FunctionSupplierConsumerBiFunction)转换为工具。

FunctionToolCallback<WeatherRequest, WeatherResponse> toolCallback
        = FunctionToolCallback.builder("weatherService", weatherService)
        .description("获取天气温度").inputType(WeatherRequest.class).build();

return zhiPuChatClient.prompt()
        .toolCallbacks(toolCallback)
        .user(question)
        .stream().content();

我们来看看显示的效果:

image-20251111163850375

函数工具回调。允许您构建 FunctionToolCallback 实例并提供有关该工具的关键信息:

  • name:工具的名称。AI 模型在调用工具时使用此名称来标识工具。因此,不允许在同一上下文中有两个同名工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。必需的。
  • toolFunction:表示工具方法的函数对象(FunctionSupplierConsumerBiFunction)。必需的。
  • description:工具的描述,模型可以使用它来了解何时以及如何调用工具。如果没有提供,方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的以及如何使用它至关重要。如果不能提供一个好的描述,可能会导致模型在应该使用的时候不使用工具,或者使用不当。
  • inputType:函数输入的类型。必需的。
  • inputSchema:工具输入参数的 JSON 模式。如果没有提供,将根据 inputType 自动生成模式。您可以使用 @ToolParam 注释提供有关输入参数的其他信息,例如描述或参数是必需的还是可选的。默认情况下,所有输入参数都被认为是必需的。
  • toolMetadata:定义其他设置的 toolMetadata 实例,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。您可以使用 ToolMetadata 构建它。生成器类。
  • toolCallResultConverter:用于将工具调用的结果转换为 String 对象以发送回 AI 模型的 toolCallResultConverter 实例。如果没有提供,将使用默认转换器(DefaultToolCallResultConverter)。

函数输入和输出可以是 voidPOJO。输入和输出 POJO 必须是可序列化的,因为结果将被序列化并发送回模型。函数以及输入和输出类型必须是公共的。

ChatClient 添加工具

使用编程规范方法时,可以将 FunctionToolCallback 实例传递给 ChatClienttoolcallback() 方法。该工具仅适用于其添加到的特定聊天请求。

zhiPuChatClient.prompt()
        .toolCallbacks(toolCallback)
        .user(question)
        .stream().content();

ChatClient 添加默认工具

使用程序化规范方法时,您可以向 ChatClient 添加默认工具。通过将 FunctionToolCallback 实例传递给 defaultToolcallback() 方法来构建生成器。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。

ChatClient.builder(zhiPuAiChatModel)
        .defaultToolCallbacks(toolCallback)
        .build();

@Bean 动态规范

您可以将工具定义为 Spring bean,并让 SpringAI 在运行时使用 Toolcallback Resolver 接口(通过 SpringBeanToolcallback Resolver 实现)动态解析它们,而不是以编程方式指定工具。此选项使您可以将任何 FunctionSupplierConsumerBiFunction bean 用作工具。

bean 名称将用作工具名称,Spring Framework@Description 注释可用于提供工具的描述,模型可以使用它来了解何时以及如何调用工具。如果不提供描述,则方法名称将用作工具描述。然而,强烈建议提供详细的描述,因为这对于模型理解工具的目的以及如何使用它至关重要。如果不能提供一个好的描述,可能会导致模型在应该使用的时候不使用工具,或者使用不当。

@Configuration
public class ToolsConfig {
    @Resource
    private WeatherService weatherService;

    @Bean("currentWeather")
    @Description("获取指定位置的天气")
    public Function<WeatherRequest, WeatherResponse> currentWeather() {
        return weatherService;
    }
}

ChatClient 添加工具

使用动态规范方法时,您可以将工具名称(即函数 bean 名称)传递给 ChatClienttoolNames() 方法。该工具仅适用于其添加到的特定聊天请求。

zhiPuChatClient.prompt()
        .toolNames("currentWeather")
        .user(question)
        .stream().content();

ChatClient 添加默认工具

使用动态规范方法时,您可以向 ChatClient 添加默认工具。生成器通过将工具名称传递给 defaultToolName() 方法。如果同时提供默认工具和运行时工具,则运行时工具将完全覆盖默认工具。

ChatClient.builder(zhiPuAiChatModel)
        .defaultToolNames("currentWeather")
        .build();

案例:接入高德天气

下来我们来书写一个工具,由这个工具调用高德天气来获取具体城市的实际天气信息。

注册高德

注册高德开放平台并成为个人开发者。官网地址

image-20251113153716249

申请密钥

进入我的工作台并新建应用。

image-20251113154203765

在新建好的应用中添加 Key

image-20251113154400401

好了之后就能看到我们的 Key 了。

image-20251113154515752

注意:Key 是收费资源,请保护好你自己的 Key。可以将 Key 保存到 Windows 的环境变量中。

配置文件

AMAP-KEY: ${A_MAP_KEY} #这里是你自己的key

工具类

@Service
public class WeatherAMapService {
    @Resource
    private RestTemplate restTemplate;
    @Value("${AMAP-KEY}")
    private String key;

    @Tool(description = "根据城市名称获取到该城市的天气")
    public String weather(@ToolParam(description = "具体城市的名称") String city) {
        String url = "https://restapi.amap.com/v3/weather/weatherInfo?key=" + key + "&city=" + city;
        return restTemplate.getForObject(url, String.class);
    }
}

注意:RestTemplate 对象没有自动装配,需要自行实例化。

注册工具

将刚才的工具注册到 client

@Bean("zhiPuChatClient4")
public ChatClient chatClient4(WeatherAMapService weatherAMapService) {
    return ChatClient.builder(zhiPuAiChatModel)
            .defaultTools(weatherAMapService)
            .build();
}

省略了控制类的书写。

演示效果

image-20251113162835770

工具规范

SpringAI 中,工具是通过 ToolCallback 接口建模的。在前面的部分中,我们已经了解了如何使用 SpringAI 提供的内置支持从方法和函数中定义工具。下来我们将深入探讨工具规范,以及如何对其进行定制和扩展以支持更多用例。

工具回调

ToolCallback 接口提供了一种定义 AI 模型可以调用的工具方法,包括定义和执行逻辑。当你想从头开始定义一个工具时,它是要实现的主界面。例如,您可以从 MCP 客户端(使用模型上下文协议)或 ChatClient(构建模块化代理应用程序)定义 ToolCallback

该接口提供以下方法:

public interface ToolCallback {
    /*
    * AI模型用于确定何时以及如何调用工具的定义。
    */
    ToolDefinition getToolDefinition();
        /*
        * 元数据提供有关如何处理该工具的其他信息。
        */
    default ToolMetadata getToolMetadata() {
        return ToolMetadata.builder().build();
    }
        /*
        * 使用给定的输入执行工具,并返回结果以发送回AI模型。
        */
    String call(String toolInput);

    /*
    * 使用给定的输入和上下文执行工具,并返回结果以发送回AI模型。
    */
    default String call(String toolInput, @Nullable ToolContext tooContext) {
        if (tooContext != null && !tooContext.getContext().isEmpty()) {
            throw new UnsupportedOperationException("Tool context is not supported!");
        } else {
            return this.call(toolInput);
        }
    }
}

SpringAI 为工具方法(MethodToolCallback)和工具函数(FunctionToolCallback)提供了内置实现。

工具定义

ToolDefinition 接口为 AI 模型提供了所需的信息,以了解工具的可用性,包括工具名称、描述和输入模式。每个 ToolCallback 实现都必须提供一个 ToolDefinition 实例来定义工具。

public interface ToolDefinition {
    /*
    * 工具名称。在提供给模型的工具集中是唯一的。
    */
    String name();

        /*
        * AI模型用来确定工具功能的工具描述。
        */
    String description();

        /*
        * 用于调用工具的参数的模式。
        */
    String inputSchema();

    static DefaultToolDefinition.Builder builder() {
        return DefaultToolDefinition.builder();
    }
}

允许您使用 ToolDefinition.Builder 默认实现(DefaultToolDefinition)构建 ToolDefinition 实例。

ToolDefinition toolDefinition = ToolDefinition.builder()
    .name("currentWeather")
    .description("获取指定位置的天气")
    .inputSchema("""
        {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string"
                },
                "unit": {
                    "type": "string",
                    "enum": ["C", "F"]
                }
            },
            "required": ["location", "unit"]
        }
    """)
    .build();
  1. 方法工具定义

    从方法构建工具时,会自动为您生成工具定义。如果您更喜欢自己生成工具定义,可以使用这个方便的构建器。

    Method method = ReflectionUtils.findMethod(DateTimeToolsService.class, "getCurrentDateTime");
    ToolDefinition toolDefinition = ToolDefinitions.from(method);

    从方法生成的 ToolDefinition 包括作为工具名称的方法名称、作为工具描述的方法名称以及方法输入参数的 JSON 模式。如果该方法使用 @Tool 进行注释,则工具名称和描述将从注释中获取(如果已设置)。

    如果您更愿意显式提供部分或全部属性,可以使用 ToolDefinition。生成器生成自定义 ToolDefinition 实例。

    Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
    ToolDefinition toolDefinition = ToolDefinitions.builder(method)
        .name("currentDateTime")
        .description("获取指定位置的天气")
        .inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
        .build();
  2. 功能工具定义

    从函数构建工具时,会自动为您生成工具定义。当您使用 FunctionToolCallback 时。构建器要构建 FunctionToolCallback 实例,您可以提供将用于生成 ToolDefinition 的工具名称、描述和输入模式。

JSON 模式

当为 AI 模型提供工具时,模型需要知道调用工具的输入类型的模式。模式用于理解如何调用工具和准备工具请求。SpringAI 提供内置支持,通过 JsonSchemaGenerator 类为工具生成输入类型的 JSON 模式。该模式作为 ToolDefinition 的一部分提供。

JsonSchemaGenerator 类在幕后用于使用方法即工具函数即工具中描述的任何策略,为方法或函数的输入参数生成 JSON 模式。JSON 模式生成逻辑支持一系列注释,您可以在方法和函数的输入参数上使用这些注释来定制结果模式。

下来我们介绍在为工具的输入参数生成 JSON 模式时可以自定义的两个主要选项:描述和所需状态。

  1. 描述

    除了为工具本身提供描述外,您还可以为工具的输入参数提供描述。描述可用于提供有关输入参数的关键信息,例如参数应采用什么格式、允许使用什么值等。这有助于模型理解输入模式以及如何使用它。SpringAI 提供内置支持,使用以下注释之一生成输入参数的描述:

    • @ToolParam(description = "…") from Spring AI
    • @JsonClassDescription(description = "…") from Jackson
    • @JsonPropertyDescription(description = "…") from Jackson
    • @Schema(description = "…") from Swagger.

    这种方法适用于方法和函数,您可以递归地将其用于嵌套类型。

    @Tool(description = "Set a user alarm for the given time")
    public void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }
  2. 所需状态

    默认情况下,每个输入参数都被认为是必需的,这迫使AI模型在调用工具时为其提供一个值。但是,您可以按照以下优先级顺序使用以下注释之一将输入参数设置为可选:

    • @ToolParam(required = false) from Spring AI
    • @JsonProperty(required = false) from Jackson
    • @Schema(required = false) from Swagger
    • @Nullable from Spring Framework.

    这种方法适用于方法和函数,您可以递归地将其用于嵌套类型。

    @Tool(description = "Update customer information")
    public void updateCustomerInfo(Long id, String name, @ToolParam(required = false) String email) {
        System.out.println("Updated info for customer with id: " + id);
    }

    为输入参数定义正确的所需状态对于降低幻觉风险并确保模型在调用工具时提供正确的输入至关重要。在前面的示例中,email 参数是可选的,这意味着模型可以在不提供值的情况下调用该工具。如果该参数是必需的,则模型在调用该工具时必须为其提供值。如果没有价值,模型可能会编造一个,导致幻觉。

结果转换

工具调用的结果使用 ToolCallResultConverter 进行序列化,然后发送回 AI 模型。ToolCallResultConverter 接口提供了一种将工具调用的结果转换为 String 对象的方法。

该接口提供以下方法:

@FunctionalInterface
public interface ToolCallResultConverter {
    /*
    * 给定工具返回的Object,将其转换为与给定类类型兼容的String。
    */
    String convert(@Nullable Object result, @Nullable Type returnType);
}

结果必须是可序列化的类型。默认情况下,使用 JacksonDefaultToolCallResultConverter)将结果序列化为 JSON,但您可以通过提供自己的 ToolCallResultConverter 实现来自定义序列化过程。

SpringAI 在方法和函数工具中都依赖于 ToolCallResultConverter

工具上下文

SpringAI 支持通过 ToolContext API 向工具传递额外的上下文信息。此功能允许您提供额外的用户提供的数据,这些数据可以在工具执行过程中使用,以及 AI 模型传递的工具参数。

@Tool(description = "Retrieve customer information")
public Customer getCustomerInfo(Long id, ToolContext toolContext) {
    return customerRepository.findById(id, toolContext.getContext().get("id"));
}

ToolContext 填充了用户在调用 ChatClient 时提供的数据。

ChatClient.create(chatModel)
        .prompt("告诉我更多关于ID为42的客户的信息")
        .tools(new CustomerTools())
        .toolContext(Map.of("id", "42"))
        .call()
        .content();

响应结果

默认情况下,工具调用的结果会作为响应发送回模型。然后,模型可以使用结果继续对话。

在某些情况下,您更愿意将结果直接返回给调用者,而不是将其发送回模型。例如,如果您构建了一个依赖于 RAG 工具的代理,您可能希望将结果直接返回给调用者,而不是将其发送回模型进行不必要的后处理。或者,你可能有一些工具可以结束代理的推理循环。

每个 ToolCallback 实现都可以定义工具调用的结果是应该直接返回给调用者还是发送回模型。默认情况下,结果会被发送回模型。但您可以根据每个工具更改此行为。

负责管理工具执行生命周期的 ToolCallingManager 负责处理与工具关联的 returnDirect 属性。如果该属性设置为 true,则工具调用的结果将直接返回给调用者。否则,结果将被发送回模型。

  1. 方法直接返回

    当使用声明性方法从方法构建工具时,您可以通过将 @tool 注释的 returnDirect 属性设置为 true 来标记工具,以将结果直接返回给调用者。

    @Tool(description = "检索客户信息", returnDirect = true)
    public Customer getCustomerInfo(Long id) {
        return customerRepository.findById(id);
    }

    如果使用编程方法,您可以通过 ToolMetadata 接口设置 returnDirect 属性,并将其传递给 MethodToolCallback

    ToolMetadata toolMetadata = ToolMetadata.builder()
        .returnDirect(true)
        .build();
  2. 函数直接返回

    当使用编程方法从函数构建工具时,您可以通过 ToolMetadata 接口设置returnDirect属性,并将其传递给 FunctionToolCallback

    ToolMetadata toolMetadata = ToolMetadata.builder()
        .returnDirect(true)
        .build();

AI 应用中,工具扮演了一个非常重要的作用,尤其是在智能体的开发中。大模型完成了训练之后,其中所包含的参数化知识被冻结。通过使用检索增强生成(RAG)这样的技术,我们可以在与大模型的交互中,提供附加的上下文信息。除了 RAG 之外,工具不仅可以获取到外部的信息,还可以对外界产生影响。

对于人类来说,使用工具是一个巨大的进步。与之相似,使用工具也是 AI 应用自身进步的一个重要标志。这意味着 AI 应用的能力不仅是生成内容,还可以影响真实世界,并帮助用户解决实际的问题。

MCP 基本概述

2024年11月,MCP 发布了,而且在 AI 社区里反响特别好,这可是2024年的一个大惊喜,大家都很期待它能带来新变化。

从那时起,spring-ai-mcp 这个实验性项目就启动了,之后一直在不断发展。SpringAI 团队还和 Anthropic 公司的 David Soria Parra 等人一起合作,把这个实验性项目变成了正式的 MCP Java SDK

MCP Java SDK 提供了模型上下文协议的 Java 实现,通过同步和异步通信模式实现了与 AI 模型和工具的标准化交互。

Java MCP 实现遵循三层架构:

image-20251015115000231
  • 客户端/服务器层(Client/Server Layer):MCPClient 负责处理客户端操作,而 MCPServer 管理服务端协议操作。两者均通过 MCPSession 进行通信管理。
  • 会话层(MCPSession):通过 DefaultMcpSession 的实现,管理通信模式与状态。
  • 传输层(MCPTransport):负责 JSON-RPC 消息的序列化与反序列化,并支持多种传输协议实现(如 HTTPSSE)。

SpringAI MCP 通过 Spring Boot 集成扩展了 MCP Java SDK,提供了客户端和服务器端启动器。使用 Spring Initializer 启动支持 MCPAI 应用程序。

MCP 的核心是客户端 – 服务器架构,其中 MCP‏ 客户端主机可以连接到多个服务器。

image-20251113113111300

其实 MCP 就是把 spring 应用里的通用 tool 功能,抽取出来,单独部署,这样当新服务需要通用的 tool 时,就只用集成 mcp-client 调用 mcp-server 功能就行了,不用再重复开发。

MCP 客户端

MCP 客户端是 MCP 架构中的核心组件,负责与 MCP 服务器建立并管理连接。该客户端实现协议的客户端部分,具体处理以下功能:

  1. 协议版本协商 :确保与服务器的兼容性。

  2. 能力协商 :确定可用功能(如工具支持范围)。

  3. 消息传输与 JSON-RPC 通信 :实现结构化数据交互。

  4. 工具发现与执行 :动态识别并调用外部工具。

  5. 资源访问与管理 :协调模型与外部数据源的交互。

  6. 提示系统交互 :支持与模型提示(Prompt)系统的集成。

  7. 可选功能 :

    1. 根管理(Roots Management)。
    2. 采样支持,如模型输出概率控制。
    3. 同步与异步操作模式。
  8. 传输选项 :

    1. 基于标准输入/输出的传输(适用于进程间通信)。
    2. 基于 Java HttpClientSSE 客户端传输(支持事件流)。
    3. 基于 WebFluxSSE 客户端传输(用于响应式 HTTP 流处理)。
Java MCP Client Architecture

MCP 服务器

MCP 服务器是 MCP 架构中的基础组件,负责为客户端提供工具、资源及能力支持。该服务器实现协议的服务端部分,核心职责包括:

  1. 服务端协议操作实现 :处理协议交互逻辑。
  2. 工具暴露与发现 :通过标准化接口提供可调用的外部工具(如函数/ API)。
  3. 基于 URI 的资源管理 :支持文件、数据库等本地资源的安全访问。
  4. 提示模板提供与处理 :管理预定义的 Prompt 模板并动态注入上下文。
  5. 能力协商 :与客户端协商支持的功能范围(如同步/异步模式)。
  6. 结构化日志与通知 :记录操作日志并推送状态变更事件。
  7. 多客户端并发管理 :支持高并发连接与会话隔离。
  8. 同步与异步 API 支持 :适配不同场景的调用需求。
  9. 传输实现 :
    1. 基于标准输入/输出的传输(适用于进程间通信)。
    2. 基于 ServletSSE 服务端传输(支持事件流 HTTP 响应)。
    3. 基于 WebFlux 的响应式 SSE 传输(用于异步 HTTP 流式处理)。
    4. 基于 WebMVCServlet SSE 传输(兼容传统 HTTP 流式交互)。
Java MCP Server Architecture

MCP 的核心能力

MCP 其实就是一个标准协议,这个协议的作用就是让大模型可以无缝接入外部的工具,它解决三个大问题:

  1. 工具对外提供服务统一化:解决不断增长的各式各样的工具,大模型可以直接接入,无需适配各种工具。
  2. 不同大模型适配:无论哪种大模型,都遵循 MCP 协议,那么你就可以随意切换大模型而不影响你的应用调用工具。
  3. 安全规范:提供本地数据访问服务,也通过外部访问服务。这样既能做到数据保护,也能做到数据共享。

举个例子,以前‏我们想给 AI 增加查询地图的能力,需要自己开发工具来调用第三方地图 API。如果‏你有多个项目、或者其他开发者也需要做同样的能力,大家就要重复开发,就导致同样的功能‏做了多遍、每个人开发的质量和效果也会有差别。

而如果官方把查询地图的能力直接做成一个‏服务,谁要用谁接入,不就省去了开发成本、并且效果一致了么?如果大家都陆续开放自己的‌服务,不就相当于打造了一个服务市场,造福广大开发者了么!

MCP 入门案例

下来我们基于 SpringAI1.0 搭建 MCPSTDIO 模式下的服务端和客户端实现。

在这里要特别声明,Ollama 下的 Deepseek 模型并不支持 stdio 工具的访问。我们这里使用的是智谱大模型。

服务端的搭建

  1. 引入依赖

         <dependencies>
             <!-- MCP Server依赖 -->
             <dependency>
                     <groupId>org.springframework.ai</groupId>
                     <artifactId>spring-ai-starter-mcp-server</artifactId>
             </dependency>
             <dependency>
                     <groupId>org.springframework.boot</groupId>
                     <artifactId>spring-boot-starter-web</artifactId>
             </dependency>
     </dependencies>
    
     <build>
             <plugins>
                     <plugin>
                             <groupId>org.springframework.boot</groupId>
                             <artifactId>spring-boot-maven-plugin</artifactId>
                             <configuration>
                                     <mainClass>
                                         com.dailyblue.java.study.ai.mcp.stdio.server.AIMcpStdioServerApplication
                                     </mainClass>
                             </configuration>
                             <executions>
                                     <execution>
                                             <goals>
                                                     <goal>repackage</goal>
                                             </goals>
                                     </execution>
                             </executions>
                     </plugin>
             </plugins>
     </build>
  2. 配置文件

    server:
      port: 8002
    spring:
      ai:
        mcp:
          server:
            stdio: true # 开启stdio
            name: ai-mcp-stdio-server # 服务器名称
            version: 1.0.0 # 服务器版本
            type: sync  # 同步模式
      main:
        banner-mode: off
        web-application-type: none
      application:
        name: ai-mcp-stdio-server
        version: 1.0.0

    banner-mode:off 是关闭 Spring Boot 启动时默认显示的 ASCII 图形欢迎界面,使控制台输出更加简洁。

    web-application-type: none 表示应用不会启动嵌入式 Web 服务器,适用于后台任务、批处理作业等非 Web 场景。

  3. 书写工具类

    @Service
    public class OrderService {
    
        @Tool(name = "queryOrderStatus", description = "根据订单号查询订单状态")
        public String queryOrderStatus(
                @ToolParam(description = "订单号,格式为8位数字,比如:25070601") String orderNo) {
            return switch (orderNo) {
                case "25070601" -> "订单号:" + orderNo + ",订单状态:已发货";
                case "25070602" -> "订单号:" + orderNo + ",订单状态:已完成";
                case "25070603" -> "订单号:" + orderNo + ",订单状态:已取消";
                default -> "订单号:" + orderNo + ",订单状态:未知";
            };
        }
    }

    其实这里我们会发现,就是一个方法,根据传递的参数返回结果。

  4. 注册工具

    @Bean
    public ToolCallbackProvider registerTools(OrderService orderService) {
        return MethodToolCallbackProvider.builder().toolObjects(orderService).build();
    }

    有几个 service 就注册几个即可。

  5. 打成 jar

    image-20251017100856429

客户端的搭建

  1. 引入依赖

    <dependencies>
        <!-- MCP Client依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>
        <dependency>
             <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-zhipuai</artifactId>
        </dependency>
      ... ...
    </dependencies>

    客户端需要使用到大模型,引入智谱大模型依赖。其他依赖省略。

  2. 配置文件

    server:
      port: 8003
    spring:
      application:
        name: ai-mcp-stdio-client
        version: 1.0.0
      ai:
        zhipuai:
          api-key: ***
          chat:
            options:
              model: glm-4.6
        mcp:
          client:
            enabled: true # 启用MCP客户端
            name: ai-mcp-stdio-client # MCP客户端名称
            version: 1.0.0 # 客户端版本
            initialized: true # 自动初始化客户端
            request-timeout: 20s # 请求超时时间
            type: sync # 客户端类型为同步模式
            root-change-notification: true # 启用客户端变更通知
            toolcallback:
              enabled: true # 启用工具回调 与Spring AI工具执行框架集成
            stdio:
              servers-configuration: classpath:/stdio-server-config.json
  3. json 服务器文件

    我们这里还需要新建 stdio-server-config.json 文件,并且将服务器的 jar 复制到 resources 下。

    {
      "mcpServers": {
        "mcp-server": {
          "command": "java",
          "args": [
            "-Dspring.ai.mcp.server.stdio=true",
            "-Dspring.main.web-application-type=none",
            "-Dlogging.pattern.console=",
            "-Dfile.encoding=UTF-8",
            "-jar",
            "/Users/dailyblue/workspace/java/IdeaProjects/dailyblue_example_study/ai/ai-mcp-example-stdio/ai-mcp-stdio-client/src/main/resources/ai-mcp-stdio-server-1.0-SNAPSHOT.jar"
          ],
          "env": {}
        }
      }
    }
    image-20251017101446258
  4. 配置类

    @Configuration
    public class SpringAIConfig {
        @Resource
        private ToolCallbackProvider toolCallbackProvider;
    
        @Bean("zhipuChatClient")
        public ChatClient chatClient(ZhiPuAiChatModel zhipuAiChatModel) {
            FunctionCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();
            return ChatClient.builder(zhipuAiChatModel)
                    .defaultToolCallbacks(toolCallbacks)
                    .build();
        }
    }
  5. 控制类

    @GetMapping(value = "/a", produces = "text/html;charset=utf-8")
    public String a(@RequestParam("question") String question) {
        return zhipuChatClient
                .prompt()
                .user(question)
                .call()
                .content();
    }

    这里没有什么特别的地方,跟过去写法一致。

  6. 演示效果

    当我们查询工具中有的数据时:

    image-20251017102338271

    当我们查询其他内容时:

    image-20251017102419622

    我们会发现,会直接从大模型中获取数据。

SpringAI MCP

SpringAI MCP 为模型上下文协议提供 JavaSpring 框架集成。它使 SpringAI 应用程序能够通过标准化的接口与不同的数据源和工具进行交互,支持同步和异步通信模式。

SpringAI MCP 采用模块化架构,包括以下组件:

  • SpringAI 应用程序:使用 SpringAI 框架构建想要通过 MCP 访问数据的生成式 AI 应用程序。
  • SpringMCP 客户端:MCP 协议的 SpringAI 实现,与服务器保持 1:1 连接。
  • SpringMCP 服务器:轻量级程序,每个程序都通过标准化的模型上下文协议公开特定的功能。
  • 本地数据源:MCP 服务器可以安全访问的计算机文件、数据库和服务。
  • 远程服务:MCP 服务器可以通过互联网连接到的外部系统。

依赖管理

SpringAI‏MCP 官方 Java SDK 的基础上额外封‏装了一层,提供了和 SpringBoot 整‏合的 SDK,支持客户端和服务端的普通调用和响应‏式调用。

客户端启动器

  1. spring-ai-starter-mcp-client:核心启动器,提供基于标准输入/输出(STDIO)和 HTTPSSE 传输支持。
  2. spring-ai-starter-mcp-client-webflux:基于 WebFlux 的响应式 SSE 传输实现。

SpringAI MCP 客户端启动器为 SpringBoot 应用程序中的 MCP 客户端功能提供自动配置支持,包含同步和异步客户端实现,并支持多种传输选项。

标准 starter 通过 STDIOSSE 同时连接到一个或多个 MCP 服务器。其中 SSE 连接采用基于 HttpClient 的传输实现。每建立一个到 MCP 服务器的连接,即会创建一个新的 MCP 客户端实例。开发者可选择使用同步(SYNC)或异步(ASYNC)模式的 MCP 客户端(注意:同一应用中不可混合使用同步与异步客户端)。对于生产环境部署,建议采用基于 WebFluxSSE 连接,并搭配 spring-ai-starter-mcp-client-webflux 启动器以实现响应式流式处理。

服务端启动器

  1. spring-ai-starter-mcp-server:核心服务端启动器,支持 STDIO 传输。
  2. spring-ai-starter-mcp-server-webmvc:基于 Spring MVCSSE 传输实现(兼容传统 Servlet 环境)。
  3. spring-ai-starter-mcp-server-webflux:基于 WebFlux 的响应式 SSE 传输实现。

标准的 starter 支持基于 STDIO 连接的标准服务器,适用于命令行工具或者桌面应用,实现进程内通信,另需额外的 web 传输。WebMVC MCP 服务器可以进行 SSE 传输以及 STDIO 传输,在该依赖中已经集成了 web 依赖组件,不需额外导入。WebFlux MCP 服务器实现了 SSE 的响应式输出效果。

配置属性

下来我们来讲解 MCP 在配置文件中的相关属性。同样的,它也分为客户端和服务端相关属性。

更多的配置属性可以访问:官方文档

客户端属性

  1. 通用属性

    通用属性以 spring.ai.mcp.client 为前缀:

    属性 描述 默认值
    enabled 启用|禁用 MCP 客户端 true
    name MCP 客户端实例名称 spring-ai-mcp-client
    version MCP 客户端实例版本 1.0.0
    initialized 是否在创建时初始化客户端 true
    request-timeout MCP 客户端请求的超时时间 20s
    type 客户端类型(SYNCASYNC)。
    所有客户端必须统一为同步或异步,不可混合。
    SYNC
    root-change-notification 为所有客户端启用|禁用根变更通知 true
  2. STDIO 属性

    Stdio 传输属性以 spring.ai.mcp.client.stdio 为前缀:

    属性 描述 默认值
    servers-configuration 包含 MCP 服务器配置的 JSON 格式资源文件路径
    connections 命名 Stdio 连接的配置映射
    connections.[name].command 启动 MCP 服务器的命令
    connections.[name].args 命令参数列表
    connections.[name].env 服务器进程的环境变量映射
  3. SSE 属性

    SSE 传输属性以 spring.ai.mcp.client.sse 为前缀:

    属性 描述
    connections 命名 SSE 连接的配置映射
    connections.[name].url MCP 服务器通信的 SSE 端点 URL

服务端属性

所有属性均以 spring.ai.mcp.server 为前缀:

属性 描述 默认值
enabled 启用|禁用 MCP 服务器 true
stdio 启用|禁用 STDIO 传输 false
name 服务器标识名称 mcp-server
version 服务器版本 1.0.0
type 服务器类型(SYNC|ASYNC SYNC
resource-change-notification 启用资源变更通知 true
tool-change-notification 启用工具变更通知 true
prompt-change-notification 启用提示模板变更通知 true
sse-message-endpoint Web 传输的 SSE 端点路径 /mcp/message

功能与能力

MCP 服务器启动器允许服务器向客户端暴露工具(Tools)、资源(Resources)以及提示词(Prompts),并根据服务器类型自动转换同步|异步注册。

  1. 工具

    服务器提供可执行的函数或 API,例如计算两点距离、发送邮件等操作。客户端通过调用工具完成具体任务,如发送请求、写入文件等动态操作。工具具有动态性,允许改变状态并执行指令。

  2. 资源

    服务器暴露数据接口(如数据库、文件系统),客户端可实时订阅数据更新。例如日志文件或用户配置信息,大模型通过读取这些资源生成推理或生成内容。

  3. 提示模版

    作为模型与外部系统交互的桥梁,提示词定义工具调用逻辑。开发者通过提示词指定“应当调用什么工具、传递哪些参数”,模型仅负责生成指令文本,无需理解具体实现细节。

三者协同工作:开发者通过提示词设计交互逻辑,服务器提供工具和资源支持,模型专注于生成指令。这种架构简化了 AI 应用开发流程,支持快速接入各类工具并动态整合数据。

MCP 客户端开发

客户端开发主要基于 SpringAI MCP Client Boot Starter,能够自动完成客户端的初始化、管理多个客户端实例、自动清理资源等。

下来我们来总结下客户端开发的一般步骤:

  1. 引入依赖

    Sprin‏gAI 提供了两种客户端 SDK,‏分别支持非响应式响‏应式编程,可以根据需‌要选择对应的依赖包。

  2. 书写配置

    引入依赖后‏,需要配置与服务器‏的连接,Sprin‏gAI 支持两种‏配置方式:

    • yml 方式

      直接写‏入配置文件,这种方‏式同时支持 std‏ioSSE 连‏接方式。

    • json 方式

      引用 Claude Desktop 格式的 JSON 文件,目前仅支持 stdio 连接方式。

  3. 使用服务

    启动项目时‏,SpringA‏I 会自动注入一些‏ MCP 相关的 B‏ean。这里也有两种方式进行操作:

    • 完全控制 MCP 客户端的行为

      如果你‏想完全自主控制 M‏CP 客户端的行为‏,可以使用 Mcp‏Client Be‌an,支持同步和异步:

      // 同步客户端
      @Autowired
      private List<McpSyncClient> mcpSyncClients;
      
      // 异步客户端
      @Autowired
      private List<McpAsyncClient> mcpAsyncClients;

      查看 Mc‏pSyncClien‏t 的源码,发现提供‏了很多和 MCP 服‏务端交互的方法,比如‌获取工具信息、调用工具等等:

      img
      @Test
      public void syncClient() {
          //创建MCP Client 这里我们只有1个client,所以直接取第一个
          McpSyncClient mcpSyncClient = mcpSyncClients.get(0);
          //这一步初始化执行完后,其实就是把jar中的Spring boot应用启动了(MCP Server就能对外提供服务了)
          mcpSyncClient.initialize();
          // 获取到该client下的所有tool
          McpSchema.ListToolsResult toolsList = mcpSyncClient.listTools();
          //调用MCP Server
          McpSchema.CallToolResult orderStatus = mcpSyncClient.callTool(
                  //这里我们只有1个工具,所以直接取第一个
                  new McpSchema.CallToolRequest(toolsList.tools().get(0).name(),
                          Map.of("orderNo", "25070601")));
          // 打印结果
          log.info("orderStatus:{}", orderStatus);
          //关闭MCP Client,这里一定要记得关闭,否则MCP Server的Spring boot应用不会退出
          mcpSyncClient.closeGracefully();
      }

      需要注意的‏是,每个 MCP ‏服务连接都会创建一‏个独立的客户端实例‏。

    • 利用服务器提供的工具增强 AI 能力

      如果你想利用 MCP 服务提供的工具来增强 AI 的能力,可以使用自动注入的 ToolCallbackProvider Bean,从中获取到 ToolCallback 工具对象。

      // 和 Spring AI 的工具进行整合
      @Autowired
      private SyncMcpToolCallbackProvider toolCallbackProvider;
      ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();

      然后绑定给 ChatClient 对象即可:

      ChatResponse response = chatClient
              .prompt()
              .user(message)
              .tools(toolCallbackProvider)
              .call()
              .chatResponse();

MCP 服务端开发

服务端开发主要基于 SpringAI MCP Server Boot Starter,能够自动配置 MCP 服务端组件,使开发者能够轻松创建 MCP 服务,向 AI 客户端提供工具、资源和提示词模板,从而扩展 AI 模型的能力范围。

下来我们来总结下服务端开发的一般步骤:

  1. 引入依赖

    Spring‏AI 提供了三种‏ MCP 服务端 SD‏K,分别支持非响应式和‏响应式编程,可以根据需‌要选择对应的依赖包。

  2. 书写配置

    服务端就提供了一种 yml 形式的配置文件,可以通过配置文件配置服务端的相关信息。

  3. 开发服务

    无论采用哪种传输方式,开发 MCP 服务的过程都是类似的,跟开发工具调用一样,直接使用 @Tool 注解标记服务类中的方法。

    @Service
    public class WeatherService {
        @Tool(description = "获取指定城市的天气信息")
        public String getWeather(
                @ToolParameter(description = "城市名称,如北京、西安") String cityName) {
            // 实现天气查询逻辑
            return "城市" + cityName + "的天气是晴天,温度22°C";
        }
    }
  4. 注册服务

    开发好服务后,在 SpringBoot 项目启动时注册一个 ToolCallbackProvider Bean 即可。

    @Configuration
    public class MCPServerConfig {
        @Bean
        public ToolCallbackProvider weatherTools(WeatherService weatherService) {
            return MethodToolCallbackProvider.builder()
                    .toolObjects(weatherService)
                    .build();
        }
    }

大家只需要了解上面‏这些特性即可,无需专门的记忆。

通‏过这些特性,大家应该也会对 MCP ‏有进一步的了解。简单来说,通过这套标‏准,服务端能向客户端传递各种各样不同‌类型的信息(资源、工具、提示词等)。

SSE 方式

下来我们使用 SSE 方式构建一个 MCP 的客户端服务器系统。

什么是 SSE

Server-Sent Events 是基于 HTTP 协议的实时单向服务器推送技术,具有轻量级、自动重连和事件分类型特点,适用于实时数据推送场景。允许服务器主动向客户端(如浏览器)推送实时数据。与传统的轮询或长轮询不同,SSE 通过单一的持久连接实现数据的实时传输,客户端无需频繁发起请求。

  1. SSE 的特点

    1. 单向通信

      SSE 是单向的,服务器可以主动推送数据到客户端,但客户端无法直接通过 SSE 向服务器发送数据。

    2. 基于 HTTP 协议

      SSE 使用标准的 HTTP 协议,无需额外的协议或端口配置,兼容性好,易于实现。

    3. 轻量级

      相比 WebSocketSSE 的实现更简单,代码量更少,适合简单的实时数据推送场景。

    4. 自动重连

      如果连接断开,浏览器会自动尝试重新连接,开发者无需手动处理重连逻辑。

    5. 支持事件类型

      服务器可以发送不同类型的事件,客户端可以根据事件类型执行不同的操作。

    6. 支持消息 ID

      每条消息可以包含一个唯一的 ID,用于断线重连后恢复消息流。

  2. SSEWebSocket 对比

    维度 SSE WebSocket
    通信模式 单向(服务端→客户端) 全双工
    协议基础 HTTP 长连接 独立 TCP 协议
    数据格式 纯文本(UTF-8 支持二进制/文本
    实现复杂度 客户端仅需 EventSource API 需协议握手和帧处理逻辑
    适用场景 实时监控、新闻推送、股价更新 在线游戏、即时通讯
  3. SSE 的工作原理

    相比传统的请求-响应模式SSE 提供了一种持久连接,允许服务器随时向客户端发送事件和数据,实现了实时性的消息传递。

    SSE 的工作原理非常简单直观。客户端通过与服务器建立一条持久化的 HTTP 连接,然后服务器使用该连接将数据以事件流(event stream)的形式发送给客户端。这些事件流由多个事件(event)组成,每个事件包含一个标识符、类型和数据字段。客户端通过监听事件流来获取最新的数据,并在接收到事件后进行处理。

    image-20251030152734319
  4. SSE 的应用场景

    1. 实时通知

      如社交媒体的实时消息提醒、邮件通知等。

    2. 实时数据更新

      如股票行情、天气预报、体育比分等。

    3. 日志监控

      实时推送服务器日志或应用状态。

    4. 进度更新

      如文件上传进度、任务执行进度等。

搭建服务端

其实这个搭建的步骤和入门案例是基本一致。在上一个小节中,我们也提到了服务端的开发,上边的也有详细的步骤。

  1. 引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
        </dependency>
    </dependencies>

    这里引入的是 mvc 模式的依赖,对于 webflux 有兴趣的同学可以自行了解。

  2. 书写配置

    server:
      port: 8021
    spring:
      application:
        name: ai-mcp-sse-server
      ai:
        mcp:
          server:
            name: ai-mcp-sse-server
            sse-message-endpoint: /mcp/city

    其实这里什么都可以不用配置,使用默认值即可。

  3. 开发服务

    @Service
    public class LivingCityService {
    
        @Tool(description = "中国最宜居的城市")
        public String livingCity() {
            return "中国最宜居的城市是西安,历史厚重,环境优美!";
        }
    }

    几种服务端的 service 写法都是一样的。

  4. 注册服务

    @Configuration
    public class McpServerConfig {
    
        @Bean
        public ToolCallbackProvider toolCallbackProvider(LivingCityService livingCityService) {
            return MethodToolCallbackProvider.builder().toolObjects(livingCityService).build();
        }
    }

    这里的注册写法也是一致的。注册好之后,启动服务器即可。

搭建客户端

下来的话,我们来搭建 SSE 的客户端,分为如下的几个步骤:

  1. 引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-zhipuai</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>
    </dependencies>

    需要注意的是:要单独引入 webmvc 的依赖。如果要使用到 ai 的话,也要引入 ai 的依赖。Deepseek 并不支持 MCP 的访问。这里我们使用的是智谱 AI

  2. 书写配置

    server:
      port: 8022
    spring:
      application:
        name: ai-mcp-sse-client
      ai:
        zhipuai:
          api-key: ***
          chat:
            options:
              model: glm-4.6
        mcp:
          client:
            name: ai-mcp-sse-client
            sse:
              connections:
                server1:
                  url: http://localhost:8021
            toolcallback:
              enabled: true

    url 这里书写服务器的 IP + 端口号,并且要先启动服务器在运行客户端。

  3. 使用服务

    @Bean("zhiPuChatClient")
    public ChatClient chatClient(ZhiPuAiChatModel chatModel) {
        FunctionCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();
        return ChatClient.builder(chatModel)
                .defaultTools(toolCallbacks)
                .build();
    }
  4. 效果展示

    分别启动服务器和客户端,通过调用客户端的 API 接口,测试服务效果。

    image-20251030150432782

    如果我们询问其他问题,得到的效果是:

    image-20251030150630247

    我们可以看到,会从智谱 AI 中获取数据,而并没有触发我们的工具查询。

MySQL MCP 服务

MySQL MCPModel Context Protocol for MySQL) 是一个基于 MCP 协议的服务器组件,它像一座“桥梁”,连接大语言模型与 MySQL 数据库。通过它,LLM 可以直接理解自然语言查询,并自动转换为 SQL 语句执行,返回结果。

安装服务

首先执行以下命令安装对应的 MCP Server 到机器上。

npm install mysql-mcp-server

注意:

  1. 这种方式是局部方式安装。
  2. 需要 cd 到指定文件夹中执行命令。
  3. 如果要使用全局方式,引入 -g 命令即可。

引入依赖

这里正常引入客户端依赖即可。

<dependencies>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-zhipuai</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-client</artifactId>
    </dependency>
</dependencies>

使用的是 STDIO 方式连接服务器。

配置文件

application.yml 中配置连接。

spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            mysql:
              # Windows操作系统使用npx.cmd,Linux和MacOS使用npx
              command: "npx"
              args:
                # mcp server名称
                - "mysql-mcp-server"
              env:
                # 数据库ip
                "MYSQL_HOST": "localhost"
                # 数据库服务端口
                "MYSQL_PORT": "3306"
                # 数据库用户名
                "MYSQL_USER": "root"
                # 数据库密码
                "MYSQL_PASSWORD": "a123456"
                # 数据库名称 这里即使写了数据库名称 也会从全部数据库中获取 Bug
                "MYSQL_DATABASE": "school"
# 省略其他连接配置(大模型配置、服务配置)

注册工具

在配置类中注册工具。

@Configuration
public class ChatClientConfig {
    @Resource
    private ZhiPuAiChatModel zhiPuAiChatModel;
    @Resource
    private SyncMcpToolCallbackProvider syncMcpToolCallbackProvider;

    @Bean("zhiPuChatClient")
    public ChatClient chatClient() {
        return ChatClient.builder(zhiPuAiChatModel)
                .defaultToolCallbacks(syncMcpToolCallbackProvider.getToolCallbacks())
                .build();
    }
}

SyncMcpToolCallbackProvider 和 ToolCallbackProvider 的区别

SyncMcpToolCallbackProviderToolCallbackProviderSpringAI 框架中用于管理工具回调的组件,主要区别在于功能定位和使用场景:

  • 功能定位

    • SyncMcpToolCallbackProvider

      专为 MCP 协议设计,负责创建同步模式的 MCP 工具回调实例(SyncMcpToolCallback),支持动态获取工具定义(ToolDefinition)和参数 Schema,适用于 MCP 客户端与服务器的标准化交互。 ‌

    • ToolCallbackProvider

      通用工具回调管理器,可创建多种工具回调(如 FunctionToolCallbackMethodToolCallback),但需静态定义工具参数 Schema,不依赖 MCP 协议。 ‌

  • 使用场景

    • SyncMcpToolCallbackProvider

      用于集成 MCP 协议的场景,例如通过 Spring Boot 启动器配置 MCP 客户端,动态调用远程工具。 ‌

    • ToolCallbackProvider

      适用于本地工具调用,如直接注册 FunctionToolMethodTool,无需 MCP 协议支持。 ‌

  • 配置差异

    • SyncMcpToolCallbackProvider

      需配置 MCP 客户端(如 McpSyncClient )和工具原型(Tool),依赖 MCP 服务器的工具注册。 ‌

    • ToolCallbackProvider

      直接绑定本地工具实现类,无需 MCP 服务器参与。

测试

可以通过 SpringBootTest 测试,也可以通过 controller 进行测试。如果使用 Test 测试使用同步输出方式。

启动

image-20251124104219752

如果看到这个效果,代表启动后加载完毕。

我们访问浏览器:

image-20251124104300129

运行后我们的日志:

image-20251124105240234

换一个问题试试:

image-20251124104416489

日志如下:

image-20251124105101181

By admin

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注