MCP是什么
MCP 是一个开放协议,它为应用程序向 LLM 提供上下文的方式进行了标准化。你可以将 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 为设备连接各种外设和配件提供了标准化的方式一样,MCP 为 AI 模型连接各种数据源和工具提供了标准化的接口。
Spring AI已经支持MCP服务和客户端。引入对应的依赖,就可以开发自己的MCP服务
Spring AI文档
MCP架构
MCP 采用客户端-服务器架构:
- MCP 客户端(Client):通常是 AI 应用程序(如 Claude Desktop 或其他 LLM 工具),负责发起请求并与服务器通信。
- MCP 服务器(Server):轻量级程序,负责暴露特定的数据源或工具功能,并通过标准化协议与客户端交互。
下面介绍如何搭建Spring AI MCP
1. 新建项目
项目结构

父级pom配置
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?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.4</version>
<relativePath/>
</parent>
<groupId>com.hk</groupId>
<artifactId>springAi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springAi</name>
<description>springAi</description>
<packaging>pom</packaging>
<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.0</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
新建Server子模块
该模块用来模拟演示提供城市天气数据
pom配置
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
<?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>com.hk</groupId>
<artifactId>springAi</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.hk.springAiServer</groupId>
<artifactId>springAiServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springAiServer</name>
<description>springAiServer</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
</dependencies>
<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
server:
port: 8080
spring:
application:
name: springAiServer
ai:
mcp:
server:
name: hk-mcp-server
IHkMcpService
1
2
3
public interface IHkMcpService {
String selectTq(String city);
}
HkMcpServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.hk.springaiserver.service.IHkMcpService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
@Service
public class HkMcpServiceImpl implements IHkMcpService {
@Tool(description = "根据城市名称获取天气预报")
@Override
public String selectTq(@ToolParam(description = "城市信息")String city) {
System.out.println("查询城市:" + city);
return String.format("为您查询到%s今天天气:多云", city);
}
}

注册MCP方法
1
2
3
4
5
@Bean
public ToolCallbackProvider gzhRecommendTools(IHkMcpService service) {
System.out.println("初始化MCP工具");
return MethodToolCallbackProvider.builder().toolObjects(service).build();
}
启动服务端
启动后日志输出注入一个方法

新建client子模块
pom配置
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
<?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>com.hk</groupId>
<artifactId>springAi</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.hk.springAiClient</groupId>
<artifactId>springAiClient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springAiClient</name>
<description>springAiClient</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
</dependencies>
<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
25
26
server:
port: 8081
spring:
application:
name: springAiClient
ai:
ollama:
base-url: http://v-kun.com:11434
chat:
model: qwen3
options:
temperature: 0.7
mcp:
client:
name: mcp-client
sse:
connections:
server1:
url: http://localhost:8080
toolcallback:
enabled: true
controller
处理用户请求调用LLM
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
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.ChatClient.CallResponseSpec;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AiController {
@Autowired
private OllamaChatModel ollamaChatModel;
@Autowired
private ToolCallbackProvider toolCallbackProvider;
@GetMapping("/chat")
public String chat(String msg){
ChatClient chatClient = ChatClient.builder(ollamaChatModel).build();
CallResponseSpec call = chatClient.prompt(msg).toolCallbacks(toolCallbackProvider.getToolCallbacks()).call();
return call.content();
}
}
启动客户端

测试MCP服务
调用接口 http://localhost:8081/chat?msg=查询武汉天气
调用接口返回结果:
好的,用户之前让我查询武汉的天气,现在工具返回了结果。我需要把结果用自然的中文告诉他。返回的信息是“为您查询到武汉今天天气:多云”,看起来是正确的。我应该直接转述这个信息,保持简洁明了。用户可能只需要知道今天的天气情况,所以不需要额外添加其他内容。确认回复没有错误后,发送给用户。 为您查询到武汉今天天气:多云。

可以看到日志调用了我们自己写的工具
集成千问
pom添加依赖
1
2
3
4
5
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
application.yml
添加千问配置,spring.ai.openai.api-key不添加启动会报错
1
2
3
4
5
6
7
8
9
spring:
ai:
openai:
api-key: key
qwen:
api-key: key
endpoint: https://dashscope.aliyuncs.com/compatible-mode
model: qwen-turbo
注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Value("${spring.ai.qwen.api-key}")
private String qwenApiKey;
@Value("${spring.ai.qwen.endpoint}")
private String qwenEndpoint;
@Value("${spring.ai.qwen.model}")
private String qwenModel;
@Bean(name = "qwenChatModel")
public ChatModel qwenChatModel() {
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl(qwenEndpoint)
.apiKey(qwenApiKey)
.build();
OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
.model(qwenModel)
.temperature(0.7)
.build();
return OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(openAiChatOptions)
.build();
}
调用
1
2
3
4
5
6
7
8
9
@Resource(name = "qwenChatModel")
private ChatModel qwenModel;
@GetMapping("/ali")
public String ali(String msg){
ChatClient chatClient = ChatClient.builder(qwenModel).build();
CallResponseSpec call = chatClient.prompt(msg).toolCallbacks(toolCallbackProvider.getToolCallbacks()).call();
return call.content();
}
遇到问题
多个@Tool的name都是中文报错
server多个工具

client启动时报:Multiple tools with the same name
因为注册工具时把中文过滤掉了导致提示名称重复
影响版本:spring-ai-mcp 1.0.0

解决办法
用英文名称或者中文名称加上唯一id,例如:查询天气(cxtq)
<h2><a id="MCP_0"></a>MCP是什么</h2>
<p>MCP 是一个开放协议,它为应用程序向 LLM 提供上下文的方式进行了标准化。你可以将 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 为设备连接各种外设和配件提供了标准化的方式一样,MCP 为 AI 模型连接各种数据源和工具提供了标准化的接口。</p>
<blockquote>
<p>Spring AI已经支持MCP服务和客户端。引入对应的依赖,就可以开发自己的MCP服务</p>
</blockquote>
<p><a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html" target="_blank">Spring AI文档</a></p>
<h2><a id="MCP_7"></a>MCP架构</h2>
<p>MCP 采用客户端-服务器架构:</p>
<ul>
<li>MCP 客户端(Client):通常是 AI 应用程序(如 Claude Desktop 或其他 LLM 工具),负责发起请求并与服务器通信。</li>
<li>MCP 服务器(Server):轻量级程序,负责暴露特定的数据源或工具功能,并通过标准化协议与客户端交互。</li>
</ul>
<p>下面介绍如何搭建Spring AI MCP</p>
<h2><a id="1__16"></a>1. 新建项目</h2>
<p>项目结构<br />
<img src="https://v-kun-file.oss-cn-beijing.aliyuncs.com/blog/2025-08-07/276e8c7b42e54ada9a5affa5910782c8/image.png" alt="image.png" /><br />
父级pom配置</p>
<pre><div class="hljs"><code class="lang-xml"><span class="php"><span class="hljs-meta"><?</span>xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"UTF-8"</span><span class="hljs-meta">?></span></span>
<span class="hljs-tag"><<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">modelVersion</span>></span>4.0.0<span class="hljs-tag"></<span class="hljs-name">modelVersion</span>></span>
<span class="hljs-tag"><<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-parent<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>3.5.4<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">relativePath</span>/></span> <span class="hljs-comment"><!-- lookup parent from repository --></span>
<span class="hljs-tag"></<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.hk<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>springAi<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>0.0.1-SNAPSHOT<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">name</span>></span>springAi<span class="hljs-tag"></<span class="hljs-name">name</span>></span>
<span class="hljs-tag"><<span class="hljs-name">description</span>></span>springAi<span class="hljs-tag"></<span class="hljs-name">description</span>></span>
<span class="hljs-tag"><<span class="hljs-name">packaging</span>></span>pom<span class="hljs-tag"></<span class="hljs-name">packaging</span>></span>
<span class="hljs-tag"><<span class="hljs-name">properties</span>></span>
<span class="hljs-tag"><<span class="hljs-name">java.version</span>></span>21<span class="hljs-tag"></<span class="hljs-name">java.version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">spring-ai.version</span>></span>1.0.0<span class="hljs-tag"></<span class="hljs-name">spring-ai.version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">properties</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-web<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.projectlombok<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>lombok<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">optional</span>></span>true<span class="hljs-tag"></<span class="hljs-name">optional</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-test<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">scope</span>></span>test<span class="hljs-tag"></<span class="hljs-name">scope</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependencyManagement</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.ai<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-ai-bom<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>${spring-ai.version}<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">type</span>></span>pom<span class="hljs-tag"></<span class="hljs-name">type</span>></span>
<span class="hljs-tag"><<span class="hljs-name">scope</span>></span>import<span class="hljs-tag"></<span class="hljs-name">scope</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencyManagement</span>></span>
<span class="hljs-tag"><<span class="hljs-name">build</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-compiler-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"><<span class="hljs-name">annotationProcessorPaths</span>></span>
<span class="hljs-tag"><<span class="hljs-name">path</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.projectlombok<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>lombok<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">path</span>></span>
<span class="hljs-tag"></<span class="hljs-name">annotationProcessorPaths</span>></span>
<span class="hljs-tag"></<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-maven-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"><<span class="hljs-name">excludes</span>></span>
<span class="hljs-tag"><<span class="hljs-name">exclude</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.projectlombok<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>lombok<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">exclude</span>></span>
<span class="hljs-tag"></<span class="hljs-name">excludes</span>></span>
<span class="hljs-tag"></<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"></<span class="hljs-name">build</span>></span>
<span class="hljs-tag"></<span class="hljs-name">project</span>></span>
</code></div></pre>
<h2><a id="Server_103"></a>新建Server子模块</h2>
<p>该模块用来模拟演示提供城市天气数据</p>
<h3><a id="pom_105"></a>pom配置</h3>
<pre><div class="hljs"><code class="lang-xml"><span class="php"><span class="hljs-meta"><?</span>xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"UTF-8"</span><span class="hljs-meta">?></span></span>
<span class="hljs-tag"><<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">modelVersion</span>></span>4.0.0<span class="hljs-tag"></<span class="hljs-name">modelVersion</span>></span>
<span class="hljs-tag"><<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.hk<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>springAi<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>0.0.1-SNAPSHOT<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.hk.springAiServer<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>springAiServer<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>0.0.1-SNAPSHOT<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">name</span>></span>springAiServer<span class="hljs-tag"></<span class="hljs-name">name</span>></span>
<span class="hljs-tag"><<span class="hljs-name">description</span>></span>springAiServer<span class="hljs-tag"></<span class="hljs-name">description</span>></span>
<span class="hljs-tag"><<span class="hljs-name">properties</span>></span>
<span class="hljs-tag"><<span class="hljs-name">java.version</span>></span>21<span class="hljs-tag"></<span class="hljs-name">java.version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">properties</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.ai<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-ai-starter-mcp-server-webmvc<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">build</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-maven-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"></<span class="hljs-name">build</span>></span>
<span class="hljs-tag"></<span class="hljs-name">project</span>></span>
</code></div></pre>
<h3><a id="applicationyml_144"></a>application.yml</h3>
<pre><div class="hljs"><code class="lang-yml"><span class="hljs-comment"># 端口</span>
<span class="hljs-attr">server:</span>
<span class="hljs-attr"> port:</span> <span class="hljs-number">8080</span>
<span class="hljs-attr">spring:</span>
<span class="hljs-attr"> application:</span>
<span class="hljs-attr"> name:</span> <span class="hljs-string">springAiServer</span>
<span class="hljs-comment"># MCP 服务</span>
<span class="hljs-attr"> ai:</span>
<span class="hljs-attr"> mcp:</span>
<span class="hljs-attr"> server:</span>
<span class="hljs-attr"> name:</span> <span class="hljs-string">hk-mcp-server</span>
</code></div></pre>
<h3><a id="IHkMcpService_161"></a>IHkMcpService</h3>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IHkMcpService</span> </span>{
<span class="hljs-function">String <span class="hljs-title">selectTq</span><span class="hljs-params">(String city)</span></span>;
}
</code></div></pre>
<h3><a id="HkMcpServiceImpl_169"></a>HkMcpServiceImpl</h3>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-keyword">import</span> com.hk.springaiserver.service.IHkMcpService;
<span class="hljs-keyword">import</span> org.springframework.ai.tool.annotation.Tool;
<span class="hljs-keyword">import</span> org.springframework.ai.tool.annotation.ToolParam;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Service;
<span class="hljs-comment">/**
* <span class="hljs-doctag">@Author</span> hk
* <span class="hljs-doctag">@Description</span>
* <span class="hljs-doctag">@date</span> 2025/8/7 9:38
*/</span>
<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HkMcpServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">IHkMcpService</span> </span>{
<span class="hljs-meta">@Tool</span>(description = <span class="hljs-string">"根据城市名称获取天气预报"</span>)
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">selectTq</span><span class="hljs-params">(@ToolParam(description = <span class="hljs-string">"城市信息"</span>)</span>String city) </span>{
System.out.println(<span class="hljs-string">"查询城市:"</span> + city);
<span class="hljs-keyword">return</span> String.format(<span class="hljs-string">"为您查询到%s今天天气:多云"</span>, city);
}
}
</code></div></pre>
<p><img src="https://v-kun-file.oss-cn-beijing.aliyuncs.com/blog/2025-08-07/c97862e77e914b30ad01492404af54e5/image.png" alt="image.png" /></p>
<h3><a id="MCP_194"></a>注册MCP方法</h3>
<pre><div class="hljs"><code class="lang-java"> <span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> ToolCallbackProvider <span class="hljs-title">gzhRecommendTools</span><span class="hljs-params">(IHkMcpService service)</span> </span>{
System.out.println(<span class="hljs-string">"初始化MCP工具"</span>);
<span class="hljs-keyword">return</span> MethodToolCallbackProvider.builder().toolObjects(service).build();
}
</code></div></pre>
<h3><a id="_204"></a>启动服务端</h3>
<p>启动后日志输出注入一个方法<br />
<img src="https://v-kun-file.oss-cn-beijing.aliyuncs.com/blog/2025-08-07/edbe11c16d7f444483c9d9f3ce0548a5/image.png" alt="image.png" /></p>
<h2><a id="client_208"></a>新建client子模块</h2>
<h3><a id="pom_210"></a>pom配置</h3>
<pre><div class="hljs"><code class="lang-xml"><span class="php"><span class="hljs-meta"><?</span>xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"UTF-8"</span><span class="hljs-meta">?></span></span>
<span class="hljs-tag"><<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">modelVersion</span>></span>4.0.0<span class="hljs-tag"></<span class="hljs-name">modelVersion</span>></span>
<span class="hljs-tag"><<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.hk<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>springAi<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>0.0.1-SNAPSHOT<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.hk.springAiClient<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>springAiClient<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>0.0.1-SNAPSHOT<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">name</span>></span>springAiClient<span class="hljs-tag"></<span class="hljs-name">name</span>></span>
<span class="hljs-tag"><<span class="hljs-name">description</span>></span>springAiClient<span class="hljs-tag"></<span class="hljs-name">description</span>></span>
<span class="hljs-tag"><<span class="hljs-name">properties</span>></span>
<span class="hljs-tag"><<span class="hljs-name">java.version</span>></span>21<span class="hljs-tag"></<span class="hljs-name">java.version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">properties</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-test<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">scope</span>></span>test<span class="hljs-tag"></<span class="hljs-name">scope</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.ai<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-ai-starter-mcp-client<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.ai<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-ai-starter-model-ollama<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">build</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-maven-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"></<span class="hljs-name">build</span>></span>
<span class="hljs-tag"></<span class="hljs-name">project</span>></span>
</code></div></pre>
<h3><a id="applicationyml_262"></a>application.yml</h3>
<pre><div class="hljs"><code class="lang-yml"><span class="hljs-attr">server:</span>
<span class="hljs-comment"># 应用服务 WEB 访问端口</span>
<span class="hljs-attr"> port:</span> <span class="hljs-number">8081</span>
<span class="hljs-attr">spring:</span>
<span class="hljs-attr"> application:</span>
<span class="hljs-attr"> name:</span> <span class="hljs-string">springAiClient</span>
<span class="hljs-attr"> ai:</span>
<span class="hljs-attr"> ollama:</span>
<span class="hljs-comment"># Ollama配置</span>
<span class="hljs-attr"> base-url:</span> <span class="hljs-attr">http://v-kun.com:11434</span>
<span class="hljs-attr"> chat:</span>
<span class="hljs-attr"> model:</span> <span class="hljs-string">qwen3</span>
<span class="hljs-attr"> options:</span>
<span class="hljs-attr"> temperature:</span> <span class="hljs-number">0.7</span>
<span class="hljs-attr"> mcp:</span>
<span class="hljs-attr"> client:</span>
<span class="hljs-comment"># MCP客户端的配置</span>
<span class="hljs-attr"> name:</span> <span class="hljs-string">mcp-client</span>
<span class="hljs-attr"> sse:</span>
<span class="hljs-attr"> connections:</span>
<span class="hljs-attr"> server1:</span>
<span class="hljs-comment"># 要连接的服务地址</span>
<span class="hljs-attr"> url:</span> <span class="hljs-attr">http://localhost:8080</span>
<span class="hljs-attr"> toolcallback:</span>
<span class="hljs-comment"># 默认情况下,工具回调功能处于禁用状态,需要开启</span>
<span class="hljs-attr"> enabled:</span> <span class="hljs-literal">true</span>
</code></div></pre>
<h3><a id="controller_293"></a>controller</h3>
<p>处理用户请求调用LLM</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.ai.chat.client.ChatClient;
<span class="hljs-keyword">import</span> org.springframework.ai.chat.client.ChatClient.CallResponseSpec;
<span class="hljs-keyword">import</span> org.springframework.ai.ollama.OllamaChatModel;
<span class="hljs-keyword">import</span> org.springframework.ai.tool.ToolCallbackProvider;
<span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;
<span class="hljs-comment">/**
* <span class="hljs-doctag">@Author</span> hk
* <span class="hljs-doctag">@Description</span>
* <span class="hljs-doctag">@date</span> 2025/8/7 9:49
*/</span>
<span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AiController</span> </span>{
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> OllamaChatModel ollamaChatModel;
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> ToolCallbackProvider toolCallbackProvider;
<span class="hljs-meta">@GetMapping</span>(<span class="hljs-string">"/chat"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">chat</span><span class="hljs-params">(String msg)</span></span>{
ChatClient chatClient = ChatClient.builder(ollamaChatModel).build();
CallResponseSpec call = chatClient.prompt(msg).toolCallbacks(toolCallbackProvider.getToolCallbacks()).call();
<span class="hljs-keyword">return</span> call.content();
}
}
</code></div></pre>
<h3><a id="_327"></a>启动客户端</h3>
<p><img src="https://v-kun-file.oss-cn-beijing.aliyuncs.com/blog/2025-08-07/1cfc7bfae824462db73c058f39984497/image.png" alt="image.png" /></p>
<h2><a id="MCP_330"></a>测试MCP服务</h2>
<p>调用接口 <code>http://localhost:8081/chat?msg=查询武汉天气</code></p>
<p>调用接口返回结果:<br />
<code>好的,用户之前让我查询武汉的天气,现在工具返回了结果。我需要把结果用自然的中文告诉他。返回的信息是“为您查询到武汉今天天气:多云”,看起来是正确的。我应该直接转述这个信息,保持简洁明了。用户可能只需要知道今天的天气情况,所以不需要额外添加其他内容。确认回复没有错误后,发送给用户。 为您查询到武汉今天天气:多云。</code></p>
<p><img src="https://v-kun-file.oss-cn-beijing.aliyuncs.com/blog/2025-08-07/daa8c913020b4587a72ffbfaa4fa9d33/image.png" alt="image.png" /></p>
<p>可以看到日志调用了我们自己写的工具</p>
<h2><a id="_340"></a>集成千问</h2>
<h3><a id="pom_341"></a>pom添加依赖</h3>
<pre><div class="hljs"><code class="lang-xml">
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.ai<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-ai-starter-model-openai<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
</code></div></pre>
<h3><a id="applicationyml_351"></a>application.yml</h3>
<p>添加千问配置,spring.ai.openai.api-key不添加启动会报错</p>
<pre><div class="hljs"><code class="lang-yml"><span class="hljs-attr">spring:</span>
<span class="hljs-attr"> ai:</span>
<span class="hljs-attr"> openai:</span>
<span class="hljs-comment"># 不添加启动会报错</span>
<span class="hljs-attr"> api-key:</span> <span class="hljs-string">key</span>
<span class="hljs-attr"> qwen:</span>
<span class="hljs-attr"> api-key:</span> <span class="hljs-string">key</span>
<span class="hljs-attr"> endpoint:</span> <span class="hljs-attr">https://dashscope.aliyuncs.com/compatible-mode</span>
<span class="hljs-attr"> model:</span> <span class="hljs-string">qwen-turbo</span>
</code></div></pre>
<h3><a id="_365"></a>注入</h3>
<pre><div class="hljs"><code class="lang-java"> <span class="hljs-meta">@Value</span>(<span class="hljs-string">"${spring.ai.qwen.api-key}"</span>)
<span class="hljs-keyword">private</span> String qwenApiKey;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${spring.ai.qwen.endpoint}"</span>)
<span class="hljs-keyword">private</span> String qwenEndpoint;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${spring.ai.qwen.model}"</span>)
<span class="hljs-keyword">private</span> String qwenModel;
<span class="hljs-meta">@Bean</span>(name = <span class="hljs-string">"qwenChatModel"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> ChatModel <span class="hljs-title">qwenChatModel</span><span class="hljs-params">()</span> </span>{
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl(qwenEndpoint)
.apiKey(qwenApiKey)
.build();
OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
.model(qwenModel)
.temperature(<span class="hljs-number">0.7</span>)
.build();
<span class="hljs-keyword">return</span> OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(openAiChatOptions)
.build();
}
</code></div></pre>
<h3><a id="_391"></a>调用</h3>
<pre><div class="hljs"><code class="lang-java"> <span class="hljs-meta">@Resource</span>(name = <span class="hljs-string">"qwenChatModel"</span>)
<span class="hljs-keyword">private</span> ChatModel qwenModel;
<span class="hljs-meta">@GetMapping</span>(<span class="hljs-string">"/ali"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">ali</span><span class="hljs-params">(String msg)</span></span>{
ChatClient chatClient = ChatClient.builder(qwenModel).build();
CallResponseSpec call = chatClient.prompt(msg).toolCallbacks(toolCallbackProvider.getToolCallbacks()).call();
<span class="hljs-keyword">return</span> call.content();
}
</code></div></pre>
<h2><a id="_405"></a>遇到问题</h2>
<h3><a id="Toolname_406"></a>多个@Tool的name都是中文报错</h3>
<p>server多个工具<br />
<img src="https://v-kun-file.oss-cn-beijing.aliyuncs.com/blog/2025-08-11/1db37cc66f9445c1899bea93f6163ef7/image.png" alt="image.png" /><br />
client启动时报:Multiple tools with the same name<br />
因为注册工具时把中文过滤掉了导致提示名称重复<br />
影响版本:spring-ai-mcp 1.0.0<br />
<img src="https://v-kun-file.oss-cn-beijing.aliyuncs.com/blog/2025-08-11/efc46e05da0448cab707f939119d37a7/image.png" alt="image.png" /></p>
<h3><a id="_413"></a>解决办法</h3>
<p>用英文名称或者中文名称加上唯一id,例如:查询天气(cxtq)</p>