如何用Spring AI构建MCP Client-Server架构

现代 Web 应用正加速与大语言模型(LLMs)深度融合,构建超越传统问答场景的智能解决方案。为突破模型知识边界,增强上下文理解能力,开发者普遍采用多源数据集成策略,将 LLM 与搜索引擎、数据库、文件系统等外部资源互联。然而,异构数据源的协议差异与格式壁垒,往往导致集成复杂度激增,成为制约 AI 应用规模化落地的关键瓶颈。因此,Anthropic公司推出了模型上下文协议(Model Context Protocol, MCP),通过标准化接口为 AI 应用与外部数据源建立统一交互通道。这一协议体系不仅实现了数据获取与操作的规范化,更构建起可扩展的智能体开发框架,使开发者能够基于原生 LLM 能力快速构建复杂工作流。

本文我们就来尝试通过 Spring AI 框架来构建MCP 客户端 - 服务器架构的实现方法。

什么是MCP

关于MCP的架构,这里可以看看ByteByteGo的这张架构图:

null

MCP遵循客户端 - 服务器架构,围绕几个关键组件:

  • MCP Host:用户使用的应用程序,比如:Claude客户端、Cursor这样的AI应用程序,它与大语言模型集成,提供 AI 交互环境以访问不同工具和数据源。
  • MCP Client:与MCP Server建立并维护一对一连接的组件。它属于AI应用程序的内部组件,使其能够与 MCP Server通信。例如,若需要 PostgreSQL 数据,MCP 客户端会将请求格式化为结构化消息发送给 MCP 服务器。
  • MCP Server:外部数据源集成并公开与之交互功能的组件。作为中间件连接 AI 模型与外部系统(如 PostgreSQL、Google Drive 或 API)。例如,当 Claude 分析 PostgreSQL 中的销售数据时,PostgreSQL 的 MCP 服务器会充当 Claude 与数据库之间的连接器。

下面我们就参考Spring AI的官方文档,来尝试应用简单的MCP,想要了解更多,读者可以点击链接查看官方文档。

创建MCP Host

下面我们将使用Anthropic的Claude模型构建一个聊天机器人,该模型将充当我们的MCP Host。

引入相关依赖

首先,将必要的依赖项添加到项目的pom.xml文件中:

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
    <version>1.0.0-M6</version> 
</dependency> 
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
    <version>1.0.0-M6</version> 
</dependency>

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository> 
</repositories>
  • • 下面的案例使用Anthropic的Claude模型,所以使用spring-ai-anthropic-spring-boot-starter,如果你使用其他模型,也可以使用其他启动器依赖项,比如:使用deepseek的话也可以参考之前的《Spring AI + Ollama 实现 deepseek的API服务和调用》去引入相关链接和调用实现。
  • spring-ai-mcp-client-spring-boot-starter是引入MCP的重点,用来实现将Spring Boot应用程序与MCP服务器保持一对一连接的客户端。
  • • 由于Spring AI M6是一个里程碑版本,所以增加了相关repository的配置

接下来,在application.yaml文件中配置调用大模型的密钥和模型名称:

yaml 复制代码
spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-3-7-sonnet-20250219
  • • 使用${}属性占位符从环境变量中加载API密钥的值。
  • • 模型使用claude-3-7-sonnet-20250219, 你也可以根据需要使用其他模型。

配置上述属性后,Spring AI会自动创建一个ChatModel类型的bean,使我们能够与指定的模型进行交互。

为Brave Search和文件系统服务器配置MCP Client

现在,让我们为两个预构建的MCP服务器实现(Brave Search和文件系统)配置MCP客户端。这些服务器将使我们的聊天机器人能够执行网络搜索和文件系统操作。

首先,在application.yaml文件中为Brave Search MCP Server注册一个MCP Client:

yaml 复制代码
spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            brave-search:
              command: npx
              args:
                - "-y"
                - "@modelcontextprotocol/server-brave-search"
              env:
                BRAVE_API_KEY: ${BRAVE_API_KEY}

在这里,我们配置了一个使用stdio传输的客户端,使用npx命令来下载并运行@modelcontextprotocol/server-brave-search,并使用-y标志自动确认所有安装提示。

接下来,为文件系统MCP Server配置一个MCP Client:

yaml 复制代码
spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            filesystem:
              command: npx
              args:
                - "-y"
                - "@modelcontextprotocol/server-filesystem"
                - "./"

与之前的配置类似,我们通过命令行参数启动文件系统MCP服务器。该配置允许机器人在指定目录执行文件创建、读写操作(默认当前目录,可通过参数扩展多目录)。Spring AI启动时会自动扫描配置,创建MCP客户端连接服务器,并生成包含所有可用工具的SyncMcpToolCallbackProvider Bean。

构建简单的聊天机器人

配置好人工智能模型和MCP Client后,让我们构建一个简单的聊天机器人:

scss 复制代码
@Bean
ChatClient chatClient(ChatModel chatModel, SyncMcpToolCallbackProvider toolCallbackProvider) {
    return ChatClient
          .builder(chatModel)
          .defaultTools(toolCallbackProvider.getToolCallbacks())
          .build();
}

我们首先使用ChatModelSyncMcpToolCallbackProvider创建一个ChatClient,它将作为我们与模型交互的主要入口点。

接下来,创建一个新的ChatbotService类,chat()方法将用户的问题传递给聊天客户端bean,并简单返回人工智能模型的响应。:

scss 复制代码
String chat(String question) {
    return chatClient
          .prompt()
          .user(question)
          .call()
          .content();
}

再创建一个Controller,暴露一个REST API来实现聊天的交互:

less 复制代码
@PostMapping("/chat")
ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest chatRequest) {
    String answer = chatbotService.chat(chatRequest.question());
    return ResponseEntity.ok(new ChatResponse(answer));
}

record ChatRequest(String question) {}

record ChatResponse(String answer) {}

构建 MCP Server

除了使用预构建的MCP服务器外,我们还可以创建自己的MCP服务器,用我们的业务逻辑扩展聊天机器人的功能。下面我们创建一个新的Spring Boot应用程序来尝试构建一个简单的MCP Server

引入相关依赖

首先,在pom.xml文件中包含必要的依赖项:

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
    <version>1.0.0-M6</version> 
</dependency>

定义并暴露自定义工具

接下来,定义一些我们的MCP服务器将暴露的自定义工具。

我们创建一个AuthorRepository类,该类提供获取作者详细信息的方法:

typescript 复制代码
class AuthorRepository {

    @Tool(description = "Get Baeldung author details using an article title")
    Author getAuthorByArticleTitle(String articleTitle) {
        return new Author("John Doe", "[email protected]");
    }

    @Tool(description = "Get highest rated Baeldung authors")
    List<Author> getTopAuthors() {
        return List.of(
          new Author("John Doe", "[email protected]"),
          new Author("Jane Doe", "[email protected]")
        );
    }

    record Author(String name, String email) {
    }
}

这里使用@Tool注解对两个方法进行注解,并为每个方法提供简要描述。该描述有助于人工智能模型根据用户输入决定是否调用以及何时调用这些工具,并将结果纳入其响应中。

为了演示,这里直接硬编码返回信息,在实际应用程序中,这些工具通常会与数据库或外部API进行交互。

接下来,向MCP Server注册我们的作者工具:

scss 复制代码
@Bean
ToolCallbackProvider authorTools() {
    return MethodToolCallbackProvider
          .builder()
          .toolObjects(new AuthorRepository())
          .build();
}

我们使用MethodToolCallbackProvider从AuthorRepository类中定义的工具创建一个ToolCallbackProvider。应用程序启动时,用@Tool注解的方法将作为MCP工具暴露出来。

构建 MCP Client

最后,为了在聊天机器人应用程序中使用我们的自定义MCP服务器,我们需要针对它配置一个MCP客户端:

yaml 复制代码
spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            author-tools-server:
              url: http://localhost:8081

application.yaml文件中,我们针对自定义MCP服务器配置了一个新客户端。请注意,这里我们使用的是sse传输类型。

此配置假设MCP服务器在http://localhost:8081上运行,如果它在不同的主机或端口上运行,请确保更新url。

通过此配置,我们的MCP客户端现在除了可以调用Brave Search和文件系统MCP服务器提供的工具外,还可以调用我们的自定义服务器暴露的工具。

测试聊天效果

现在我们已经构建了聊天机器人并将其与各种MCP Server集成,让我们与它进行交互并进行测试。

我们将使用HTTPie CLI调用聊天机器人的API端点:

ini 复制代码
http POST :8080/chat question="How much was Elon Musk's initial offer to buy OpenAI in 2025?"

在这里,我们向聊天机器人发送一个关于大语言模型知识截止日期之后发生的事件的简单问题。让我们看看得到的响应是什么:

bash 复制代码
{
    "answer": "Elon Musk's initial offer to buy OpenAI was $97.4 billion. [Source](https://www.reuters.com/technology/openai-board-rejects-musks-974-billion-offer-2025-02-14/)." 
}

正如我们所见,聊天机器人能够使用配置的Brave Search MCP服务器执行网络搜索,并提供准确的答案以及来源。

接下来,让我们验证聊天机器人是否可以使用文件系统MCP服务器执行文件系统操作:

ini 复制代码
http POST :8080/chat question="Create a text file named 'mcp-demo.txt' with content 'This is awesome!'."

我们指示聊天机器人创建一个名为mcp-demo.txt的文件,并包含特定内容。让我们看看它是否能够完成请求:

json 复制代码
{
    "answer": "The text file named 'mcp-demo.txt' has been successfully created with the content you specified." 
}

聊天机器人给出了成功的响应。我们可以验证文件是否在我们在application.yaml文件中指定的目录中创建。

最后,让我们验证聊天机器人是否可以调用我们的自定义MCP服务器暴露的工具之一。我们将通过提及文章标题来询问作者详细信息:

ini 复制代码
http POST :8080/chat question="Who wrote the article 'Testing CORS in Spring Boot?' on Baeldung, and how can I contact them?"

让我们调用API,看看聊天机器人的响应是否包含硬编码的作者详细信息:

less 复制代码
{
    "answer": "The article 'Testing CORS in Spring Boot' on Baeldung was written by John Doe. You can contact him via email at [[email protected]](mailto:[email protected])." 
}

上述响应验证了聊天机器人使用我们的自定义MCP服务器暴露的getAuthorByArticleTitle()工具获取了作者详细信息。

结论

在本文中,我们探索了模型上下文协议,并使用Spring AI实现了MCP的Client-Server架构,就从Client-Server这一侧其实也挺简单的吧,核心难的其实还是能力的提供方,也就是Server段能调用到多少能力,往往一些专业的能力是由专业软件提供的,这就需要对方有API的支持,然后通过MCP协议来调用。

最后,灵魂一问,你目前有在AI应用中使用MCP Server吗?把你觉得好用的MCP Server在评论推荐一波吧!

相关推荐
一恍过去2 小时前
SpringBoot通过Map实现天然的策略模式
spring boot·后端·策略模式
uhakadotcom3 小时前
分布式协调服务:ZooKeeper入门指南
后端·面试·github
uhakadotcom3 小时前
ClickHouse Keeper:分布式协调的新时代
后端·面试·github
uhakadotcom3 小时前
分布式锁的基础知识与应用场景
后端·面试·github
GoGeekBaird3 小时前
69天探索操作系统-第57天:现代操作系统的容错性:原理与实践
后端·操作系统
爱掉发的小李4 小时前
使用python爬取网络资源
开发语言·后端·python·动态规划
霍珵璁4 小时前
Elixir语言的测试开发
开发语言·后端·golang
LCY1335 小时前
redis+spring 的基本操作 RedisTemplate和RedisClient
redis·spring·微信
网络风云5 小时前
Flask(三)路由与视图函数
后端·python·flask
Asthenia04125 小时前
Java 线程的状态转换 / 操作系统线程状态转换 / 线程上下文切换详解 / 如何避免线程切换
后端