1. MCP Server
引入依赖- <dependency>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-webflux</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.ai</groupId>
- spring-ai-starter-mcp-server-webflux</artifactId>
- </dependency>
复制代码 定义工具- @Service
- public class DateService {
- private static final String[] WEEKDAY_NAMES = { "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日" };
- /**
- * 获取今天是星期几
- *
- * @return 星期描述
- */
- @Tool(description = "获取今天是星期几")
- public String getTodayWeekday() {
- System.out.println("getTodayWeekday 被调用");
- DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
- int index = dayOfWeek.getValue() - 1;
- if (index < 0 || index >= WEEKDAY_NAMES.length) {
- return "未知星期";
- }
- return String.format("今天是%s", WEEKDAY_NAMES[index]);
- }
- }
- @Service
- public class NumberService {
- /**
- * 判断一个数字是奇数还是偶数
- *
- * @param number 要判断的数字
- * @return 判断结果,返回"奇数"或"偶数"
- */
- @Tool(description = "判断一个数字是奇数还是偶数")
- public String checkOddOrEven(@ToolParam(description = "要判断的数字") int number) {
- System.out.println("checkOddOrEven 被调用");
- if (number % 2 == 0) {
- return String.format("数字 %d 是偶数", number);
- } else {
- return String.format("数字 %d 是奇数", number);
- }
- }
- }
- @Service
- public class MyToolService {
- /**
- * 打印 1-9 的九九乘法表
- *
- * @return 九九乘法表文本
- */
- @Tool(description = "打印标准九九乘法表")
- public String printMultiplicationTable() {
- System.out.println("printMultiplicationTable 被调用");
- StringBuilder table = new StringBuilder();
- for (int i = 1; i <= 9; i++) {
- for (int j = 1; j <= i; j++) {
- table.append(String.format("%d×%d=%-2d", j, i, i * j));
- if (j < i) {
- table.append(" ");
- }
- }
- if (i < 9) {
- table.append('\n');
- }
- }
- return table.toString();
- }
- }
- @Service
- public class BankService {
- private static final String BASE_URL = "https://www.abc.xyz.com/payBankInfo/getByBankNo";
- private final RestClient restClient;
- private final ObjectMapper objectMapper;
- public BankService(RestClient.Builder restClientBuilder, ObjectMapper objectMapper) {
- this.restClient = restClientBuilder
- .baseUrl(BASE_URL)
- .build();
- this.objectMapper = objectMapper;
- }
- @Tool(description = "通过行号查询开户行信息")
- public String queryBankInfo(@ToolParam(description = "开户行行号") String bankNo) {
- System.out.println("queryBankInfo 被调用");
- try {
- // 参数验证
- if (bankNo == null || bankNo.trim().isEmpty()) {
- return "行号不能为空";
- }
- String responseBody = restClient.post()
- .uri(uriBuilder -> uriBuilder.queryParam("bankNo", bankNo.trim()).build())
- .contentType(MediaType.APPLICATION_JSON)
- .accept(MediaType.APPLICATION_JSON)
- .retrieve()
- .body(String.class);
- // 尝试格式化 JSON
- try {
- Object json = objectMapper.readValue(responseBody, Object.class);
- return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
- } catch (Exception e) {
- // 如果不是 JSON,直接返回原始响应
- return responseBody;
- }
- } catch (RestClientResponseException e) {
- return String.format("请求失败: HTTP %d - %s", e.getStatusCode().value(), e.getStatusText());
- } catch (RestClientException e) {
- return String.format("请求异常: %s", e.getMessage());
- } catch (Exception e) {
- return String.format("处理异常: %s", e.getMessage());
- }
- }
- }
复制代码 启动MCP服务- package com.example.springaimcpserver;
- import com.example.springaimcpserver.service.BankService;
- import com.example.springaimcpserver.service.DateService;
- import com.example.springaimcpserver.service.MyToolService;
- import com.example.springaimcpserver.service.NumberService;
- import org.springframework.ai.tool.ToolCallback;
- import org.springframework.ai.tool.ToolCallbackProvider;
- import org.springframework.ai.tool.function.FunctionToolCallback;
- import org.springframework.ai.tool.method.MethodToolCallbackProvider;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.annotation.Bean;
- import java.time.LocalDateTime;
- @SpringBootApplication
- public class SpringAiMcpServerApplication {
- public static void main(String[] args) {
- SpringApplication.run(SpringAiMcpServerApplication.class, args);
- }
- /**
- * ToolCallback:单个工具,适合简单、独立的工具
- * ToolCallbackProvider:工具提供者,适合批量注册(通过扫描 @Tool 方法)
- * 两种方式都会被 Spring AI 识别并注册为 MCP 工具。
- */
- @Bean
- public ToolCallbackProvider customTools(MyToolService myToolService) {
- return MethodToolCallbackProvider.builder().toolObjects(myToolService).build();
- }
- @Bean
- public ToolCallbackProvider customTools2(NumberService numberService, DateService dateService, BankService bankService) {
- return MethodToolCallbackProvider.builder().toolObjects(numberService, dateService, bankService).build();
- }
- public record TextInput(String input) {
- }
- public record CalculatorInput(double a, double b, String operation) {
- }
- /**
- * 将输入字母转为大写
- */
- @Bean
- public ToolCallback toUpperCase() {
- System.out.println("toUpperCase 被调用");
- return FunctionToolCallback.builder("toUpperCase", (TextInput input) -> input.input().toUpperCase())
- .inputType(TextInput.class)
- .description("将字母转成大写")
- .build();
- }
- /**
- * 计算器工具 - 支持加减乘除运算
- */
- @Bean
- public ToolCallback calculator() {
- System.out.println("calculator 被调用");
- return FunctionToolCallback.builder("calculator", (CalculatorInput input) -> {
- double a = input.a();
- double b = input.b();
- String op = input.operation().toLowerCase().trim();
- return switch (op) {
- case "add", "+" -> String.format("%.2f + %.2f = %.2f", a, b, a + b);
- case "subtract", "-" -> String.format("%.2f - %.2f = %.2f", a, b, a - b);
- case "multiply", "*", "×" -> String.format("%.2f × %.2f = %.2f", a, b, a * b);
- case "divide", "/", "÷" -> {
- if (b == 0) {
- yield "错误:除数不能为零";
- }
- yield String.format("%.2f ÷ %.2f = %.2f", a, b, a / b);
- }
- default -> "错误:不支持的操作符。支持的操作:add/+, subtract/-, multiply/*, divide//";
- };
- })
- .inputType(CalculatorInput.class)
- .description("执行基本的数学运算(加法、减法、乘法、除法)。支持的操作:add/+, subtract/-, multiply/*, divide//")
- .build();
- }
- /**
- * 获取当前时间
- */
- @Bean
- public ToolCallback getCurrentTime() {
- System.out.println("getCurrentTime 被调用");
- return FunctionToolCallback.builder("getCurrentTime",
- () -> LocalDateTime.now().toString())
- .inputType(Void.class)
- .description("获取当前时间")
- .build();
- }
- }
复制代码 2. MCP Client
pom.xml- spring:
- application:
- name: spring-ai-mcp-server
- main:
- banner-mode: off
- ai:
- mcp:
- server:
- name: my-spring-ai-mcp-server
- version: 1.0.0
- type: async
- sse-endpoint: /sse
- sse-message-endpoint: /mcp/message
- capabilities:
- tool: true
- resource: true
- prompt: true
- completion: true
复制代码 application.yml- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-parent</artifactId>
- <version>3.5.7</version>
- <relativePath/>
- </parent>
- <groupId>com.example</groupId>
- spring-ai-mcp-client</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>spring-ai-mcp-client</name>
- <description>spring-ai-mcp-client</description>
- <properties>
- <java.version>17</java.version>
- <spring-ai.version>1.0.3</spring-ai.version>
- <spring-ai-alibaba.version>1.0.0.4</spring-ai-alibaba.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.ai</groupId>
- spring-ai-starter-mcp-client-webflux</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud.ai</groupId>
- spring-ai-alibaba-starter-dashscope</artifactId>
- </dependency>
- </dependencies>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.ai</groupId>
- spring-ai-bom</artifactId>
- <version>${spring-ai.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud.ai</groupId>
- spring-ai-alibaba-bom</artifactId>
- <version>${spring-ai-alibaba.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
复制代码 测试工具调用- server:
- port: 8081
- spring:
- application:
- name: my-spring-ai-mcp-client
- main:
- web-application-type: none
- ai:
- dashscope:
- api-key: sk-66217287a102487c4c52
- chat:
- enabled: true
- options:
- model: qwen3-max
- temperature: 0
- mcp:
- client:
- toolcallback:
- enabled: true
- sse:
- connections:
- server1:
- url: http://localhost:8080
- sse-endpoint: /sse
- mcp-chinese-fortune:
- url: https://mcp.api-inference.modelscope.net
- sse-endpoint: /d103803834594b/sse
复制代码 测试结果:
服务器端控制台输出:- package com.example.springaimcpclient;
- import org.springframework.ai.chat.client.ChatClient;
- import org.springframework.ai.tool.ToolCallbackProvider;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.ConfigurableApplicationContext;
- import org.springframework.context.annotation.Bean;
- @SpringBootApplication
- public class SpringAiMcpClientApplication {
- public static void main(String[] args) {
- SpringApplication.run(SpringAiMcpClientApplication.class, args);
- }
- @Bean
- public CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools,
- ConfigurableApplicationContext context) {
- // 打印所有可用的工具
- System.out.println("\n========== 所有可用的工具 ==========");
- var toolCallbacks = tools.getToolCallbacks();
- System.out.println("工具总数: " + toolCallbacks.length);
- for (int i = 0; i < toolCallbacks.length; i++) {
- var toolDef = toolCallbacks[i].getToolDefinition();
- System.out.println("\n【工具 " + (i + 1) + "】");
- System.out.println(" 名称: " + toolDef.name());
- System.out.println(" 描述: " + toolDef.description());
- }
- System.out.println("=====================================\n");
- return args -> {
- String systemPrompt = """
- 你是一个智能助手,拥有多个可用的工具。
- 当用户提问时,你必须优先考虑使用可用的工具来回答问题。
- 只有在没有合适的工具时,才使用你自己的知识来回答。
-
- 重要规则:
- 1. 如果有工具可以获取实时信息(如时间、日期),必须使用工具
- 2. 如果有工具可以执行操作(如字符串转换),必须使用工具
- 3. 使用工具后,将工具返回的结果展示给用户
- """;
- var chatClient = chatClientBuilder
- .defaultSystem(systemPrompt)
- .defaultToolCallbacks(tools)
- .build();
- // 测试问题1:当前时间(应该调用getCurrentTime工具)
- System.out.println("\n>>> QUESTION: 当前时间是多少");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("当前时间是多少").call().content());
- // 测试问题2:字符串转大写(应该调用toUpperCase工具)
- System.out.println("\n>>> QUESTION: 将abcd转成大写");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("将abcd转成大写").call().content());
- // 测试问题3:计算(应该调用calculator工具)
- System.out.println("\n>>> QUESTION: 计算 123 加 456 等于多少");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("计算 123 加 456 等于多少").call().content());
- // 测试问题4:今天星期几(应该调用getTodayWeekday工具)
- System.out.println("\n>>> QUESTION: 今天星期几");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("今天星期几").call().content());
- // 测试问题5:打印九九乘法表(应该调用printMultiplicationTable工具)
- System.out.println("\n>>> QUESTION: 打印九九乘法表");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("打印九九乘法表").call().content());
- // 测试问题6:查询开户行信息(应该调用queryBankInfo工具)
- System.out.println("\n>>> QUESTION: 查询行号为303100000629的开户行信息");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("查询行号为303100000629的开户行信息").call().content());
- // 测试问题7:判断奇偶数(应该调用checkOddOrEven工具)
- System.out.println("\n>>> QUESTION: 数字16是奇数还是偶数");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("数字16是奇数还是偶数").call().content());
- // 测试问题8:帮我算下命,出生时间 2015年 10月19日8点
- System.out.println("\n>>> QUESTION: 帮我算下命,出生时间 2015年 10月19日8点");
- System.out.println(">>> ASSISTANT: " + chatClient.prompt("帮我算下命,出生时间 2015年 10月19日8点").call().content());
-
- context.close();
- };
- }
- }
复制代码 客户端控制台输出:- getTodayWeekday 被调用
- printMultiplicationTable 被调用
- queryBankInfo 被调用
- checkOddOrEven 被调用
复制代码 抓包截图
SSE方式通信流程:
参考文档:
https://docs.spring.io/spring-ai/reference/getting-started.html
https://github.com/spring-ai-community/awesome-spring-ai
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |