写在前面

前面我们学习了MCP以及其中的三个角色,包含客户端、服务端和资源端,对应的解释如下:

(1)客户端:可理解为是Ai应用层,如聊天对话框。用户输入信息后,由客户端接口接收,再由客户端调用服务端调配资源处理;

(2)服务端:可理解为注册中心,请注意所有的工具都是注册在服务端;

(3)资源端:资源端就是大模型解析之后的用户需求的服务,即实际的处理逻辑。资源端可以和服务端部署在一起,也可以单独部署。

资源端

第一步,新建一个名为ai-mcp-server的SpringBoot项目,注意SpringBoot版本为3.4.2,JDK版本为17。之后pom.xml文件信息如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?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.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gutsyzhan</groupId>
<artifactId>ai-mcp-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai-mcp-server</name>
<description>ai-mcp-server</description>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

第二步,新建一个名为service的包,我们在里面定义两个类,分别用于实现数据采集和数据生成功能。新建一个名为DataAcquisitionService的类,里面的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class DataAcquisitionService {
@Tool(description = "数据采集工具,从一个源头数据源中采集数据到目标数据源")
public String acquisition(
@ToolParam(description = "源头数据源")String sourceDB,
@ToolParam(description = "目标数据源")String targetDB,
@ToolParam(description = "源头数据表")String sourceTable,
@ToolParam(description = "目标数据表")String targetTable,
@ToolParam(description = "采集策略")String type
){
//采集逻辑
System.out.println("数据采集信息为:"+"\n,源头数据源=" + sourceDB +
"\n,目标数据源="+ targetDB + "\n,源头数据表=" + sourceTable +
"\n,目标数据表=" + targetTable + "\n,采集策略=" + type);
return "数据采集成功";
}
}

这里我们模拟数据采集的逻辑,从源头数据源的数据表中,根据指定的策略,将数据采集到目标数据源的表中。接着再新建一个名为DataInterfaceService的类,里面的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class DataInterfaceService {
@Tool(description = "数据服务工具,用于生成一个数据接口")
public String produceMsg(
@ToolParam(description = "服务名称")String interfaceName,
@ToolParam(description = "数据源")String dataSource,
@ToolParam(description = "查询语句")String sql,
@ToolParam(description = "入参")String inParams,
@ToolParam(description = "出参")String outParams
){
//生成逻辑
System.out.println("数据生成信息为:"+"\n,服务名称=" + interfaceName +
"\n,数据源="+ dataSource + "\n,查询语句=" + sql +
"\n,入参=" + inParams + "\n,出参=" + outParams);
return "数据生成成功";
}
}

同样这里我们模拟数据生成的逻辑,定义一个服务名称,根据传入的参数和查询语句,从源数据源中查询得到出参数据。

服务端

这里我们将服务端和资源端写在一个项目中,在applicaiton.yml配置文件中新增如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 8009
# 服务端定义
spring:
ai:
mcp:
server:
name: ai-mcp-server
version: 0.0.1
# 服务方式(同步或者异步)
type: SYNC
# MCP有两种通讯方式stdio(内部进程通讯)和Server-Sent Events(SSE服务器发送事件)
stdio: false
sse-message-endpoint: /mcp/message
enabled: true
# 变化通知
resource-change-notification: true
tool-change-notification: true
prompt-change-notification: true

当然,如果使用的是MCP的内部进程通讯方式,需要添加如下两行配置:

1
2
spring.main.banner-mode=off
logging.file.name=D:/logs/server.log

定义一个名为CustomServer的类,里面的代码如下:

1
2
3
4
5
6
7
8
9
@Component
public class CustomServer {
@Bean
public ToolCallbackProvider myTools(DataAcquisitionService dataAcquisitionService,
DataInterfaceService dataInterfaceService){
return MethodToolCallbackProvider.builder().toolObjects(dataAcquisitionService
,dataInterfaceService).build();
}
}

此处就是将工具注册进来,方便后续客户端调用。

客户端

客户端笔者建议另起一个新的项目来写,第一步,新建一个名为ai-mcp-client的SpringBoot项目,注意SpringBoot版本为3.4.2,JDK版本为17。之后pom.xml文件信息如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?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.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gutsyzhan</groupId>
<artifactId>ai-mcp-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai-mcp-client</name>
<description>ai-mcp-client</description>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

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

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

第二步,修改application.yml配置文件中的内容为如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
application:
name: ai-mcp-client
main:
web-application-type: none
ai:
ollama:
base-url: http://127.0.0.1:11434
chat:
options:
model: qwen3:1.7b
temperature: 0.7
mcp:
client:
sse:
connections:
server1:
url: http://127.0.0.1:8009
server:
port: 8010
# ai.user.input表示用户的需求
ai:
user:
input: 第一步,将prod数据源中的t_order表中的数据全量采集到test数据源的t_order表中,第二步再生成一个数据接口,用于获取所有的用户,从prod数据源中查询全量的用户数据,入参为default,出参为用户名和时间

第三步,新建一个名为client的包,并在里面定义一个名为CustomClient的类,里面的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class CustomClient {
@Value("${ai.user.input}")
private String userInput;

@Bean
public CommandLineRunner solveQuestion(ChatClient.Builder chatBuilder,
ToolCallbackProvider tools,
ConfigurableApplicationContext context){
return (args -> {
ChatClient chatClient = chatBuilder.defaultTools(tools).build();
System.out.println("\n>>> 问题为:" + userInput);
System.out.println("\n>>> 参考方法为:" + chatClient.prompt(userInput).call().content());
context.close();
});
}
}

可以看到这里我们将用户输入的信息直接写在了配置文件中,通过@Value注解进行获取。项目在启动时,会将其传入给服务端,服务端将用户输入和注册的工具列表一起传给大模型。大模型会根据需求来进行工具匹配,匹配到对应工具之后,再看参数是否对应的上,如果对应的上,则执行具体的逻辑,如果匹配不上,则不执行对应逻辑。

第四步,先启动服务端项目,再启动客户端,可以发现此时客户端输出如下信息:

接着再去看服务端,可以看到客户端也输出如下信息:

小结

本篇通过一个实战项目学习了如何使用SpringBoot和MCP实现本地工具调用。请注意,如果开发者存在多个工具进行编排的需求,如何保证工具调用的顺序呢?实际上我们只需在问题描述中,写清楚你每一步需要做什么,然后大模型就会找到对应的工具类,并按照顺序进行调用,所不同的是,这种编排动作是在输入内容中完成的。

看到这里你可能会有疑问,MCP方式和注入Dify配置工作流这种方式有什么区别?我们知道MCP它是大模型上下文协议,提供了一种大模型调用本地工具,使用本地文件的一种规范,开发者可以使用这种方式将本地逻辑发布为工具,以满足用户对需求不明的诉求,如果大模型找不到满足要求的工具,则会给出一个合适的回复,这样用户也能接受。