使用 Spring AI 探索模型上下文协议MCP

在本文中,我们探讨了模型上下文协议并使用 Spring AI 实现了其客户端-服务器架构。

首先,我们使用 Anthropic 的 Claude 3.5 Sonnet 模型构建了一个简单的聊天机器人作为我们的 MCP 主机。

然后,为了让我们的聊天机器人具有网络搜索功能并使其能够执行文件系统操作,我们根据 Brave Search API 和文件系统的预构建 MCP 服务器实现配置了 MCP 客户端。

最后,我们创建了一个自定义 MCP 服务器,并在我们的 MCP 主机应用程序内配置了其相应的 MCP 客户端。

现代网络应用程序越来越多地与大型语言模型 (LLM)相结合来构建解决方案,而这些解决方案不仅限于基于一般知识的问答。

为了增强 AI 模型的响应能力并使其更能感知环境,我们可以将其连接到搜索引擎、数据库和文件系统等外部源。但是,集成和管理具有不同格式和协议的多个数据源是一项挑战。

Anthropic推出的模型上下文协议 (MCP)解决了这一集成挑战,并提供了一种将 AI 驱动的应用程序与外部数据源连接起来的标准化方法。通过 MCP,我们可以在原生 LLM 之上构建复杂的代理和工作流。

在本教程中,我们将通过使用 Spring AI 实际实现其客户端-服务器架构来理解 MCP 的概念。我们将创建一个简单的聊天机器人,并通过 MCP 服务器扩展其功能以执行 Web 搜索、执行文件系统操作和访问自定义业务逻辑。

模型上下文协议 101
在深入实现之前,让我们仔细看看 MCP 及其各个组件:

模型上下文协议 (MCP) 的架构图展示了主机、客户端、服务器和外部源之间的关系。
MCP 遵循客户端-服务器架构,围绕几个关键组件:

  • MCP Host:是我们的主要应用程序,它与 LLM 集成并需要它与外部数据源连接
  • MCP 客户端:是与 MCP 服务器建立并维持 1:1 连接的组件
  • MCP 服务器:是与外部数据源集成并公开与其交互功能的组件
工具:指 MCP 服务器向客户端开放的可执行函数/方法

此外,为了处理客户端和服务器之间的通信,MCP 提供了两个传输通道。

为了通过标准输入和输出流与本地进程和命令行工具进行通信,它提供了标准输入/输出 (stdio) 传输类型。或者,对于客户端和服务器之间基于 HTTP 的通信,它提供了服务器发送事件 (SSE) 传输类型。

MCP 是一个复杂而庞大的主题,请参阅官方文档以了解更多信息。

创建 MCP 主机
现在我们已经对 MCP 有了高层次的了解,让我们开始实际实现 MCP 架构。

我们将使用Anthropic 的 Claude模型构建一个聊天机器人,它将充当我们的 MCP 主机。或者,我们可以通过Hugging Face 或 Ollama使用本地 LLM ,因为特定的 AI 模型与此演示无关。

依赖项
让我们首先向项目的pom.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>

Anthropic启动器依赖项是Anthropic Message API 的包装器,我们将使用它在我们的应用程序中与 Claude 模型进行交互。

此外,我们导入了MCP 客户端启动器依赖项,这将允许我们在 Spring Boot 应用程序内配置与 MCP 服务器保持 1:1 连接的客户端。

由于当前版本1.0.0-M6是一个里程碑版本,我们还需要将 Spring Milestones 存储库添加到我们的pom.xml中:

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

此存储库是发布里程碑版本的地方,与标准 Maven Central 存储库不同。

鉴于我们在项目中使用了多个 Spring AI 启动器,我们还将在pom.xml中包含Spring AI 物料清单 (BOM):

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0-M6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

通过此添加,我们现在可以从两个启动依赖项中删除版本标签。 BOM 消除了版本冲突的风险,并确保我们的 Spring AI 依赖项彼此兼容。

接下来,让我们在application.yaml文件中配置我们的Anthropic API 密钥和聊天模型:

spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-3-5-sonnet-20241022

我们使用${}属性占位符从环境变量中加载 API 密钥的值。

此外,我们指定了Anthropic 最智能的模型Claude 3.5 Sonnet,使用claude-3-5-sonnet-20241022模型 ID。您可以根据需要随意探索和使用其他模型。

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

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

让我们首先在application.yaml文件中为 Brave 搜索 MCP 服务器注册一个 MCP 客户端:

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命令来下载并运行基于 TypeScript 的@modelcontextprotocol/server-brave-search包,并使用-y标志确认所有安装提示。

此外,我们还提供BRAVE_API_KEY作为环境变量。

接下来,让我们为文件系统 MCP 服务器配置一个 MCP 客户端:

spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            filesystem:
              command: npx
              args:
                - "-y"
                -
"@modelcontextprotocol/server-filesystem"
                -
"./"

与前面的配置类似,我们指定运行Filesystem MCP 服务器包所需的命令和参数。此设置允许我们的聊天机器人执行在指定目录中创建、读取和写入文件等操作。

这里,我们只配置当前目录(./)用于文件系统操作,但是,我们可以通过将多个目录添加到args列表来指定多个目录。

在应用程序启动期间,Spring AI 将扫描我们的配置,创建 MCP 客户端,并与相应的 MCP 服务器建立连接。它还会创建一个SyncMcpToolCallbackProvider类型的 bean ,该 bean 提供已配置的 MCP 服务器公开的所有工具的列表。

构建基本聊天机器人
配置好 AI 模型和 MCP 客户端后,让我们构建一个简单的聊天机器人:

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

我们首先使用 ChatModel和SyncMcpToolCallbackProvider bean创建ChatClient类型的 bean。ChatClient类将作为我们与聊天完成模型(即 Claude 3.5 Sonnet)交互的主要入口点。

接下来,让我们注入ChatClient bean 来创建一个新的ChatbotService类:

String chat(String question) {
    return chatClient
      .prompt()
      .user(question)
      .call()
      .content();
}

我们创建一个chat()方法,将用户的问题传递 给聊天客户端 bean,并简单地返回 AI 模型的响应。

现在我们已经实现了服务层,让我们在其上公开一个 REST API:

@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) {}

在本教程的后面我们将使用上述 API 端点与我们的聊天机器人进行交互。

创建自定义 MCP 服务器
除了使用预先构建的 MCP 服务器之外,我们还可以创建自己的 MCP 服务器,以使用我们的业务逻辑扩展聊天机器人的功能。

让我们探索如何使用 Spring AI 创建自定义 MCP 服务器。

我们将在本节中创建一个新的 Spring Boot 应用程序。

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

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

我们导入Spring AI 的 MCP 服务器依赖项,它提供了创建支持基于 HTTP 的 SSE 传输的自定义 MCP 服务器所需的类。

定义和公开自定义工具
接下来,让我们定义一些 MCP 服务器将公开的自定义工具。

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

class AuthorRepository {
    @Tool(description = "Get Baeldung author details using an article title")
    Author getAuthorByArticleTitle(String articleTitle) {
        return new Author(
"John Doe", "john.doe@baeldung.com");
    }
    @Tool(description =
"Get highest rated Baeldung authors")
    List<Author> getTopAuthors() {
        return List.of(
          new Author(
"John Doe", "john.doe@baeldung.com"),
          new Author(
"Jane Doe", "jane.doe@baeldung.com")
        );
    }
    record Author(String name, String email) {
    }
}

为了演示,我们返回硬编码的作者详细信息,但在实际应用程序中,这些工具通常会与数据库或外部 API 交互。

我们用@Tool注释注释了这两个方法,并为它们各自提供了简短的描述。该描述可帮助 AI 模型根据用户输入决定是否以及何时调用工具,并将结果纳入其响应中。

接下来,让我们将我们的创作工具注册到 MCP 服务器上:

@Bean
ToolCallbackProvider authorTools() {
    return MethodToolCallbackProvider
      .builder()
      .toolObjects(new AuthorRepository())
      .build();
}

我们使用MethodToolCallbackProvider从AuthorRepository类中定义的工具创建ToolCallbackProvider bean 。使用@Tool注释的方法将在应用程序启动时作为 MCP 工具公开。

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

spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            author-tools-server:
              url: http://localhost:8081

在application.yaml文件中,我们针对自定义 MCP 服务器配置新客户端。请注意,我们在此处使用sse传输类型。

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

通过此配置,除了 Brave Search 和 Filesystem MCP 服务器提供的工具之外,我们的 MCP 客户端现在还可以调用我们自定义服务器公开的工具。

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

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

http POST :8080/chat question="How much was Elon Musk's initial offer to buy OpenAI in 2025?"
在这里,我们向聊天机器人发送一个简单的问题,询问 LLM知识截止日期之后发生的事件。让我们看看我们得到的答复:

{
    "answer": "Elon Musk's initial offer to buy OpenAI was $97.4 billion. <a href="https://www.reuters.com/technology/openai-board-rejects-musks-974-billion-offer-2025-02-14/">Source</a>."
}

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

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

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

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

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

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

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

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

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

{
    "answer": "The article 'Testing CORS in Spring Boot' on Baeldung was written by John Doe. You can contact him via email at <a href="mailto:john.doe@baeldung.com">john.doe@baeldung.com</a>."
}

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

我们强烈建议在本地设置代码库并使用不同的提示来尝试聊天机器人。