SpringAI-MCP技术初探

一、前言

在之前的文章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

内容创作不易,转载请务必注明出处,谢谢。

相关推荐
小杨4041 分钟前
springboot框架项目实践应用九(多数据源路由)
spring boot·后端·架构
GIS数据转换器9 分钟前
北斗+多技术融合地面沉降监测:精准守护城市安全
大数据·人工智能·物联网·无人机·智慧城市
背水22 分钟前
音频大语言模型可作为描述性语音质量评价器
人工智能·语言模型·音视频·语音识别
带电的小王36 分钟前
【大模型基础_毛玉仁】3.1 Prompt 工程简介
人工智能·语言模型·prompt
机器之心1 小时前
Django创造者Simon Willison分享:我如何使用LLM帮我写代码
人工智能
油泼辣子多加1 小时前
【计算机视觉】工业表计读数(3)--指针及刻度关键点识别
人工智能·计算机视觉
机器之心1 小时前
原作者带队再次改造xLSTM,7B模型速度最快超Mamba 50%,权重代码全开源
人工智能
搬砖的阿wei1 小时前
TensorRT:高性能深度学习推理的利器
人工智能·深度学习·tensorrt
uhakadotcom1 小时前
FPGA编程语言入门:从基础到实践
后端·面试·github
港 澳 在 线1 小时前
马蜂窝携手腾讯云接入DeepSeek,率先应用于旅游AI智能应用“AI游贵州”
人工智能·腾讯云·旅游