自定义MCP Server
1. MCP Server
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>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());
}
}
}
注册工具
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();
}
}
启动MCP服务
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
2. MCP Client
pom.xml
<?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>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>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>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>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>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
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 被调用
客户端控制台输出:
========== 所有可用的工具 ==========
工具总数: 8
【工具 1】
名称: spring_ai_mcp_client_server1_calculator
描述: 执行基本的数学运算(加法、减法、乘法、除法)。支持的操作:add/+, subtract/-, multiply/*, divide//
【工具 2】
名称: spring_ai_mcp_client_server1_toUpperCase
描述: 将字母转成大写
【工具 3】
名称: spring_ai_mcp_client_server1_getCurrentTime
描述: 获取当前时间
【工具 4】
名称: spring_ai_mcp_client_server1_queryBankInfo
描述: 通过行号查询开户行信息
【工具 5】
名称: spring_ai_mcp_client_server1_checkOddOrEven
描述: 判断一个数字是奇数还是偶数
【工具 6】
名称: spring_ai_mcp_client_server1_printMultiplicationTable
描述: 打印标准九九乘法表
【工具 7】
名称: spring_ai_mcp_client_server1_getTodayWeekday
描述: 获取今天是星期几
【工具 8】
名称: spring_ai_mcp_client_mcp_chinese_fortune_fortune_teller
描述: 根据出生年月日小时分析生辰八字和命理信息来进行算命
=====================================
2025-11-07T18:15:26.146+08:00 INFO 57852 --- [my-spring-ai-mcp-client] [ main] c.e.s.SpringAiMcpClientApplication : Started SpringAiMcpClientApplication in 3.926 seconds (process running for 4.366)
>>> QUESTION: 当前时间是多少
>>> ASSISTANT: 当前时间是:2025年11月7日 18:15:27。
>>> QUESTION: 将abcd转成大写
>>> ASSISTANT: ABCD
>>> QUESTION: 计算 123 加 456 等于多少
>>> ASSISTANT: 123 加 456 等于 579。
>>> QUESTION: 今天星期几
>>> ASSISTANT: 今天是星期五。
>>> QUESTION: 打印九九乘法表
>>> ASSISTANT: 以下是标准的九九乘法表:
```
1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
1×4=4 2×4=8 3×4=12 4×4=16
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25
1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36
1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49
1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64
1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81
```
>>> QUESTION: 查询行号为303100000629的开户行信息
>>> ASSISTANT: 根据查询结果,行号为 **303100000629** 的开户行信息如下:
- **银行名称**: 中国光大银行股份有限公司北京通州支行
- **联系电话**: 80883251
- **生效日期**: 2014-08-06
- **失效日期**: 2999-12-31
如需其他帮助,请随时告知!
>>> QUESTION: 数字16是奇数还是偶数
>>> ASSISTANT: 数字 16 是偶数。
>>> QUESTION: 帮我算下命,出生时间 2015年 10月19日8点
>>> ASSISTANT: 根据您提供的出生时间(2015年10月19日8点),以下是命理分析结果:
### 基本信息
- **公历生日**:2015年10月19日
- **农历生日**:乙未年九月初七
- **生肖**:羊
- **星座**:天秤座
- **八字**:乙未 丙戌 戊辰 丙辰
- **纳音五行**:金
### 八字解析
- **日主**:戊土(代表自己)
- **年柱**:乙未(比肩)
- **月柱**:丙戌(伤官)
- **日柱**:戊辰(日主坐财)
- **时柱**:丙辰(伤官)
### 命理特点
1. **性格特征**:
- 日主为戊土,为人诚实稳重,有责任感。
- 月柱和时柱均为丙火伤官,表示聪明伶俐、思维活跃,具有创造力和表达能力。
- 伤官透出,说明个性独立,不喜欢受约束,有时可能显得有些叛逆。
2. **五行分析**:
- 八字中土旺(戊土日主,辰戌未三土),火生土(丙火伤官),整体偏燥。
- 需要注意调和五行,适当补充金水元素以平衡命局。
3. **运势特点**:
- 伤官生财,说明有较强的赚钱能力和商业头脑。
- 财星在日支,表示财运较好,但需注意理财规划。
- 比肩在年柱,代表早年家庭环境较为稳定,但竞争意识较强。
4. **注意事项**:
- 伤官过旺,需要注意情绪管理和人际关系,避免过于直言不讳。
- 土旺之人容易固执,建议多听取他人意见,保持开放心态。
### 建议
- **发展方向**:适合从事创意、艺术、技术或商业相关的工作。
- **健康方面**:注意脾胃和消化系统的保养,避免过度劳累。
- **人际关系**:培养耐心和包容心,有助于建立良好的人际网络。
以上是基于传统命理学的分析,仅供参考。命运掌握在自己手中,积极努力才是成功的关键!
抓包截图

SSE方式通信流程:

参考文档:
https://docs.spring.io/spring-ai/reference/getting-started.html
https://github.com/spring-ai-community/awesome-spring-ai

浙公网安备 33010602011771号