一、前言
在之前的文章SpringAI介绍里面我介绍了SpringAI 目前的技术发展现状以及一些RAG、Function Call 、向量数据库等知识,当时使用的还是1.0.0-M3 版本,然后这段时间我再次登录SpringAI官方网站发现又有了很多新变化,版本也已经来到了1.0.0-M6 版本,不得不感慨Spring在AI领域的发展动作还是很快的。
可以发现SpringAI把FunctionCall已经标记为废弃状态,真的让人大吃一惊,仔细深入查看文档发现如今的Spring更为推荐使用Tool Calling 感兴趣的读者可以阅读下这篇文章 docs.spring.io/spring-ai/r... 简单来说,Tool Calling 和 FunctionCall的作用大致相同,只不过换用了一套注解,使用Tool Calling之后 可以像下面的代码这样去绑定到ChatClient上面
java
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
// 注册工具到大模型上面
ChatModel chatModel = ...
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build():
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
那为什么好好的FunctionCall需要改为ToolCall,我猜测背后的原因主要是SpringAI想去支持MCP协议,MCP协议中规定了服务端程序需要具备Prompt、Resource、Tools三个要素(后文会有详细讲解),所以把FunctionCall的API全部修改为ToolCall,读者可以参考:docs.spring.io/spring-ai/r... 有两种API的使用对比,改造的规模还是挺大的,不得不说以后升级SpringAI大版本又不知道会让多少研发陷入API改造的深渊。
二、MCP协议介绍
MCP(Model Context Protocol)又被称之为模型上下文协议,MCP 就像是 AI 世界中的 "通用插座",为 AI 模型与外部数据源和工具之间搭建起沟通的桥梁,致力于标准化应用程序为 LLM 提供上下文的方式,让不同的 AI 组件和系统能够顺畅连接、协同工作。这个就有点像Jdbc 协议一样,通过定义规范,让中间件厂商进行实现。
我们看几张来自SpringAI官网的图示:
在上图中我们可以发现,我们开发的大模型应用程序可以通过MCPClient去访问外部资源,并且是典型的Client-Server架构。实现了大模型工具实现和工具调用的分离,就像通过一个第三方管家,来统一管理函数调用,在我看来MCP就是比FunctionCalling的更高一级抽像,也是实现智能体Agent的基础。
在上面的图示中我们可以发现目前的MCP 服务端存在两种类型一种是标准IO,一种是SSE模式,对于标准IO的服务端和大模型应用是一对一的对应关系,而通过SSE方式进行客户端服务端通信的模型,服务端则可以与客户端进行一对多通信
也许在不久的未来,目前的服务端开发人员,不再需要和前端页面对接只需要和大模型的McpClient对接了,即一切服务端程序都需要接入MCP协议以更好的服务大模型和智能体。
三、MCP体验
下面我们开发一个简单demo,去体验一下SpringAI的MCP能力,我们计划通过MCPClient让大模型具备访问数据库和本地文件系统的能力,实际效果如下图所示:
这个demo中我们先不开发MCP服务端应用程序,而是利用 modelcontextprotocol.io/introductio...中提供的一些开源MCP服务端实现。
此外额外说明下版本问题,考虑到目前大部分人使用的都是SpringAI 1.0.0-M5版本,所以本节先使用SpringAI 1.0.0-M5版本做演示,在第四节将具体演示SpringAI 1.0.0-M6的功能,也可以给大家做一个对比,方便大家比较两个大版本的API改动。
3.1 本地安装npx和uvx
npx是nodeJs下的一个工具可以执行一些Ts或者JS脚本甚至应用程序,uvx的功能则和他很类似是python环境下执行脚本的工具,因为我们使用的文件服务器MCP服务端是使用Ts代码写的,而访问数据库的MCP服务端程序是用Python写的所以我们必须去安装下这两个命令以支持MCPClient调用,简单来说,使用 TypeScript 编写的 MCP server 可以通过 npx 命令来运行,使用 Python 编写的 MCP server 可以通过 uvx 命令来运行。
uvx安装命令如下:
shell
# 确保你已经安装了python
pip install uvx
npx则是随着npm一起安装的,一般情况下只要安装了npm则也就自动安装了npx工具。
3.2 使用大模型生成一个简单的前端页面
这一步就比较简单,笔者是使用DeepSeek生成的前端页面,但是需要注意的是Prompt需要反复打磨,Deepseek具备识图能力也可以直接发图给Deepseek,记住需要在提示词里面告诉大模型,不要用Vue或者React, 因为我们的功能非常简单,所以不需要上这种重量级框架,此外可以告诉大模型需要使用EventSource来接收服务端的数据以实现打字机效果
3.3 编写代码声明MCP配置
java
@Configuration
public class McpConfig {
@Bean
public List<McpFunctionCallback> functionCallbacks(List<McpSyncClient> mcpSyncClients) {
List<McpFunctionCallback> list = new ArrayList<>();
for (McpSyncClient mcpSyncClient : mcpSyncClients) {
list.addAll(mcpSyncClient.listTools(null)
.tools()
.stream()
.map(tool -> new McpFunctionCallback(mcpSyncClient, tool))
.toList());
}
return list;
}
@Bean(destroyMethod = "close")
public McpSyncClient mcpFileSysClient() {
// 把这里的路径记得改为自己的真实路径
var stdioParams = ServerParameters.builder("D:\\software\\nodeJs\\npx.cmd")
.args("-y", "@modelcontextprotocol/server-filesystem", "D:\\工作日志")
.build();
var mcpClient = McpClient.using(new StdioClientTransport(stdioParams))
.requestTimeout(Duration.ofSeconds(10)).sync();
var init = mcpClient.initialize();
System.out.println("mcpFileSysClient loading init=" + init);
return mcpClient;
}
@Bean(destroyMethod = "close")
public McpSyncClient mcpDbClient() {
// 把这里的路径记得改为自己的真实路径
var stdioParams = ServerParameters.builder("D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe")
.args("mcp-server-sqlite", "--db-path", "D:\\work-space-study\\spring-ai-mcp-demo\\mcp-client\\src\\main\\resources\\test.db")
.build();
var mcpClient = McpClient.using(new StdioClientTransport(stdioParams))
.requestTimeout(Duration.ofSeconds(10)).sync();
var init = mcpClient.initialize();
System.out.println("mcpDbClient loading init=" + init);
return mcpClient;
}
}
在上面的代码中我们声明了两个McpClient使用stdIo(标准IO)的方式去链接MCP服务端,并通过@Bean(destroyMethod = "close") 让Spring容器关闭的时候一并释放客户端占用,最后我们把两个服务端提供的tools全部转为List集合中,这个集合会在后面定义ChatClient中使用到。
3.4 大模型客户端集成
java
public FileSysController(ChatClient.Builder chatClientBuilder,
List<McpFunctionCallback> functionCallbacks) {
this.chatClient = chatClientBuilder
// 注意这里
.defaultFunctions(functionCallbacks.toArray(new McpFunctionCallback[0]))
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build())
.build();
}
在这里我们创建ChatClient 并利用Spring的依赖注入能力注入了在之前McpConfig类中声明的functionCallbacks,至此这个大模型ChatClient就具备了调用MCP服务端的能力。使用大模型生成的前端页面输入问题访问下面的Controller接口即可进行测试。
java
@RequestMapping(value = "/generate", method = RequestMethod.GET)
public Flux<ChatResponse> generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) {
response.setCharacterEncoding("UTF-8");
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
return this.chatClient.prompt(prompt)
.advisors(messageChatMemoryAdvisor).stream().chatResponse();
}
四、如何基于SpringAI开发服务端应用程序
在前面的开发环节中,我们使用的是开源的MCP服务端,如何自己用Java语言开发一个MCP服务端程序呢?
本节将给出一个案例介绍使用SpringAI开发MCPserver的主要步骤
注意到这里你需要将你的SpringAI升级到1.0.0-M6版本,低版本的无法很好的支持MCP服务端调用
4.1 MCP-Server开发
开发MCPserver 需要引入SpringAI下面三种依赖中的一种,取决于你打算使用什么通信方式
xml
<!--标准IO通信类型的MCP服务端,适合命令行形式的桌面工具,例如之前的案例中的文件助手 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<!--基于Http协议通信类型的MCP服务端 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
</dependency>
<!--基于SSE通信类型的MCP服务端 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
一个标准的MCP服务端程序需要包含三个主要信息分别为Tools、Prompts、Resources
资源(Resources):资源是AI可以读取的数据,比如文件内容、数据库查询结果或API的响应。 例如,AI可能通过资源获取你的日历事件列表。
工具(Tools):工具是AI可以调用的函数,用于执行特定操作,比如添加新任务或发送邮件,使用工具时,通常需要用户先批准,以确保安全。
提示词(Prompts):提示词是服务器提供给AI的预写消息或模板,帮助AI理解如何使用资源和工具,例如,服务器可能告诉AI:"你可以添加任务,试试说'添加任务:买牛奶'",从而帮助用户更轻松地完成任务。提示词虽然直接提供给AI,但实际上是通过AI间接帮助用户,比如AI会根据提示词告诉用户如何操作。
这三个要素在SpringAI中对应的API如下:
java
@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
@Bean
public ToolCallbackProvider bookServiceTools(BookService bookService) {
// ...... 注册工具
return MethodToolCallbackProvider.builder().toolObjects(bookService).build();
}
@Bean
public List<McpServerFeatures.SyncResourceRegistration> resourceRegistrations() {
// ...... 注册资源
return List.of(resourceRegistration);
}
@Bean
public List<McpServerFeatures.SyncPromptRegistration> promptRegistrations() {
// ......注册Prompt
return List.of(promptRegistration);
}
}
上面的代码中BookService 可以是你自己写的一个服务,比如可以执行查询DB或者调用其他微服务等代码实现。
最后还需要再application.yaml中进行服务暴露
java
# Using spring-ai-mcp-server-webmvc-spring-boot-starter
spring:
ai:
mcp:
server:
name: webmvc-mcp-server
version: 1.0.0
type: SYNC
sse-message-endpoint: /mcp/messages
至此你的服务端基本就开发完毕了。
4.2 MCP-Client开发
在4.1中我们开发了我们自己的MCP-server 那么怎么去让大模型去调用它呢?相比SpringAI1.0.0-M5版本的McpClient集成方式,在SpringAI 1.0.0-M6那就发生了翻天覆地的变化。总体来说配置变得更加简单:
首先我们需要在application.yaml中做下述配置:
yaml
server:
port: 9999
spring:
ai:
mcp:
client:
enabled: true
name: call-mcp-server
sse:
connections:
server1:
# 这里是你的Mcp服务端的暴露地址
url: http://127.0.0.1:8080
dashscope:
api-key: {通义千问API-Key}
接着你需要在pom文件中引入下面的依赖:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
最后我们就可以直接使用了,详细可以看下下面的代码
java
@RestController
@RequestMapping("/dashscope/chat-client")
public class ChatController {
private final ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
// 直接使用自动装配就会自动注入McpClient
public ChatController(ChatClient.Builder chatClientBuilder,
List<McpSyncClient> mcpSyncClients,
ToolCallbackProvider tools) {
this.chatClient = chatClientBuilder
// 注意这里API发生了变化 不再使用defaultFunctions
.defaultTools(tools)
.defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build())
.build();
}
@RequestMapping(value = "/generate_stream", method = RequestMethod.GET)
public Flux<ChatResponse> generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) {
response.setCharacterEncoding("UTF-8");
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
return this.chatClient.prompt(prompt)
.advisors(messageChatMemoryAdvisor)
.stream()
.chatResponse();
}
}
我们对比下之前在SpringAI1.0.0-M5的客户端实现方式就可以发现,现在我们不需要自己去定义McpClient,由于我们引入了spring-ai-mcp-client-spring-boot-starter 就可以自动识别我们配置的mcpClient信息,进行自动装配,此外我们也可以发现我们创建ChatClient使用的API也不再是defaultFunctions而是defaultTools。
那就有聪明的读者要问了那这样配置的话我的超时时间怎么配置呢?SpringAI提供了McpSyncClientCustomizer接口这个接口里面提供了定制逻辑,可以实现该接口做一些McpClient定制工作。
最后我们先启动Mcp-Server然后启动我们的Mcp-client应用程序,开始提问,实际效果如下:
在上图中大模型通过了McpClient调用了我在Mcp-Server提供了BookService,然后按照名字查询到了对应的书籍,这些书籍信息是我在代码中预制的信息,大模型输出这个信息也很好的证明了确实调用到了我们的Mcp服务端接口。
五、总结
本文介绍了下目前比较火热的MCP协议设计内容以及关键思想,同时基于SpringAI的两个版本分别给出了具体的实现方案,希望对各位读者有所帮助,由于SpringAI发展很快,内容如有错误欢迎沟通。
也欢迎大家在评论区交流,需要源码的也可以在下方评论区进行评论,我会适时将源码放置到Github
内容创作不易,转载请务必注明出处,谢谢。