SpringAI(1.1.0-M)—MCP Server各协议分析及示例

172 阅读8分钟

原文链接:SpringAI(1.1.0-M)—MCP Server各协议分析及示例

[!TIP] Streamable HTTP 更适合大规模企业级部署

实战代码可见:github.com/GTyingzi/sp… 下的 mcp 目录下的

  • server/mcp-streamable-webmvc-server、server/mcp-streamable-webflux-server
  • client/mcp-streamable-client、client/mcp-streamable-webflux-client

各协议详细分析

STDIO:本地进程通信的简单方案

描述:STDIO 协议是 Spring AI MCP Server 最基础的传输实现,它基于标准输入/输出流进行通信,无需网络协议支持。在技术实现上,客户端直接启动并管理服务器进程,通过向服务器标准输入(stdin)写入消息,从标准输出(stdout)读取消息完成交互

优势:

  • 简单和安全性:数据传输完全在进程内存中进行,避免了网络传输的安全风险
  • 无需网络配置:开发者只需通过命令行启动服务进程即可开始通信
  • 低延迟低场景:适合需要快速响应的本地工具调用

劣势:

  • 仅支持本地通信:无法跨网络或分布式环境使用
  • 并发处理限制:采用同步机制,单线程处理请求,难以应对高并发场景
  • 资源管理问题:每次请求都需要重建连接,无法有效复用资源

SSE:传统 HTTP 流式传输的单向方案

描述:SSE 协议是 Spring AI 早期版本中主流的远程传输方案,基于 HTML5 标准的服务器发送事件技术。在 Spring AI 框架中,SSE 分为两种实现方式:WebMVC 模式(基于 Servlet API)和 WebFlux 模式(基于响应式编程)

特点:服务器向客户端单向推送数据,允许服务器在建立连接后随时发送实时更新,无需客户端反复发起请求

优势:

  • 实时推送能力:支持长连接保持,适合需要持续更新的场景
  • 实现复杂度低:客户端只需通过浏览器原生支持的 EventSource 对象即可实现连接
  • 传统环境集成方便:适合与现有 Spring MVC 项目无缝衔接

劣势:

  • 高并发资源消耗:每个连接需占用约 80KB 内存,万级并发时可能导致服务器资源耗尽
  • 连接稳定性差:在弱网环境下中断率高达 15%-30%,且不支持断线自动恢复
  • 架构扩展性限制:强制要求服务器维护粘性会话,在负载均衡场景下增加了配置复杂度

Streamable HTTP:平衡性能与状态的创新方案

描述 Streamable HTTP 协议是 MCP 协议在 2025 年 3 月的重大升级,它取代了原有的 HTTP+SSE 作为默认传输方式。Streamable HTTP 的核心创新在于统一了请求/响应端点,支持按需流式传输和会话管理,同时保留了 HTTP 协议的简洁性

特点:在高并发场景下,TCP 连接数仅为几十条,远低于 SSE 的上千条,显著降低了服务器资源压力。响应时间方面,Streamable HTTP 在 1000 并发用户测试中平均响应时间为 7.5ms,而 SSE 飙升至 1511ms,性能提升近 200 倍

Stateless Streamable HTTP:无状态设计的极致优化

描述:Stateless Streamable HTTP 是 Streamable HTTP 的无状态变体,它通过移除会话状态管理,进一步优化了资源利用率和扩展性。Stateless 模式的核心理念是将状态管理责任从服务器转移到客户端,每次请求都包含完整的上下文信息

资源效率优势

  • 内存消耗降至 5KB/请求以下,且在空闲状态下资源占用趋近于零
  • 水平扩展能力强:请求可在服务器集群中任意路由,无需复杂的粘性会话机制
  • 网络兼容性好:完全遵循标准 HTTP 语义,能更好地穿透企业防火墙

实现复杂度

  • 客户端实现复杂:会话状态完全由客户端管理,增加了客户端实现复杂度
  • 复杂交互处理困难:对于需要持续会话的复杂交互,可能导致客户端代码臃肿
  • 断线重连要求高:客户端需主动传递所有必要信息,增加了实现难度

总结

下面是 Higrees 官网的测试文章:(使用 Streamable HTTP 前后的数据比对)

在 1000 个并发用户的测试场景下,TCP 连接数的变化

  • **HTTP + SSE:**需要维持大量长连接,TCP 连接数随时间持续增长
  • **Streamable HTTP:**按需建立连接,TCP 连接数维持在较低水平

模拟不同并发用户数下的请求成功率测试

  • **HTTP + SSE:**随着并发用户数增加,成功率显著下降
  • **Streamable HTTP:**即使在高并发场景下仍能保持较高的请求成功率

不同用户数量下的平均返回响应时间

  • HTTP + SSE 的平均响应时间更长,在高并发场景下响应时间波动较大
  • Streamable HTTP 的平均响应时间更短,响应时间波动较小,随并发用户数增加,响应时间增长更平

适用场景推荐


推荐协议
推荐理由
本地开发与调试场景
STDIO
无需网络配置,实现简单,适合快速验证工具逻辑和本地调试。例如,开发一个天气查询工具时,可以通过STDIO协议快速启动服务进程并测试工具功能
传统Servlet应用的实时通知
WebMVC模式的SSE
与现有Spring MVC项目无缝集成,适合简单的实时通知场景,如聊天室消息推送。但需注意高并发场景下的性能问题
高性能流式交互场景
Streamable HTTP
在高并发场景下性能优势明显,同时支持会话状态管理和断线重连。例如,实现一个AI对话系统,用户发送查询后,系统以流形式返回处理结果
大规模分布式系统
Stateless Streamable HTTP
无状态设计使请求可在集群中自由路由,支持真正的线性扩展。例如,微服务架构中的工具调用服务,每个请求都是独立的

下面带来 Streamable-HTTP 的 WebFlux 的示例

server 侧

pom 依赖

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

application.yml

server:
  port: 20000

spring:
  application:
    name: mcp-streamable-webflux-server
  ai:
    mcp:
      server:
        name: streamable-mcp-server
        protocol: streamable # SSE、STREAMABLE、STATELESS
        version: 1.0.0
        type: ASYNC  # Recommended for reactive applications
        instructions: "This reactive server provides time information tools and resources"
        capabilities:
          tool: true
          resource: true
          prompt: true
          completion: true
        request-timeout: 20s
        streamable-http:
          mcp-endpoint: /mcp
          keep-alive-interval: 30s
          disallow-delete: false


# 调试日志
logging:
  level:
    io:
      modelcontextprotocol:
        client: DEBUG
        spec: DEBUG
        server: DEBUG
  • spring.ai.mcp.server.protocol:选择协议,这里可以选三种类型SSE、STREAMABLE、STATELESS

  • spring.ai.mcp.server.streamable-http:

    • mcp-endpoint:设置端点,默认为/mcp
    • keep-alive-interval:定期向所有活跃的会话发送心跳消息,以维持活跃状态,默认不设置
    • disallow-delete:是否允许客户端通过 DELETE 请求来删除会话

工具及启动类

package com.spring.ai.tutorial.mcp.server.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author yingzi
 * @date 2025/5/28 08:55
 */

@Service
public class TimeService {

    private static final Logger logger = LoggerFactory.getLogger(TimeService.class);

    @Tool(description = "Get the time of a specified city.")
    public String  getCityTimeMethod(@ToolParam(description = "Time zone id, such as Asia/Shanghai") String timeZoneId) {
        logger.info("The current time zone is {}", timeZoneId);
        return String.format("The current time zone is %s and the current time is " + "%s", timeZoneId,
                getTimeByZoneId(timeZoneId));
    }

    private String getTimeByZoneId(String zoneId) {

        // Get the time zone using ZoneId
        ZoneId zid = ZoneId.of(zoneId);

        // Get the current time in this time zone
        ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);

        // Defining a formatter
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");

        // Format ZonedDateTime as a string
        String formattedDateTime = zonedDateTime.format(formatter);

        return formattedDateTime;
    }
}
package com.spring.ai.tutorial.mcp.server;

import com.spring.ai.tutorial.mcp.server.service.TimeService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

/**
 * @author yingzi
 * @since 2025/10/3
 */
@SpringBootApplication
public class StreamableWebfluxServerApplication {

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

    @Bean
    public ToolCallbackProvider timeTools(TimeService timeService) {
        return MethodToolCallbackProvider.builder().toolObjects(timeService).build();
    }
}

client 侧

pom 依赖

<dependencies>

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-autoconfigure-model-openai</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

application.yml

server:
  port: 19100

spring:
  application:
    name: mcp-streamable-webflux-client
  main:
    web-application-type: none
  ai:
    openai:
      api-key: ${DASHSCOPEAPIKEY}
      base-url: https://dashscope.aliyuncs.com/compatible-mode
      chat:
        options:
          model: qwen-max
    mcp:
      client:
        enabled: true
        name: my-mcp-client
        version: 1.0.0
        request-timeout: 600s
        type: ASYNC  # or ASYNC for reactive applications
        streamable-http:
          connections:
            server1:
              url: http://localhost:20000
              endpoint: /mcp

启动类

package com.spring.ai.tutorial.mcp.client;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.Scanner;

/**
 * @author yingzi
 * @since 2025/10/3
 */
@SpringBootApplication
public class StreamableWebfluxClientApplication {

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

    @Bean
    public CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools,
                                                 ConfigurableApplicationContext context) {

        return args -> {
            var chatClient = chatClientBuilder
                    .defaultToolCallbacks(tools.getToolCallbacks())
                    .build();

            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.print("\n>>> QUESTION: ");
                String userInput = scanner.nextLine();
                if (userInput.equalsIgnoreCase("exit")) {
                    break;
                }
                System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());
            }
            scanner.close();
            context.close();
        };
    }
}

效果

往期资料

Spring AI + Spring Ai Aliabba系统化学习资料

本教程将采用2025年5月20日正式的GA版,给出如下内容

  1. 核心功能模块的快速上手教程
  2. 核心功能模块的源码级解读
  3. Spring ai alibaba增强的快速上手教程 + 源码级解读

版本:

  • JDK21
  • SpringBoot3.4.5
  • SpringAI 1.0.3
  • SpringAI Alibaba 1.0.0.4

免费渠道:

  1. 为Spring Ai Alibaba开源社区解决解决有效的issue or 提供有价值的PR,可免费获取上述教程
  2. 往届微信推文

收费服务:收费69.9元

  1. 飞书在线云文档
  2. Spring AI会员群教程代码答疑
  3. 若Spring AI、Spring AI Alibaba教程内容无法满足业务诉求,可定制提供解决方案,带价私聊

学习交流圈

你好,我是影子,曾先后在🐻、新能源、老铁就职,兼任Spring AI Alibaba开源社区的Committer。目前新建了一个交流群,一个人走得快,一群人走得远,另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取