基于SpringAI实现MCP协议:让AI和外部服务"牵手成功"的开放标准🤝

230 阅读7分钟

引言 :当AI遇见"MCP红娘" 💘

在人工智能这个"卷王"频出的圈子里,大语言模型(LLM)的能力边界正在疯狂扩张🚀。然而,由于数据孤岛和工具集成的复杂性,AI模型们常常像是被关在象牙塔里的学霸——知识渊博却无法实操。直到2024年底,Anthropic推出的模型上下文协议(MCP)如同一位专业的"红娘",为AI和外部服务牵线搭桥。本文将带你深入了解这位"红娘"MCP,并展示如何基于SpringAI框架实现MCP的"相亲大会"(客户端和服务端)💑。

一、MCP:AI世界的"万能翻译官" 🦸

MCP(Model Context Protocol)是一种标准化协议,专门负责统一大型语言模型与外部数据源和工具之间的"聊天方式"。它的核心价值就像是给AI配了一个"万能翻译官",让AI能够安全地访问和操作本地及远程数据,真正实现"连接万物"的超能力⚡。 image.png 由上图可以看出,在没有MCP协议时,AI需要像个"多语种翻译"一样学习各种软件服务的协议;而有了MCP后,AI只需要学会这一门"世界语"就能与所有服务交流了。

二、MCP vs Tool Calling:为什么有了筷子🥢还要发明叉子?🍴

既然有了Tool Calling,为什么还要有MCP,举两个场景来说明:

  • 在SpringAI中编写的Tools,可以放到Python中使用吗🤔?显然是不可以的,要想让Python中使用,就得使用Python语言重写一次Tool,才能使用,这就是不通用性的体现😓。
  • 在企业项目开发中,我们采用Spring AI框架来构建智能体🤖。这些智能体通常需要执行一系列通用功能,如天气查询🌤️、商品检索及下单🛒等。基于以往的做法,每当开发新项目时,都需要重新编写实现这些功能的工具(Tool),这显然不利于代码复用🔄。为解决这一问题,可以采用MCP标准进行服务器端开发,并将这些常用工具封装成服务接口🛠️。这样一来,其他的智能体只需通过调用该服务即可轻松访问所需功能,从而极大地提高了代码的复用性和开发效率📈。

所以说,MCP就像是餐具中的"瑞士军刀"——更加通用和便捷。

从技术实现角度看,MCP本质上也是基于Tool Calling机制,但通过标准化协议进行了封装和抽象,提供了更加统一和规范的交互方式。

image.png

三、MCP通信机制:两种"约会方式" 💕

根据 MCP 的规范,当前支持两种通信机制(传输方式):

  • Stdio(标准输入输出):本地进程间通信(IPC),主要用在本地服务上,操作你本地的软件或者本地的文件。
  • SSE(Server-Sent Events):远程网络通信(基于HTTP),主要用在跨网络的实时数据传输,适用于需要访问远程资源或分布式部署的场景。

四、 Spring AI MCP: Java开发者的"神兵利器" ⚔️

SpringAI对MCP做了支持,简化了Java项目中的MCP开发。参考文档

1. MCP Client:AI的"私人助理" 💼

MCP Client 负责建立和管理与 MCP 服务器之间的连接,以及管理:

  • 协议版本协商以确保与服务器的兼容性
  • 功能协商以确定可用特性
  • 消息传输和 JSON-RPC 通信
  • 工具发现与执行
  • 资源访问与管理
  • 提示系统(Prompt )交互
  • 可选功能:
    • 根管理
    • 采样支持
  • 同步和异步操作
  • 传输选项:
    • 基于标准输入输出(stdio)的进程间通信传输
    • 基于 Java HttpClient 的 SSE 客户端传输
    • 用于响应式 HTTP 流的 WebFlux SSE 客户端传输

2. MCP Server:工具的"才艺展示平台" 🎤

  • 服务器端协议操作实现
    • 工具暴露与发现
    • 基于URI的资源管理
    • 提示模板提供与处理
    • 与客户端的能力协商
    • 结构化日志记录和通知
  • 并发客户端连接管理
  • 同步和异步API支持
  • 传输实现:
    • 基于标准输入输出(stdio)的进程间通信传输
    • 基于Servlet的SSE(服务器发送事件)服务器传输
    • 用于响应式HTTP流的WebFlux SSE服务器传输
    • WebMVC SSE 服务器传输,用于基于 Servlet 的 HTTP 流式传输

五、MCP Client实战:让AI成为"浏览器操控大师" 🎮

下面我们将基于SpringAIMCP功能进行对MCPClient进行学习。

我们的需求是,给大模型提问:打开网站:https://www.coze.cn/,总结下这个网站的内容`,就会进行相应的操作。显然,大模型如果不做增强的话,是做不到的,下面我们就基于MCP进行实现。

第一步,先在本机通过npm安装@executeautomation/playwright-mcp-server,如下:

#使用国内源安装命令:
npm install -g @executeautomation/playwright-mcp-server --registry=https://registry.npmmirror.com

第二步,增加依赖:

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

第三步,增加mcp-servers.json文件:

{
  "mcpServers": {
    "playwright": {
      "command": "npx.cmd",
      "args": [
        "-y",
        "@executeautomation/playwright-mcp-server"
      ]
    }
  }
}

第四步,在application.yml中增加MCP的配置内容:

spring:
  ai:
    mcp:
      client:
        enabled: true # 启用mcp客户端
        name: ${spring.application.name} # 客户端名称
        version: 1.0.0 # 版本号
        request-timeout: 30s # 请求超时时间
        toolcallback:
          enabled: true # 开启工具回调功能
        stdio:
          servers-configuration: classpath:mcp-servers.json # mcp服务器配置文件

第五步,在SpringAIConfig中配置Tools

@Configuration
public class SpringAIConfig {

    private static final String SYSTEM_PROMPT = """
            你是一个全能助手,可以帮我解决各种问题。
            """;

    /**
     * 创建并返回一个ChatClient的Spring Bean实例。
     *
     * @param builder 用于构建ChatClient实例的构建者对象
     * @return 构建好的ChatClient实例
     */
    @Bean
    public ChatClient chatClient(ChatClient.Builder builder,
                                 ToolCallbackProvider tools
    ) {
        return builder
                .defaultSystem(SYSTEM_PROMPT) // 设置默认的系统角色
                .defaultTools(tools) // 设置默认的工具
                .build();
    }

}

完成以上步骤后,AI就能自动打开浏览器并访问指定网站了!就像给AI配了一个"机械手",让它能够实际操作浏览器。 image.png

image.png

原理分析:背后的"魔法" ✨

通过上面的实战,已经可以看到大模型可以控制浏览器了,是怎么做到的呢?

实际上,底层的实现也是基于Tool Calling实现的,由于配置了mcpServers,就拥有了好多的工具,注册到SpringAI中。

而这些工具的开发,并不是我们自己做的,也不是用java开发的,是别人开发好的,遵循了MCP协议,所以就可以集成到SpringAI中使用了,而这工具就是用来控制浏览器的,所以就实现了上面的效果。

六、MCP Server实战:打造自己的"天气查询小能手" 🌤️:

前面我们都是使用的已经写好的MCP Server,实际上,也可以自己来实现的。

接下来,我们将写一个自己的天气查询服务,将他封装成MCP Server,这样需要的天气查询服务的大模型,就可以直接集成了。

查询天气服务平台:t.weather.itboy.net/api/weather…

1. 创建my-spring-ai-mcp-server

2. 导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.xiaokui</groupId>
        <artifactId>my-spring-ai-mcp</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>my-spring-ai-mcp-server</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
        </dependency>
    </dependencies>
</project>

3. 编写配置

server:
  port: 8101  #端口
  tomcat:
    uri-encoding: UTF-8   #服务编码
spring:
  application:
    name: my-spring-ai-mcp-server
  ai:
    mcp:
      server:
        enabled: true
        name: ${spring.application.name}
        version: 1.0.0
        type: ASYNC # 服务类型 异步,支持 SYNC 和 ASYNC

4. 启动类

@Slf4j
@SpringBootApplication
public class MCPServerApplication {

    public static void main(String[] args) throws UnknownHostException {
        SpringApplication app = new SpringApplicationBuilder(MCPServerApplication.class).build(args);
        Environment env = app.run(args).getEnvironment();
        String protocol = "http";
        if (env.getProperty("server.ssl.key-store") != null) {
            protocol = "https";
        }
        log.info("--/\n---------------------------------------------------------------------------------------\n\t" +
                        "Application '{}' is running! Access URLs:\n\t" +
                        "Local: \t\t{}://localhost:{}\n\t" +
                        "External: \t{}://{}:{}\n\t" +
                        "Profile(s): \t{}" +
                        "\n---------------------------------------------------------------------------------------",
                env.getProperty("spring.application.name"),
                protocol,
                env.getProperty("server.port"),
                protocol,
                InetAddress.getLocalHost().getHostAddress(),
                env.getProperty("server.port"),
                env.getActiveProfiles());
    }

}

5. 编写工具

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WeatherDTO {

    @JsonPropertyDescription("城市ID")
    private String cityId;

    @JsonPropertyDescription("城市名称")
    private String city;

    @JsonPropertyDescription("当前温度(单位:℃)")
    private String temperature;

    @JsonPropertyDescription("低温(单位:℃)")
    private String lowTemperature;

    @JsonPropertyDescription("高温(单位:℃)")
    private String highTemperature;

    @JsonPropertyDescription("数据日期(格式:YYYYMMDD)")
    private String date;

    @JsonPropertyDescription("空气质量指数")
    private String quality;

    @JsonPropertyDescription("PM2.5 浓度(单位:微克/立方米)")
    private double pm25;

}
@Service
public class WeatherService {

    @Tool(description = "根据城市id查询天气信息")
    public WeatherDTO getWeather(@ToolParam(description = "城市id") String cityId) {
        // 通过http请求获取天气信息,并且通过json数据解析为WeatherDTO对象
        String url = "http://t.weather.itboy.net/api/weather/city/" + cityId;
        String data = HttpUtil.get(url);
        JSONObject jsonObject = JSONUtil.parseObj(data);

        return WeatherDTO.builder()
                .cityId(jsonObject.getByPath("cityInfo.citykey", String.class)) // 城市ID
                .city(jsonObject.getByPath("cityInfo.city", String.class)) // 城市名称
                .date(jsonObject.getByPath("date", String.class))// 数据日期
                .temperature(jsonObject.getByPath("data.wendu", String.class))   // 当前温度
                .lowTemperature(jsonObject.getByPath("data.forecast[0].low", String.class))// 低温
                .highTemperature(jsonObject.getByPath("data.forecast[0].high", String.class))// 高温
                .quality(jsonObject.getByPath("data.quality", String.class))// 空气质量
                .pm25(jsonObject.getByPath("data.pm25", Double.class))// PM2.5数值
                .build();
    }

}

6. MCPServer配置

@Configuration
public class McpConfig {

    /**
* 申明对外提供服务的工具
*/
@Bean
    public List<ToolCallback> tools(WeatherService weatherService) {
        return List.of(ToolCallbacks.from(weatherService));
    }

}

7. 在MCPClient中集成服务

只需要在MCPClient中指定服务地址即可。

server:
  port: 8100  #端口
  tomcat:
    uri-encoding: UTF-8   #服务编码
spring:
  application:
    name: my-spring-ai-mcp-client
  ai:
    dashscope:
      api-key: ${ALIYUN_API_KEY}
      chat:
        options:
          model: qwen-plus
    mcp:
      client:
        enabled: true # 启用mcp客户端
        name: ${spring.application.name} # 客户端名称
        version: 1.0.0 # 版本号
        request-timeout: 30s # 请求超时时间
        toolcallback:
          enabled: true # 开启工具回调功能
        stdio:
          servers-configuration: classpath:mcp-servers.json # mcp服务器配置文件
        type: ASYNC # 服务类型,指定为异步,支持 SYNC 和 ASYNC
        sse:
          connections:
            server1:
              url: http://localhost:8101 # 指定MCP Server服务器地址

image.png

七、在线MCP服务:直接"呼叫外援" 📞

前面我们自定义了MCP服务,并且通过sse协议提供了服务,这种在线服务的方式也是以后的一种主流方式。 使用起来也非常的简单,这里我们以高德为例进行讲解。

1. 集成使用

通过官方文档,可以看到sse的地址:

url规则为: https://mcp.amap.com/sse?key=您在高德官网上申请的key

需要注意:

由于使用外部的sse服务,不要通过配置url的方式进行配置,需要通过代码的方式集成,否则会不能正常使用

增加配置类:

@Configuration
public class McpConfig {

    /**
     * 高德地图 MCP 服务
     */
    @Bean
    public List<NamedClientMcpTransport> amapMcpClientTransport() {
        McpClientTransport transport = HttpClientSseClientTransport
                .builder("https://mcp.amap.com")
                .sseEndpoint("/sse?key=xxxxxxx")
                .objectMapper(new ObjectMapper())
                .build();
        return List.of(new NamedClientMcpTransport("amap", transport));
    }

}

image.png

可以看到,已经调用了高德服务了。

八、 MCP服务市场:AI工具的"应用商店" 🏪

目前也有挺多的MCP服务市场了,以后应该还会有很多。


通过本文的学习,相信你已经对MCP协议有了深入的理解,并且能够基于SpringAI实现MCP客户端和服务端。MCP协议为AI与外部服务的交互提供了标准化方案,极大地扩展了AI的能力边界。未来,随着MCP生态的不断完善,AI应用开发将会变得更加简单和高效!