6 - MCP 协议 - AI 超级智能体项目教程

106 阅读30分钟

我们团队最近在疯狂的研究AI,期间调研学习了大量的资料,感谢大佬们的分享。

期间不仅做了几个还不错的项目,也踩坑不少,我们也发光发热,把我们总结的经验以专栏的方式分享出来,希望对大家有帮助。

这是专栏内容的第6篇,这是专栏链接,没看之前文章的朋友,建议先看之前的内容。

本节重点

学习 AI 应用开发的高级特性 —— MCP 模型上下文协议,打通 AI 与外部服务的边界。先学习 MCP 的⁠几种使用方式,然后基于 Spring AI 框架实战开发 MCP 客户端与服务端,帮你掌握 MCP 的架构原理和最佳实践。

具体内容包括:

  • MCP 必知必会
  • MCP 的 3 种使用方式
  • Spring AI MCP 开发模式
  • Spring AI MCP 开发实战 - 图片搜索 MCP
  • MCP 开发最佳实践
  • MCP 部署方案
  • MCP 安全问题

友情提示:由于 AI 的更新速度飞快,随着平台 / 工具 / 技术 / 软件的更新,教程的部分细节可能会失效,所以请⁠大家重点学习思路和方法,不要因为实操和教程不一致就过于担心,而是要学会自己阅读官方文档并查阅资料,多锻炼自己解决问题的能力。

一、MCP 必知必会

什么是 MCP?

MCP(Model Context Protocol,模型上下文协议)是一种开放标准,目的是增强 AI 与外部系统的交互⁠能力。MCP 为 AI 提供了与外部工具、资源和服务交互的标准化方式,让 AI 能够访问最新数据、执行复杂操作,并与现有系统集成。

根据 官方定义,MCP 是一种开放协议,它标准化了应用程序如何向大模型提供上下文的方式。可以将 MCP 想象成 AI 应用的 USB 接口。就像 USB 为设备连接各种外设和配件提供了标准化方式一样,MCP 为 AI 模型连接不同的数据源和工具提供了标准化的方法。

前面说的可能有些抽象,让我举些例子帮大家理解 MCP 的作用。首先是 增强 AI 的能力,通过 MCP 协议,AI 应用可以轻松接入别人提供的服务来实现更多功能,比如搜索网页、查询数据库、调用第三方 API、执行计算。

其次,我们一定要记住 MCP 它是个 协议 或者 标准,它本身并不提供什么服务,只是定义好了一套规范,让服务提供者和服务使用者去遵守。这样的好处显而易见,就像 HTTP 协议一样,现在前端向后端发送请求基本都是用 HTTP 协议,什么 get / post 请求类别、什么 401、404 状态码,这些标准能 有效降低开发者的理解成本

此外,标准化还有其他的好处。举个例子,以前我们想给 AI 增加查询地图的能力,需要自己开发工具来调用第三方地图 API;如果你有多个项目、或者其他开发者也需要做同样的能力,大家就要重复开发,就导致同样的功能做了多遍、每个人开发的质量和效⁠果也会有差别而如果官方把查询地图的能力直接做成一个服务,谁要用谁接入,不就省去了开发成本、并且效果一致了么?如果大家都陆续开放自己的服务,不就相当于打造了一个服务市场,造福广大开发者了么!

标准可以造就生态。 其实这并不新鲜了,前端同学可以想想 NPM 包,后端同学可以想想 Maven 仓库还有 Docker 镜像源,不懂编程的同学想想手机应用市场,应该就能理解了。

这就是 MCP 的三大作用:

  • 轻松增强 AI 的能力
  • 统一标准,降低使用和理解成本
  • 打造服务生态,造福广大开发者

MCP 架构

1、宏观架构

MCP 的核心是 “客户端 - 服务器” 架构,其中 MCP 客户端主机可以连接到多个服务器。客户端⁠主机是指希望访问 MCP 服务的程序,比如 Claude Desktop、IDE、AI 工具或部署在服务器上的项目。

2、SDK 3 层架构

如果我们要在程序中使用 MCP 或开发 MCP 服务,可以引入 MCP 官方的 SDK,比如 Java SDK。让我们先通过 MCP 官方文档了解 MCP SDK 的架构,主要分为 3 层

分别来看每一层的作用:

  • 客户端 / 服务器层:McpClient 处理客户端操作,而 McpServer 管理服务器端协议操作。两者都使用 McpSession 进行通信管理。
  • 会话层(McpSession):通过 DefaultMcpSession 实现管理通信模式和状态。
  • 传输层(McpTransport):处理 JSON-RPC 消息序列化和反序列化,支持多种传输实现,比如 Stdio 标准 IO 流传输和 HTTP SSE 远程传输。

客户端和服务端需要先经过下面的流程建立连接,之⁠后才能正常交换消息:

3、MCP 客户端

MCP Client 是 MCP 架构中的关键组件,主要负责和 MCP 服务器建立连接并进行通信。它能自动匹配服务器⁠的协议版本、确认可用功能、负责数据传输和 JSON-RPC 交互。此外,它还能发现和使用各种工具、管理资源、和提示词系统进行交互。

除了这些核心功能,MCP 客户端还支持一些额外特性,比如根管理、采⁠样控制,以及同步或异步操作。为了适应不同场景,它提供了多种数据传输方式,包括:

  • Stdio 标准输入 / 输出:适用于本地调用
  • 基于 Java HttpClient 和 WebFlux 的 SSE 传输:适用于远程调用

客户端可以通过不同传输方式调用不同的 MCP ⁠服务,可以是本地的、也可以是远程的。如图:

4、MCP 服务端

MCP Server 也是整个 MCP 架构的⁠关键组件,主要用来为客户端提供各种工具、资源和功能支持。

它负责处理客户端的请求,包括解析协议、提供工具、管理资源以及处理各种交互⁠信息。同时,它还能记录日志、发送通知,并且支持多个客户端同时连接,保证高效的通信和协作。

和客户端一样,它也可以通过多种方式进行数据传输,比如 Stdio 标准输入 / 输出、⁠基于 Servlet / WebFlux / WebMVC 的 SSE 传输,满足不同应用场景。

这种设计使得客户端和服务端完全解耦,任何语言开⁠发的客户端都可以调用 MCP 服务。如图:

MCP 核心概念

很多同学以为 MCP 协议就只能提供工具给别人⁠调用,但实际上,MCP 协议的本领可大着呢!

按照官方的说法,总共有 6 大核心概念。大家简单了解一下即可,除了 T⁠ools 工具之外的其他概念都不是很实用,如果要进一步学习可以阅读对应的官方文档。

  1. Resources 资源:让服务端向客户端提供各种数据,比如文本、文件、数据库记录、API 响应等,客户端可以决定什么时候使用这些资源。使 AI 能够访问最新信息和外部知识,为模型提供更丰富的上下文。
  2. Prompts 提示词:服务端可以定义可复用的提示词模板和工作流,供客户端和用户直接使用。它的作用是标准化常见的 AI 交互模式,比如能作为 UI 元素(如斜杠命令、快捷操作)呈现给用户,从而简化用户与 LLM 的交互过程。
  3. Tools 工具:MCP 中最实用的特性,服务端可以提供给客户端可调用的函数,使 AI 模型能够执行计算、查询信息或者和外部系统交互,极大扩展了 AI 的能力范围。
  4. Sampling 采样:允许服务端通过客户端向大模型发送生成内容的请求(反向请求)。使 MCP 服务能够实现复杂的智能代理行为,同时保持用户对整个过程的控制和数据隐私保护。
  5. Roots 根目录:MCP 协议的安全机制,定义了服务器可以访问的文件系统位置,限制访问范围,为 MCP 服务提供安全边界,防止恶意文件访问。
  6. Transports 传输:定义客户端和服务器间的通信方式,包括 Stdio(本地进程间通信)和 SSE(网络实时通信),确保不同环境下的可靠信息交换。

MCP 官方文档 中提到,大多数客户端也只支持 Tools 工具调用能力:

所以接下来我们学习使用和开发 MCP 的过程中⁠,只需关注 Tools 工具即可。

二、使用 MCP

本节我们将实战 3 种使用 MCP 的方式:

  • 云平台使用 MCP
  • 软件客户端使用 MCP
  • 程序中使用 MCP

无论是哪种使用方式,原理都是类似的,而且有 2 种可选的使用模式:本地下载 MCP 服务端代码并运行(类似引入了一个 SDK),或者 直接使用已部署的 MCP 服务(类似调用了别人的 API)。

到哪里去找别人开发的 MCP 服务呢?

MCP 服务大全

目前已经有很多 MCP 服务市场,开发者可以在⁠这些平台上找到各种现成的 MCP 服务:

其中,绝大多数 MCP 服务市场仅提供本地下载 MCP ⁠服务端代码并运行的使用方式,毕竟部署 MCP 服务也是需要成本的。

有些云服务平台提供了云端部署的 MCP 服务,比如阿里云百炼平台,在线填写配置后⁠就能用,可以轻松和平台上的 AI 应用集成。但一般局限性也比较大,不太能直接在自己的代码中使用。

下面来学习 3 种使用 MCP 的方式。

云平台使用 MCP

以阿里云百炼为例,参考 官方 MCP 文档,我们可以直接使用官方预置的 MCP 服务,或者部署自己的 MCP 服务到阿里云平台上。

如图,官方提供了很多现成的 MCP 服务:

让我们进入一个智能体应用,在左侧可以点击添加 MCP 服务,然后选择想要使用⁠的 MCP 服务即可,比如使用高德地图 MCP 服务,提供地理信息查询等 12 个工具。

测试一下,输入 Prompt:我现在居住在⁠北京三里桥,请帮我找到8公里内合适的通勤上班的科技公司。

发现 AI 自动调用了 MCP 提供的多个工具⁠,给出了不错的回答:

AI 会根据需要调用不同的工具,比如将地点转为⁠坐标、查找某坐标附近的地点:

调用工具完成后,AI 会利用工具的输出结果进一步分析并生成⁠回复。这个流程是不是很像工具调用(Tool Calling)?

软件客户端使用 MCP

不同的客户端软件对 MCP 支持程度不同,可以在 官方文档 中查看各客户端支持的特性。

下面我们以主流 AI 客户端 Cursor 为例,演示如何使用 M⁠CP 服务。由于没有现成的部署了 MCP 服务的服务器,我们采用本地运行的方式。

1、环境准备

首先安装本地运行 MCP 服务需要用到的工具,⁠具体安装什么工具取决于 MCP 服务的配置要求。

比如我们到 MCP 市场 找到 高德地图 MCP,发现 Server Config 中定义了使用 npx 命令行工具来安装和运行服务端代码:

大多数 MCP 服务都支持基于 NPX 工具运行,所以推荐安装 Node.js 和 NPX,去 官网 傻瓜式安装即可。

从配置中我们发现,使用地图 MCP 需要 API Key,我们可以到 地图开放平台 创建应用并添加 API Key:

2、Cursor 接入 MCP

在右上角进入 Cursor Settings ⁠设置界面,然后选择 MCP,添加全局的 MCP Server:

接下来从 MCP 市场中找到 MCP Server Config,并粘贴到 mcp.json 配置中,注意要将 API Key 更改为自己的:

保存配置,软件会自动识别并启动服务,效果如图:

3、测试使用 MCP

接下来就可以使用 MCP 服务了,还是提供之前的 Promp⁠t:我现在居住在⁠北京三里桥,请帮我找到8公里内合适的通勤上班的科技公司。

观察效果,发现 AI 可能会多次调用 MCP

但是这也让我们意识到使用 MCP 服务的代价 —— 由于调用次数不稳定,可能产生较高的 AI 和 API 调用费用,所以一般我的建议是 能不用就不用

如果要使用其他软件客户端,接入 MCP 的方法也是⁠类似的,可以直接看软件官方(或 MCP 官方)提供的接入文档,比如:

程序中使用 MCP

让我们利用 Spring AI 框架,在程序中使用 MCP ⁠并完成我们的需求,实现一个能够根据另一半的位置推荐约会地点的 AI 助手。

💡 类似的 Java MCP 开发框架还有 Solon AI MCP,但由于我们更多地使用 Spring 生态,所以还是推荐使用 Spring AI 框架。

首先了解 Spring AI MCP 客户端的基本使用方法。建议参考 Spring AI Alibaba 的文档,因为 Spring AI 官方文档 更新的太快了,包的路径可能会变动。

1)在 Maven 中央仓库 中可以找到正确的依赖,引入到项目中:

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

2)在 resources 目录下新建 mcp-servers.json 配置,定义需要用到的 MCP 服务:

{
  "mcpServers": {
    "amap-maps": {
      "command": "npx.cmd",
      "args": [
        "-y",
        "@amap/amap-maps-mcp-server"
      ],
      "env": {
        "AMAP_MAPS_API_KEY": "你的key"
      }
    }
  }
}

💡 特别注意:在 Windows 环境下,命令配置需要添加 .cmd 后缀(如 npx.cmd),否则会报找不到命令的错误。

3)修改 Spring 配置文件,编写 MCP 客户端配置。由于是本地运行 ⁠MCP 服务,所以使用 stdio 模式,并且要指定 MCP 服务配置文件的位置。代码如下:

spring:
    ai:
      mcp:
        client:
            stdio:
                servers-configuration:classpath:mcp-servers.json

这样一来,MCP 客户端程序启动时,会额外启动⁠一个子进程来运行 MCP 服务,从而能够实现调用。

4)修改 InterviewAPP 的代码,新增一个利用 MCP 完成对话的方法。通过自动注入的 ToolCallbackProvider 获取到配置中定义的 MCP 服务提供的 所有工具,并提供给 ChatClient。代码如下:

@Resourceprivate ToolCallbackProvider toolCallbackProvider;

public String doChatWithMcp(String message, String chatId) {
    ChatResponse response = chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
            // 开启日志,便于观察效果
            .advisors(new MyLoggerAdvisor())
            .tools(toolCallbackProvider)
            .call()
            .chatResponse();
    String content = response.getResult().getOutput().getText();
    log.info("content: {}", content);
    return content;
}

从这段代码我们能够看出,MCP 调用的本质就是类似工具调用,并不是让 AI 服务器主动去调用 MCP 服务,而是告诉 AI “MCP 服务提供了哪些工具”,如果 AI 想要使用这些工具完成任务,就会告诉我们的后端程序,后端程序在执行工具后将结果返回给 AI,最后由 AI 总结并回复。

5)测试运行。编写单元测试代码:

@Testvoid doChatWithMcp() {
    String chatId = UUID.randomUUID().toString();
    // 测试地图 MCPString message = "我现在居住在⁠北京三里桥,请帮我找到8公里内合适的通勤上班的科技公司。";
    String answer =  InterviewAPP.doChatWithMcp(message, chatId);
}

运行效果如图所示,可以看到 functionC⁠allbacks 中加载了多个地图 MCP 提供的工具:

可以在地图开放平台的控制台查看 API Key⁠ 的使用量,注意控制调用次数避免超出限额:

三、Spring AI MCP 开发模式

Spring AI 在 MCP 官方 Java SDK 的基础上额外封装了一层,提供了和 Spring Boot 整⁠合的 SDK,支持客户端和服务端的普通调用和响应式调用。下面分别学习如何使用 Spring AI 开发 MCP 客户端和服务端。

MCP 客户端开发

客户端开发主要基于 Spring AI MCP Client Boot Starter,能够自动完成客户端的初始化、管理多个客户端实例、自动清理资源等。

1、引入依赖

Spring AI 提供了 2 种客户端 SDK,⁠分别支持非响应式和响应式编程,可以根据需要选择对应的依赖包:9miz/

  • spring-ai-starter-mcp-client:核心启动器,提供 STDIO 和基于 HTTP 的 SSE 支持
  • spring-ai-starter-mcp-client-webflux:基于 WebFlux 响应式的 SSE 传输实现

比如下面的依赖(具体的依赖名称以官方文档为准):

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

2、配置连接

引入依赖后,需要配置与服务器的连接,Spring AI 支持⁠两种配置方式:                                

1)直接写入配置文件,这种方式同时支持 std⁠io 和 SSE 连接方式。

spring:
  ai:
    mcp:
      client:
        enabled: true
        name: my-mcp-client
        version: 1.0.0
        request-timeout: 30s
        type: SYNC
        sse:
          connections:
            server1:
              url: http://localhost:8080
        stdio:
          connections:
            server1:
              command: /path/to/server
              args:
                - --port=8080
              env:
                API_KEY: your-api-key

先了解上面这些配置即可,更多配置属性可参考 官方文档

2)引用 Claude Desktop 格式 的 JSON 文件,目前仅支持 stdio 连接方式。

spring:
  ai:
    mcp:
      client:
        stdio:
          servers-configuration: classpath:mcp-servers.json

配置文件格式如下:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/username/Desktop",
        "/Users/username/Downloads"
      ]
    }
  }
}

3、使用服务

启动项目时,Spring AI 会自动注入一些⁠ MCP 相关的 Bean。

1)如果你想完全自主控制 MCP 客户端的行为⁠,可以使用 McpClient Bean,支持同步和异步:

// 同步客户端
@Autowired
private List<McpSyncClient> mcpSyncClients;

// 异步客户端
@Autowired
private List<McpAsyncClient> mcpAsyncClients;

查看 McpSyncClient 的源码,发现提供⁠了很多和 MCP 服务端交互的方法,比如获取工具信息、调用工具等等

需要注意的是,每个 MCP 服务连接都会创建一⁠个独立的客户端实例。

2)如果你想利用 MCP 服务提供的工具来增强 AI 的能力,可以使用自动注入的 ToolCallbackProvider Bean,从中获取到 ToolCallback 工具对象。

// 和 Spring AI 的工具进行整合
@Autowired
private SyncMcpToolCallbackProvider toolCallbackProvider;
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();

然后绑定给 ChatClient 对象即可:

ChatResponse response = chatClient
        .prompt()
        .user(message)
        .tools(toolCallbackProvider)
        .call()
        .chatResponse();

4、其他特性

1)Spring AI 同时支持 同步和异步客户端类型,可根据应用需求选择合适的模式,只需要更改配置即可:

spring.ai.mcp.client.type=ASYNC

2)开发者还可以通过编写自定义 Client Bean 来 定制客户端行为,比如设置请求超时时间、设置文件系统根目录的访问范围、自定义事件处理器、添加特定的日志处理逻辑。rxUlXocL6iI0ChnjjDcUaJVycix5vlr0Z9nXRSlePXA=

官方提供的示例代码如下,简单了解即可:

@Component
public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {
    @Overridepublic void customize(String serverConfigurationName, McpClient.SyncSpec spec) {
        // 自定义请求超时配置
        spec.requestTimeout(Duration.ofSeconds(30));
        
        // 设置此客户端可访问的根目录URI
        spec.roots(roots);
        
        // 设置处理消息创建请求的自定义采样处理器
        spec.sampling((CreateMessageRequest messageRequest) -> {
            // 处理采样CreateMessageResult result = ...
            return result;
        });

        // 添加在可用工具变更时通知的消费者
        spec.toolsChangeConsumer((List<McpSchema.Tool> tools) -> {
            // 处理工具变更
        });

        // 添加在可用资源变更时通知的消费者
        spec.resourcesChangeConsumer((List<McpSchema.Resource> resources) -> {
            // 处理资源变更
        });

        // 添加在可用提示词变更时通知的消费者
        spec.promptsChangeConsumer((List<McpSchema.Prompt> prompts) -> {
            // 处理提示词变更
        });

        // 添加接收服务器日志消息时通知的消费者
        spec.loggingConsumer((McpSchema.LoggingMessageNotification log) -> {
            // 处理日志消息
        });
    }
}

MCP 服务端开发

服务端开发主要基于 Spring AI MCP Server Boot Starter,能够自动配置 MCP 服务端组件,使开发者能够轻松创建 MCP 服务,向 AI 客户端提供工具、资源和提示词模板,从而扩展 AI 模型的能力范围。

1、引入依赖

Spring AI 提供了 3 种 MCP 服务端 SD⁠K,分别支持非响应式和响应式编程,可以根据需要选择对应的依赖包:

  • spring-ai-starter-mcp-server:提供 stdio 传输支持,不需要额外的 web 依赖
  • spring-ai-starter-mcp-server-webmvc:提供基于 Spring MVC 的 SSE 传输和可选的 stdio 传输(一般建议引入这个)
  • spring-ai-starter-mcp-server-webflux:提供基于 Spring WebFlux 的响应式 SSE 传输和可选的 stdio 传输

比如下面的依赖(具体的依赖名称以官方文档为准):

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

2、配置服务

如果要开发 stdio 服务,配置如下:

# 使用 spring-ai-starter-mcp-server
spring:
  ai:
    mcp:
      server:
        name: stdio-mcp-server
        version: 1.0.0
        stdio: true
        type: SYNC # 同步

开发 SSE 服务,配置如下:

# 使用 spring-ai-starter-mcp-server-webmvc
spring:
  ai:
    mcp:
      server:
        name: webmvc-mcp-server
        version: 1.0.0
        type: SYNC # 同步
        sse-message-endpoint: /mcp/message  # SSE 消息端点路径
        sse-endpoint: /sse                  # SSE 端点路径

如果要开发响应式(异步)服务,配置如下:

# 使用 spring-ai-starter-mcp-server-webflux
spring:
  ai:
    mcp:
      server:
        name: webflux-mcp-server
        version: 1.0.0
        type: ASYNC  # 异步
        sse-message-endpoint: /mcp/messages # SSE 消息端点路径
        sse-endpoint: /sse                  # SSE 端点路径

还有更多可选配置,详细信息可参考 官方文档

spring:
  ai:
    mcp:
      server:
        enabled: true                # 启用/禁用 MCP 服务
        stdio: false                 # 启用/禁用 stdio 传输
        name: my-mcp-server          # 服务名称
        version: 1.0.0               # 服务版本
        type: SYNC                   # 服务类型(SYNC/ASYNC)
        resource-change-notification: true  # 启用资源变更通知
        prompt-change-notification: true    # 启用提示词变更通知
        tool-change-notification: true      # 启用工具变更通知
        sse-message-endpoint: /mcp/message  # SSE 消息端点路径
        sse-endpoint: /sse                  # SSE 端点路径
        # 可选 URL 前缀
        base-url: /api/v1           # 客户端访问路径将是/api/v1/sse 和 /api/v1/mcp/message

3、开发服务

无论采用哪种传输方式,开发 MCP 服务的过程都是类似的,跟开发工具调用一样,直接使用 @Tool 注解标记服务类中的方法。

@Service
public class WeatherService {
    @Tool(description = "获取指定城市的天气信息")
    public String getWeather(
            @ToolParameter(description = "城市名称,如北京、上海") String cityName) {
        // 实现天气查询逻辑
        return "城市" + cityName + "的天气是晴天,温度22°C";
    }
}

然后在 Spring Boot 项目启动时注册一个 ToolCallbackProvider Bean 即可:

@SpringBootApplication
public class McpServerApplication {
    @Beanpublic ToolCallbackProvider weatherTools(WeatherService weatherService) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherService)
                .build();
    }
}

4、其他特性

我们还可以利用 SDK 来开发 MCP 服务的⁠多种特性,比如:

1)提供工具

支持两种方式:

@Bean
public ToolCallbackProvider myTools(...) {
    List<ToolCallback> tools = ...
    return ToolCallbackProvider.from(tools);
}

@Bean
public List<McpServerFeatures.SyncToolSpecification> myTools(...) {
    List<McpServerFeatures.SyncToolSpecification> tools = ...
    return tools;
}

2)资源管理:可以给客户端提供静态文件或动态生成的内容

@Beanpublic List<McpServerFeatures.SyncResourceSpecification> myResources(...) {
    var systemInfoResource = new McpSchema.Resource(...);
    var resourceSpecification = new McpServerFeatures.SyncResourceSpecification(systemInfoResource, (exchange, request) -> {
        try {
            var systemInfo = Map.of(...);
            String jsonContent = new ObjectMapper().writeValueAsString(systemInfo);
            return new McpSchema.ReadResourceResult(
                    List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent)));
        }
        catch (Exception e) {
            throw new RuntimeException("Failed to generate system info", e);
        }
    });

    return List.of(resourceSpecification);
}

3)提示词管理:可以向客户端提供模板化的提示词

@Beanpublic List<McpServerFeatures.SyncPromptSpecification> myPrompts() {
    var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt",
        List.of(new McpSchema.PromptArgument("name", "The name to greet", true)));

    var promptSpecification = new McpServerFeatures.SyncPromptSpecification(prompt, (exchange, getPromptRequest) -> {
        String nameArgument = (String) getPromptRequest.arguments().get("name");
        if (nameArgument == null) { nameArgument = "friend"; }
        var userMessage = new PromptMessage(Role.USER, new TextContent("Hello " + nameArgument + "! How can I assist you today?"));
        return new GetPromptResult("A personalized greeting message", List.of(userMessage));
    });

    return List.of(promptSpecification);
}

4)根目录变更处理:当客户端的根目录权限发生变⁠化时,服务端可以接收通知

@Beanpublic BiConsumer<McpSyncServerExchange, List<McpSchema.Root>> rootsChangeHandler() {
    return (exchange, roots) -> {
        logger.info("Registering root resources: {}", roots);
    };
}

大家只需要了解上面这些特性即可,无需记忆和编写代码。通过这些特性,大家应该也会对 MCP ⁠有进一步的了解。简单来说,通过这套标准,服务端能向客户端传递各种各样不同类型的信息(资源、工具、提示词等)。

MCP 工具类

Spring AI 还提供了一系列 辅助 MCP 开发的工具类,用于 MCP 和 ToolCallback 之间的互相转换。

也就是说,开发者可以直接将之前开发的工具转换为⁠ MCP 服务,极大提高了代码复用性:

四、MCP 开发实战 - 图片搜索服务

下面我们将开发一个网络图片搜索 MCP 服务,⁠带大家快速掌握 MCP 开发。

MCP 服务端开发

可以使用 Pexels 图片资源网站的 API 来构建图片搜索服务。

1)首先在 Pexels 网站生成 API Key⁠:                                

2)在项目根目录下新建 module,名称为 da⁠yu-image-search-mcp-server:

注意,建议在新项目中 单独打开该模块,不要直接在原项目的子文件夹中操作,否则可能出现路径上的问题。

3)引入必要的依赖,包括 Lombok、hut⁠ool 工具库和 Spring AI MCP 服务端依赖。

有 Stdio、WebMVC SSE 和 WebFlux SSE 三种服务端依赖⁠可以选择,开发时只需要填写不同的配置,开发流程都是一样的。此处我们选择引入 WebMVC:

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

引入这个依赖后,会自动注册 SSE 端点,供客户端⁠调用。包括消息和 SSE 传输端点:

4)在 resources 目录下编写服务端配置文件。这⁠里我们编写两套配置方案,分别实现 stdio 和 SSE 模式的传输。

stdio 配置文件 application-stdio.yml(需关闭 web 支持):

spring:
  ai:
    mcp:
      server:
        name: dayu-image-search-mcp-server
        version: 0.0.1
        type: SYNC
        # stdio
        stdio: true
  # stdio
  main:
    web-application-type: none
    banner-mode: off

SSE 配置文件 application-sse.yml(需关闭 stdio 模式):

spring:
  ai:
    mcp:
      server:
        name: dayu-image-search-mcp-server
        version: 0.0.1
        type: SYNC
        # sse
        stdio: false

然后编写主配置文件 application.yml,可以灵活指定激活哪套配置:

spring:
  application:
    name: dayu-image-search-mcp-server
  profiles:
    active: stdio
server:
  port: 8127

5)编写图片搜索服务类,在 tools 包下新建 ImageSearchTool,使用 @Tool 注解标注方法,作为 MCP 服务提供的工具。

@Servicepublic class ImageSearchTool {

    // 替换为你的 Pexels API 密钥(需从官网申请)private static final String API_KEY = "你的 API Key";

    // Pexels 常规搜索接口(请以文档为准)private static final String API_URL = "https://api.pexels.com/v1/search";

    @Tool(description = "search image from web")public String searchImage(@ToolParam(description = "Search query keyword") String query) {
        try {
            return String.join(",", searchMediumImages(query));
        } catch (Exception e) {
            return "Error search image: " + e.getMessage();
        }
    }

    /**
     * 搜索中等尺寸的图片列表
     *
     * @param query
     * @return
     */public List<String> searchMediumImages(String query) {
        // 设置请求头(包含API密钥)
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", API_KEY);

        // 设置请求参数(仅包含query,可根据文档补充page、per_page等参数)
        Map<String, Object> params = new HashMap<>();
        params.put("query", query);

        // 发送 GET 请求String response = HttpUtil.createGet(API_URL)
                .addHeaders(headers)
                .form(params)
                .execute()
                .body();

        // 解析响应JSON(假设响应结构包含"photos"数组,每个元素包含"medium"字段)return JSONUtil.parseObj(response)
                .getJSONArray("photos")
                .stream()
                .map(photoObj -> (JSONObject) photoObj)
                .map(photoObj -> photoObj.getJSONObject("src"))
                .map(photo -> photo.getStr("medium"))
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toList());
    }
}

编写对应的单元测试类,先来验证工具是否可用:

@SpringBootTestclass ImageSearchToolTest {

    @Resourceprivate ImageSearchTool imageSearchTool;

    @Testvoid searchImage() {
        String result = imageSearchTool.searchImage("computer");
        Assertions.assertNotNull(result);
    }
}

测试结果如图,成功根据关键词搜索到了多张图片:

6)在主类中通过定义 ToolCallbackProvider Bean 来注册工具:

@SpringBootApplicationpublic class YuImageSearchMcpServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(YuImageSearchMcpServerApplication.class, args);
    }

    @Beanpublic ToolCallbackProvider imageSearchTools(ImageSearchTool imageSearchTool) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(imageSearchTool)
                .build();
    }
}

7)至此就开发完成了,最后使用 Maven Package 命令打包,⁠会在 target 目录下生成可执行的 JAR 包,等会儿客户端调用时会依赖这个文件。

客户端开发

接下来直接在根项目中开发客户端,调用刚才创建的⁠图片搜索服务。

1)先引入必要的 MCP 客户端依赖

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

当然,实际开发中,你也可以按需添加 WebFl⁠ux 支持,但要与服务端模式匹配。

2)先测试 stdio 传输方式。在 mcp-servers.json 配置文件中新增 MCP Server 的配置,通过 java 命令执行我们刚刚打包好的 jar 包。代码如下:

{
  "mcpServers": {
    "yu-image-search-mcp-server": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.stdio=true",
        "-Dspring.main.web-application-type=none",
        "-Dlogging.pattern.console=",
        "-jar",
        "yu-image-search-mcp-server/target/yu-image-search-mcp-server-0.0.1-SNAPSHOT.jar"
      ],
      "env": {}
    }
  }
}

3)测试运行。编写单元测试代码:

@Testvoid doChatWithMcp() {
    // 测试图片搜索 MCPString message = "帮我搜索一些F1赛车的图片";
    String answer =  InterviewAPP.doChatWithMcp(message, chatId);
    Assertions.assertNotNull(answer);
}

4)接下来测试 SSE 连接方式,首先修改 M⁠CP 服务端的配置文件,激活 SSE 的配置:

spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            server1:
              url: http://localhost:8127
        # stdio:
        # servers-configuration: classpath:mcp-servers.json

然后以 Debug 模式启动 MCP 服务。

然后修改客户端的配置文件,添加 SSE 配置,⁠同时要注释原有的 stdio 配置以避免端口冲突:

spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            server1:
              url: http://localhost:8127
        # stdio:
        # servers-configuration: classpath:mcp-servers.json

五、MCP 开发最佳实践

已经学会如何开发 MCP 服务端和客户端后,我⁠们来学习一些 MCP 开发的最佳实践。

1)慎用 MCP:MCP 不是银弹,其本质就是工具调用,只不过统一了标准、更容易共享而已。如果我们自己开发一些不需要共享的工具,完全没必要使用 MCP,可以节约开发和部署成本。我个人的建议是 能不用就不用,先开发工具调用,之后需要提供 MCP 服务时再将工具调用转换成 MCP 服务即可。

2)传输模式选择:Stdio 模式作为客户端子进程运行,无需网络传输,因此安全性和性能都更⁠高,更适合小型项目;SSE 模式适合作为独立服务部署,可以被多客户端共享调用,更适合模块化的中大型项目团队。

3)明确服务:设计 MCP 服务时,要合理划分工具和资源,并且利用 @Tool@ToolParam 注解尽可能清楚地描述工具的作用,便于 AI 理解和选择调用。

4)注意容错:和工具开发一样,要注意 MCP 服务的容错性和⁠健壮性,捕获并处理所有可能的异常,并且返回友好的错误信息,便于客户端处理。

5)性能优化:MCP 服务端要防止单次执行时间过长,可以采用异步模式来处理耗时操作,或⁠者设置超时时间。客户端也要合理设置超时时间,防止因为 MCP 调用时间过长而导致 AI 应用阻塞。

6)跨平台兼容性:开发 MCP 服务时,应该考虑在 Windows、Linux 和 macOS 等不同操作系统上的兼容性。特别是使用 stdio 传输模式时,注意路径分隔符差异、进程启动方式和环境变量设置。比如客户端在 Windows 系统中使用命令时需要额外添加 .cmd 后缀。

六、MCP 部署方案

由于 MCP 的传输方式分为 stdio(本地)和 SSE(远程),因此 MCP 的部署也可以对应分为 本地部署远程部署,部署过程和部署一个后端项目的流程基本一致。

本地部署

适用于 stdio 传输方式。跟我们开发 MCP 的流程一致,只需要把 MCP Server 的代码⁠打包(比如 jar 包),然后上传到 MCP Client 可访问到的路径下,通过编写对应的 MCP 配置即可启动。

举个例子,我们的后端项目放到了服务器 A 上,如果这个项目需要调用 java 开发的 ⁠MCP Server,就要把 MCP Server 的可执行 jar 包也放到服务器 A 上。

这种方式简单粗暴,适合小项目,但缺点也很明显,每个 MCP 服务都要单独部署(放到服务器上),如果 MCP⁠ 服务多了,可能会让人很崩溃。这时你不禁会想:我为什么不直接在后端项目中开发工具调用,非要新搞个项目开发 MCP 呢?

远程部署

适用于 SSE 传输方式。远程部署 MCP 服务的流程跟部署一个后⁠端 web 项目是一样的,都需要在服务器上部署服务(比如 jar 包)并运行。

除了部署到自己的服务器之外,由于 MCP 服务一般都是职责单一的小型项目,很适合部署到 Serverless 平台上。使用 Server⁠less 平台,开发者只需关注业务代码的编写,无需管理服务器等基础设施,系统会根据实际使用量自动扩容并按使用付费,从而显著降低运维成本和开发复杂度。

百炼提供了详细的 使用和部署 MCP 服务指南,可以将自己的 MCP 服务部署到阿里云函数计算平台,实现 Serverless 部署。

1)首先进入 MCP 管理页面,点击创建 MCP 服务:

2)创建 MCP 服务,建议把描述写清楚。注意,安装方式必须选择 npx 或者 uvx 才可以触发函数部署,因为部署的原⁠理就是在阿里云提供的计算资源上运行这些命令来启动服务进程。暂时不支持部署 Java 开发的 MCP,所以此处我们拿地图 MCP 演示:

编写 MCP 服务配置:

3)创建 MCP 服务成功后,可以到阿里云控制⁠台查看函数详情:

4)之后,可以在 AI 应用中使用自定义的 MCP 服务:

💡 友情提示,如果是学习使用,建议及时删除 ⁠MCP 服务哦,会自动关联删除函数计算资源。

提交至平台

你还可以把 MCP 服务提交到各种第三方 MCP 服务市⁠场,类似于发布应用到应用商店,让其他人也能使用你的 MCP 服务。

这样做有什么好处呢?A47lphkkDDkxdCS/rQHpdWHGIqT1c+f0rRj5gEHvdTo=

其实这个做法有点像开源,你就想想开源代码有什么好处就理解了,咱直白地说,至少有一个好处⁠是可以提升技术影响力、收获一波流量。要不然你看大公司为啥那么快就在 MCP 服务市场上占坑呢?

当然,如果你有自己的 API 接口服务,通过提供 MCP 服务,相当于增加了用户数和调用量。比如⁠我们前面使用的高德地图 MCP,就依赖高德地图的 API Key,每次调用都会计算费用。

怎么把 MCP 服务提交至平台呢?

其实我们不需要提前学习,因为每个平台的提交规则不同、可能⁠也会不断变化,我们只需要在想提交服务时遵循平台的规则和标准即可。

举个例子,比如提交 MCP 到 MCP.so,直接点击右上角的提交按钮,然后填写 MCP 服务的 GitHub 开源地址、以及服务器配置,点击提交即可。

扩展思路

1)自主实现一个 MCP 服务,并通过 env⁠ 环境变量传递参数(如 API Key)

2)在自己的服务器上部署一个 SSE 传输方式⁠的 MCP 服务

3)通过阿里云百炼平台部署一个自定义的 MCP⁠ 服务,重点是学习部署流程

4)在任何一个 MCP 服务市场上提交自己开源⁠的 MCP 服务,注意不要暴露敏感信息

本节作业

1)完成本节代码,开发图片搜索 MCP 服务,⁠并基于 Stdio 和 SSE 模式调用服务

2)使用 Cursor 调用 MCP 服务

3)掌握 Spring AI 开发 MCP 服⁠务端和客户端的方法

4)理解 MCP 的调用原理,为什么客户端通过⁠配置就能让 AI 调用 MCP 服务呢?

结语

AI智能体,AI编程感兴趣的朋友可以在掘金私信我,或者直接加我微信:wangzhongyang1993。

后面我还会更新更多跟AI相关的文章,欢迎关注我一起学习