Spring AI - MCP 小试牛刀

Spring AI 是 Spring 生态中专门为人工智能应用开发的框架,其核心目标是将 Spring 的设计理念应用于 AI 领域。
目前Spring AI 已经支持了 MCP。

这里我就用它实现一个简单的 MCP,功能就是获取当前的日期。
使用 Spring AI 的 MCP Server 和 MCP Client。

Spring AI 版本:1.0.0-M7

MCP Server 端

pom 依赖

1
2
3
4
<dependency>  
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

application.yaml

1
2
3
4
5
6
7
8
server:  
port: 8081
spring:
ai:
mcp:
server:
name: get-date-mcp-server
version: 1.0.0

工具定义

1
2
3
4
5
6
7
8
9
@Service  
public class DateService {

@Tool(description = "Get today date")
public String getTodayDate() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}

}

将工具注册

1
2
3
4
@Bean  
public ToolCallbackProvider tools(DateService dateService) {
return MethodToolCallbackProvider.builder().toolObjects(dateService).build();
}

MCP Client 端

这里大模型服务我用的不是 API 云服务,而是自己部署的大模型服务,所以需要自己构造 ChatClient 实例,更适用于企业级大规模应用的场景。
这里大模型用的是支持 function call 的 QwQ 32B(本想用 DeepSeeK R1,但是 spring ai mcp 这种方式还走不通,详见下边遇到的坑)。

QwQ 32B 部署时需要开启 function call 支持:
vllm 部署: --enable-auto-tool-choice --tool-call-parser hermes
sglang 部署:--tool-call-parser qwen25

先看一下没有加 MCP 时问 QwQ 32B “今天几号”的结果:

1
2
3
4
5
6
7
8
9
10
好的,用户问“今天几号”,我需要先确定当前的日期。不过,作为AI助手,我其实是不知道实时时间的,但可以假设用户是在提问时的当前日期。首先,我应该检查系统时间或者获取当前的日期信息。不过可能系统会自动提供,或者我需要根据编程时的日期来推断?

假设现在的时间是2023年10月5日,用户可能想知道今天的日期,可能是为了安排日程、记住某个事件或者确认时间。我需要直接回答今天的日期,同时保持友好和简洁。另外,用户可能没有其他深层需求,但有时候可能需要进一步帮助,比如提醒某个事件或者转换时区,但问题本身比较直接,不需要过多扩展。

需要确保回答准确,如果系统无法获取实时数据,可能需要说明当前日期是基于系统时间,可能存在时差或错误。但通常这类问题会由前端处理,所以作为后端回答,可能只需要给出格式化的回复即可。例如:“今天是2023年10月5日。”或者加上星期几。另外,检查是否有文化差异,比如不同地区日期格式不同,但中文环境下通常是年月日顺序。

可能用户的问题背后有其他需求,比如需要知道周末安排,或者即将到来的节日,但如果没有更多信息,保持回答简洁最好。确认回复无误后,就可以发送了。
</think>

今天是2023年10月5日。

实际现在已经2025年了, 这显然是不对的。

下边让我们实现 MCP client 然后再问问它。

application.yaml

1
2
3
4
5
6
7
8
9
10
11
12
server:  
port: 8080
spring:
ai:
chat.client.enabled: false # 使用自部署大模型的关键配置
openai.api-key: "必须配这个,否则启动报错"
mcp:
client:
toolcallback.enabled: true # mcp 关键配置
sse.connections:
get-date-mcp-server:
url: http://localhost:8081

构建 ChatClient

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
import org.springframework.ai.chat.client.ChatClient;  
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenAiConfig {

@Bean
@Qualifier(ModelConstants.QWQ_32B)
public ChatClient qwq32b(ToolCallbackProvider tools) {
OpenAiChatOptions options = OpenAiChatOptions.builder()
.model(ModelConstants.QWQ_32B)
.temperature(0.6)
.topP(0.95)
.build();
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl(ApiConstants.BASE_URL2)
.apiKey(ApiConstants.API_KEY2)
.build();
OpenAiChatModel model = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.build();
return ChatClient.builder(model)
.defaultOptions(options)
.defaultTools(tools)
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory()),
new SimpleLoggerAdvisor()
)
.build();
}

}

整个 API 接口方便测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import jakarta.annotation.Resource;  
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path = "/")
public class ChatController {

@Resource
@Qualifier(ModelConstants.QWQ_32B)
private ChatClient qwq32b;

@GetMapping("/api/qwq")
public String callService3(@RequestParam String prompt) {
return qwq32b.prompt().user(prompt).call().content();
}

}

再次问它 http://127.0.0.1:8080/api/qwq?prompt=今天几号

1
2
3
4
好的,用户问今天几号,我需要调用获取当前日期的工具。首先,检查可用的工具,发现有一个叫做spring_ai_mcp_client_get_date_mcp_server_getTodayDate的函数,不需要参数。于是生成对应的tool_call,调用这个函数。然后,系统返回了日期"2025-04-22",我需要将这个结果以自然的方式告诉用户。确认日期格式正确,没有错误信息,直接回复今天的日期即可。
</think>

今天是2025年4月22日。

这次显然调用了 MCP Server 并且结果是对了的。

遇到的一些坑

1、mcp server 重启之后,mcp client 并不会重连,这个在生产环境没法直接用,需要自己来构建维护

image.png

2、MCP Client 的调用方式是使用 function call 的方式,也就是 DeepSeek R1 用不了,服务器直接500报错
image.png


Spring AI - MCP 小试牛刀
https://www.haoyizebo.com/posts/52988ad5/
作者
一博
发布于
2025年4月22日
许可协议