自定义MCP Server
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);
}
}
@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: trueapplication.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.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
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! 东西不错很实用谢谢分享 很好很强大我过来先占个楼 待编辑
页:
[1]