SpringAI-MCP技术详解 从概念到实战

人工智能 潘老师 来源:自然吸气发动机 1个月前 (03-19) 220 ℃ (2) 扫码查看

一、引言

之前在介绍SpringAI技术现状,以及RAG、Function Call、向量数据库等相关知识时,用的还是SpringAI 1.0.0 – M3版本。最近再看SpringAI官方网站,好家伙,变化可真不小,版本都到1.0.0 – M6了。这更新速度,不得不说Spring在AI领域发力真猛!

在新版本里,有个挺让人意外的改动,Function Call被标记为废弃了。仔细研究文档才知道,现在Spring更推荐用Tool Calling。这俩功能其实差不多,就是换了套注解。用Tool Calling后,能像下面这样把它绑定到ChatClient上:

class DateTimeTools {
    // 使用@Tool注解描述该方法的功能,即获取用户所在时区的当前日期和时间
    @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);

为啥要把Function Call改成Tool Call呢?我琢磨着,主要是SpringAI想支持MCP协议。MCP协议规定服务端程序得包含Prompt、Resource、Tools这三个要素,所以就把Function Call的API全改成Tool Call了。要是想了解这俩API的使用对比,可以参考https://docs.spring.io/spring-ai/reference/1.0/api/tools.html,改动规模还挺大,以后升级SpringAI大版本,估计不少研发人员又得忙着改API了。

二、MCP协议详解

MCP(Model Context Protocol),也就是模型上下文协议,它就好比AI世界里的“万能插座”。这个协议的作用是在AI模型和外部数据源、工具之间搭建沟通的桥梁,标准化应用程序给LLM提供上下文的方式,让不同的AI组件和系统能顺畅协作。这就跟Jdbc协议类似,通过制定规范,让中间件厂商去实现。

从SpringAI官网的图示能看到:大模型应用程序可以通过MCP Client访问外部资源,采用的是典型的Client – Server架构。这实现了大模型工具实现和工具调用的分离,就像有个第三方管家统一管理函数调用一样。在我看来,MCP比Function Calling更高级,是实现智能体Agent的基础。
目前MCP服务端有两种类型:标准IO和SSE模式。标准IO的服务端和大模型应用是一对一的关系,而SSE模式下,服务端能和多个客户端通信。说不定以后,服务端开发人员不用再对接前端页面,只需要对接大模型的McpClient就行,所有服务端程序都得接入MCP协议,更好地服务大模型和智能体。

三、体验MCP功能

下面通过一个简单的demo,感受下SpringAI的MCP能力。我们要让大模型通过MCP Client具备访问数据库和本地文件系统的能力,实际效果如下:

这个demo先不开发MCP服务端应用程序,用https://modelcontextprotocol.io/introduction提供的开源MCP服务端实现。考虑到很多人用的是SpringAI 1.0.0 – M5版本,这里先用这个版本演示,后面再讲讲1.0.0 – M6版本的功能,方便大家对比两个版本的API变化。

3.1 安装npx和uvx工具

npx是nodeJs下的工具,能执行Ts或JS脚本,甚至运行应用程序。uvx和它功能类似,是Python环境下执行脚本的工具。因为要用的文件服务器MCP服务端是用Ts代码写的,访问数据库的MCP服务端程序是用Python写的,所以得安装这俩工具,才能让MCP Client正常调用。简单说,TypeScript编写的MCP server用npx命令运行,Python编写的用uvx命令运行。
uvx的安装命令如下:

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

npx一般随npm一起安装,装了npm就有npx工具。

3.2 利用大模型生成前端页面

这步不难,我用DeepSeek生成前端页面。不过有个小注意点,Prompt得好好打磨。Deepseek有识图能力,也能直接发图。记得在提示词里告诉大模型,别用Vue或React框架,因为功能简单,用不上这么重量级的框架。还可以让大模型用EventSource接收服务端数据,实现打字机效果。

3.3 配置MCP相关代码

// 配置类,用于声明MCP相关的Bean
@Configuration
public class McpConfig {
    // 定义一个Bean,将McpSyncClient中的工具转换为McpFunctionCallback列表
    @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,创建用于访问文件系统的McpSyncClient
    @Bean(destroyMethod = "close")
    public McpSyncClient mcpFileSysClient() {
        // 配置文件系统服务端的参数,注意路径要改成自己的真实路径
        var stdioParams = ServerParameters.builder("D:\\software\\nodeJs\\npx.cmd")
               .args("-y", "@modelcontextprotocol/server-filesystem", "D:\\工作日志")
               .build();
        // 创建McpSyncClient,设置请求超时时间为10秒
        var mcpClient = McpClient.using(new StdioClientTransport(stdioParams))
               .requestTimeout(Duration.ofSeconds(10)).sync();
        // 初始化McpSyncClient,并打印初始化结果
        var init = mcpClient.initialize();
        System.out.println("mcpFileSysClient loading init=" + init);
        return mcpClient;
    }

    // 定义一个Bean,创建用于访问数据库的McpSyncClient
    @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();
        // 创建McpSyncClient,设置请求超时时间为10秒
        var mcpClient = McpClient.using(new StdioClientTransport(stdioParams))
               .requestTimeout(Duration.ofSeconds(10)).sync();
        // 初始化McpSyncClient,并打印初始化结果
        var init = mcpClient.initialize();
        System.out.println("mcpDbClient loading init=" + init);
        return mcpClient;
    }
}

这段代码声明了两个用标准IO方式连接MCP服务端的McpClient,用@Bean(destroyMethod = "close")让Spring容器关闭时释放客户端资源。最后把两个服务端的tools转成List集合,供后续定义ChatClient使用。

3.4 集成大模型客户端

// 构造函数,用于创建ChatClient并注入相关依赖
public FileSysController(ChatClient.Builder chatClientBuilder, 
                         List<McpFunctionCallback> functionCallbacks) {
    this.chatClient = chatClientBuilder
            // 将之前定义的functionCallbacks注入到ChatClient中
           .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) {
    // 设置响应编码为UTF-8
    response.setCharacterEncoding("UTF-8");
    // 创建消息聊天记忆顾问,用于管理聊天记忆
    var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
    // 调用ChatClient的prompt方法,生成聊天响应流
    return this.chatClient.prompt(prompt)
           .advisors(messageChatMemoryAdvisor).stream().chatResponse();
}

四、基于SpringAI开发服务端应用程序

前面用的是开源的MCP服务端,那怎么用Java自己开发一个MCP服务端程序呢?下面讲讲主要步骤。注意,得把SpringAI升级到1.0.0 – M6版本,低版本对MCP服务端调用支持不太好。

4.1 开发MCP – Server

开发MCP – server要引入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会根据提示词告诉用户怎么操作。

在SpringAI中,这三个要素对应的API代码如下:

// 配置类,用于配置MCP服务端相关的Bean
@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
    // 定义一个Bean,注册工具
    @Bean
    public ToolCallbackProvider bookServiceTools(BookService bookService) {
        // 通过MethodToolCallbackProvider.builder()方法构建工具回调提供者,将BookService注册为工具
        return MethodToolCallbackProvider.builder().toolObjects(bookService).build();
    }

    // 定义一个Bean,注册资源
    @Bean
    public List<McpServerFeatures.SyncResourceRegistration> resourceRegistrations() {
        // 这里返回一个包含资源注册的List,实际使用时需要根据具体情况添加资源注册
        return List.of(resourceRegistration);
    }

    // 定义一个Bean,注册Prompt
    @Bean
    public List<McpServerFeatures.SyncPromptRegistration> promptRegistrations() {
        // 这里返回一个包含提示词注册的List,实际使用时需要根据具体情况添加提示词注册
        return List.of(promptRegistration);
    }
}

这里的BookService可以是自己写的服务,比如实现查询数据库、调用其他微服务等功能。
最后,还得在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

开发好MCP – server后,怎么让大模型调用它呢?和SpringAI 1.0.0 – M5版本比,1.0.0 – M6版本的McpClient集成方式变化很大,配置更简单了。
首先在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,处理与聊天相关的请求
@RestController
@RequestMapping("/dashscope/chat-client")
public class ChatController {
    // 注入ChatClient
    private final ChatClient chatClient;
    // 定义聊天记忆,这里使用InMemoryChatMemory
    private final ChatMemory chatMemory = new InMemoryChatMemory();

    // 构造函数,通过依赖注入获取ChatClient.Builder、McpSyncClients和ToolCallbackProvider
    public ChatController(ChatClient.Builder chatClientBuilder, 
                          List<McpSyncClient> mcpSyncClients, 
                          ToolCallbackProvider tools) {
        this.chatClient = chatClientBuilder
        // 使用defaultTools方法注入工具,与之前版本API不同
       .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) {
        // 设置响应编码为UTF-8
        response.setCharacterEncoding("UTF-8");
        // 创建消息聊天记忆顾问,用于管理聊天记忆
        var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
        // 调用ChatClient的prompt方法,生成聊天响应流
        return this.chatClient.prompt(prompt)
       .advisors(messageChatMemoryAdvisor)
       .stream()
       .chatResponse();
    }
}

对比1.0.0 – M5版本的客户端实现,现在不用自己定义McpClient了,引入spring - ai - mcp - client - spring - boot - starter后,会自动识别配置的mcpClient信息并自动装配。而且创建ChatClient用的API也从defaultFunctions变成了defaultTools

要是想配置超时时间,可以实现SpringAI提供的McpSyncClientCustomizer接口,在里面写定制逻辑就行。
最后,先启动Mcp – Server,再启动Mcp – client应用程序,就可以开始提问了。实际效果如下:
在上图中大模型通过了McpClient调用了我在Mcp-Server提供了BookService,然后按照名字查询到了对应的书籍,这些书籍信息是我在代码中预制的信息,大模型输出这个信息也很好的证明了确实调用到了我们的Mcp服务端接口。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/ai/15988.html
喜欢 (1)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】

(2) 个小伙伴在畅所欲言
  1. 用户头像
    大佬,这个可以提供一下项目的代码吗
    骑在猪上的勇士 2025-03-22 16:36 回复
    • 潘老师
      原作者已将代码放置:https://github.com/AHUCodingBeast/spring-ai-mcp-demo/tree/main,更具体参考原文链接:https://juejin.cn/post/7483127098352877579
      潘老师 2025-03-22 16:39 回复