稀土掘金技术社区 04月09日 19:22
SpringAI-MCP技术初探
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了Spring AI在AI领域的发展,特别是其对模型上下文协议(MCP)的支持。文章介绍了Spring AI从FunctionCall到Tool Calling的转变,以及MCP协议的核心概念,包括Prompt、Resource和Tools。通过实际演示,展示了如何利用Spring AI的MCP能力构建与外部资源交互的AI应用,并提供了开发MCP服务端和客户端的详细步骤,帮助开发者更好地理解和应用Spring AI。

🔄 Spring AI 从 FunctionCall 转向 Tool Calling,旨在更好地支持 MCP 协议,简化了 API 接口,方便开发者进行升级和维护。

💡 MCP(模型上下文协议)是 AI 领域中的“通用插座”,它为 AI 模型与外部数据源和工具之间搭建了沟通桥梁,实现了大模型工具实现和工具调用的分离,是构建智能体 Agent 的基础。

🛠️ Spring AI 提供了 MCP 的客户端实现,通过配置,可以使大模型具备访问数据库和本地文件系统的能力,从而扩展了 AI 应用的功能。

⚙️ 开发 MCP 服务端需要注册 Tools、Prompts 和 Resources,Spring AI 提供了相应的 API,方便开发者构建自己的 MCP 服务端程序。

🚀 在 Spring AI 1.0.0-M6 版本中,MCP 客户端的配置和使用更加简化,开发者可以直接通过自动装配的方式注入 McpClient,简化了开发流程。

原创 自然吸气发动机 2025-04-05 09:01 重庆

点击关注公众号,“技术干货”及时达!

关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding

一、前言

在之前的文章 SpringAI 介绍[1]里面我介绍了 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…[2] 简单来说,Tool Calling 和 FunctionCall 的作用大致相同,只不过换用了一套注解,使用 Tool Calling 之后 可以像下面的代码这样去绑定到 ChatClient 上面

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…[3] 有两种 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…[4] 中提供的一些开源 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 安装命令如下:

# 确保你已经安装了python
pip install uvx


npx 则是随着 npm 一起安装的,一般情况下只要安装了 npm 则也就自动安装了 npx 工具。

3.2 使用大模型生成一个简单的前端页面

这一步就比较简单,笔者是使用 DeepSeek 生成的前端页面,但是需要注意的是 Prompt 需要反复打磨,Deepseek 具备识图能力也可以直接发图给 Deepseek,记住需要在提示词里面告诉大模型,不要用 Vue 或者 React, 因为我们的功能非常简单,所以不需要上这种重量级框架,此外可以告诉大模型需要使用 EventSource 来接收服务端的数据以实现打字机效果

3.3 编写代码声明 MCP 配置

@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 大模型客户端集成

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 接口即可进行测试。

@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 下面三种依赖中的一种,取决于你打算使用什么通信方式

<!--标准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 如下:

@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 中进行服务暴露

# 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 中做下述配置:

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 文件中引入下面的依赖:

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


最后我们就可以直接使用了,详细可以看下下面的代码

@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.com/AHUCodingBe…[5] ,运行之前请前往阿里云百炼平台[6]申请自己的 API-Key,目前 SpringAI 最新 1.0.0-M6 版本的网上资源比较少,如果源码对你有帮助,欢迎 Star。

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

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Spring AI MCP协议 Tool Calling AI应用开发 大模型
相关文章