【MCP】使用SpringBoot基于Streamable-HTTP构建MCP-Server

【MCP】使用SpringBoot基于Streamable-HTTP构建MCP-Server

背景

最近几年AI应用越来越广泛,Anthropic公司在2024年11月提出了MCP协议,随着时间推移,支持MCP协议的厂商越来越多。

MCPclient端和server端通讯的协议也在逐步演进,一开始主流的是SSE协议,随后又诞生了Streamable-HTTP协议,用于取代SSE协议。

本文将介绍如何使用SpringBoot基于Streamable-HTTP构建MCP-Server服务端。

SSE与Streamable-HTTP

MCP(Model Context Protocol)协议通过PR #206引入的Streamable HTTP传输层,是针对AI模型与外部工具通信场景的一次底层革新。它并非简单替代传统HTTP+SSE方案,而是通过 "统一通信范式、动态传输适配、状态化增强" 三大核心设计,解决了企业级AI应用在高并发、长周期任务、复杂基础设施环境下的通信痛点,同时降低开发与维护成本,成为MCP协议规模化落地的关键支撑。

本文开发环境介绍

开发依赖 版本
Spring Boot 4.0.1
spring-ai-bom 2.0.0-M1
spring-ai-starter-mcp-server-webflux 2.0.0-M1

pom核心依赖

xml 复制代码
<dependencyManagement>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-bom</artifactId>
    <version>${spring-ai.version}</version>
    <type>pom</type>
    <scope>import</scope>
  </dependency>
</dependencyManagement>

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

spring-ai-starter-mcp-server-webfluxspring-ai-starter-mcp-server-webmvc都可以,一种是响应式架构,一种是非响应式架构,两者只能二选一。

MCP工具类

创建一个MCP工具类,包含2个工具方法

java 复制代码
package com.wen3.demo.ai.mcp.server.tools;


import lombok.extern.slf4j.Slf4j;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
import reactor.core.publisher.Mono;

import java.util.Map;

/**
 * @author tangheng
 */
@Slf4j
public class DemoTool {

    @McpTool(name="hello", description = "根据城市名称获取天气预报")
    public Mono<String> hello(
            @McpToolParam(description = "城市名称,比如:广州") String city
    ) {
        log.info("city: {}", city);
        Map<String, String> mockData = Map.of(
                "西安", "晴天",
                "北京", "小雨",
                "上海", "大雨"
        );
        return Mono.just(mockData.getOrDefault(city, "抱歉:未查询到对应城市!"));
    }

    @McpTool(description = "根据名字算八字")
    public Mono<String> helloWithName(
            @McpToolParam(description = "姓名") String name
    ) {
       log.info("name: {}", name);
        return Mono.just("五行缺火!");
    }
}

创建自动配置类

java 复制代码
package com.wen3.demo.ai.mcp.server.autoconfigure;


import com.wen3.demo.ai.mcp.server.tools.DemoTool;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author tangheng
 */
@Configuration
class McpServerAutoConfiguration {

    @ConditionalOnMissingBean
    @Bean
    DemoTool demoTool() {
        return new DemoTool();
    }
}

创建启动类

最后创建Spring Boot应用的启动类

java 复制代码
package com.wen3.demo.ai.mcp.server.autoconfigure;


import com.wen3.demo.ai.mcp.server.tools.DemoTool;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author tangheng
 */
@Configuration
class McpServerAutoConfiguration {

    @ConditionalOnMissingBean
    @Bean
    DemoTool demoTool() {
        return new DemoTool();
    }

//    @ConditionalOnMissingBean
//    @Bean
//    public ToolCallbackProvider dateTimeTools() {
//        return MethodToolCallbackProvider.builder().toolObjects(demoTool()).build();
//    }
}

spring.ai.mcp.server.annotation-scanner.enabled=true默认是true,所以会自动扫描@McpTool标注的类,并注册为工具对象。不需要再使用ToolCallbackProvider进行注册,否则会重复注册。

配置文件

yaml 复制代码
server:
  port: 9090

spring:
  ai:
    mcp:
      server:
        name: demo-mcp-server
        version: 1.0.0
        type: ASYNC  # Recommended for reactive applications
        protocol: STREAMABLE
        streamable-http:
          mcp-endpoint: /mcp
          keep-alive-interval: 30s

必须配置项

properties 复制代码
spring.ai.mcp.server.protocol=STREAMABLE

必须把spring.ai.mcp.server.protocol设置为STREAMABLE,才能开启Streamable-HTTP协议

注意配置项

properties 复制代码
spring.ai.mcp.server.type=ASYNC
  • 如果spring.ai.mcp.server.type配置ASYNC,则工具的返回值类型必须是如下类型
    • Mono<T>
    • Flux<T>
    • Publisher<T>
  • 如果spring.ai.mcp.server.type配置SYNC,则工具的返回值类型必须是如下类型
    • Primitive types (int, double, boolean)
    • Object types (String, Integer, custom POJOs)
    • MCP types (CallToolResult, ReadResourceResult, GetPromptResult, CompleteResult)
    • Collections (List<String>, Map<String, Object>)

相关配置类

  • org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties 前缀为spring.ai.mcp.server的配置项
  • org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerProperties 前缀为spring.ai.mcp.server.annotation-scanner的配置项,默认为扫描@McpTool注解进行工具的注册
  • org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties 前缀为spring.ai.mcp.server.streamable-http的配置项,请求端点默认是/mcp

核心逻辑

  • io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider#handlePost
    • 这个方法是收到POST请求的处理逻辑
    • Accept: application/jsonAccept: text/event-stream,同时包含这两个请求头,请求才会继续往下走
  • io.modelcontextprotocol.spec.McpSchema.deserializeJsonRpcMessage
    • 这个方法是解析json参数的过程
    • 同时传methodid,就是JSONRPCRequest请求
  • protocolVersion这个字段不能是null,但可以是空字符串
  • io.modelcontextprotocol.spec.McpStreamableServerSession#responseStream
    • 这里是根据不同method,使用不同的McpRequestHandler进行处理
  • io.modelcontextprotocol.server.McpAsyncServer#prepareRequestHandlers
    • McpRequestHandler是在这个逻辑里添加的

curl测试

  • 首选必须发起initialize请求
shell 复制代码
curl -ik -H "Accept: application/json" -H "Accept: text/event-stream" -XPOST --url "http://localhost:9090/mcp" -d '
{"id": "stream-1","method": "initialize", "params": {"capabilities":{}, "clientInfo": {}, "protocolVersion": ""}}
'
  • initialize请求的响应如下,从响应中拿到Mcp-Session-Id
shell 复制代码
HTTP/1.1 200 OK
Content-Type: application/json
Mcp-Session-Id: d6d67425-2d58-4758-9bbd-9b2c7521e033
Content-Length: 291

{"jsonrpc":"2.0","id":"stream-1","result":{"protocolVersion":"2025-06-18","capabilities":{"completions":{},"logging":{},"prompts":{"listChanged":true},"resources":{"subscribe":false,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"demo-mcp-server","version":"1.0.0"}}}
  • 发起tools/list请求
shell 复制代码
curl -ik -H "Mcp-Session-Id: d6d67425-2d58-4758-9bbd-9b2c7521e033" -H "Accept: application/json" -H "Accept: text/event-stream" -XPOST --url "http://localhost:9090/mcp" -d '
{"id": "stream-1","method": "tools/list", "params": {}}
'
  • tools/list请求的响应如下
shell 复制代码
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8

event:message
data:{"jsonrpc":"2.0","id":"stream-1","result":{"tools":[{"name":"hello","title":"hello","description":"根据城市名称获取天气预报","inputSchema":{"type":"object","properties":{"city":{"type":"string","description":"城市名称,比如:广州"}},"required":["city"]},"annotations":{"title":"","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true}}]}}
  • 发起tools/call请求
shell 复制代码
curl -ik -H "Mcp-Session-Id: d6d67425-2d58-4758-9bbd-9b2c7521e033" -H "Content-Type: application/json;charset=GBK" -H "Accept: application/json" -H "Accept: text/event-stream" -XPOST --url "http://localhost:9090/mcp" -d '
{"id": "stream-1","method": "tools/call", "params": {"name":"hello", "arguments": {"city": "北京"}}}
'

Content-Type: application/json;charset=GBK这个请求头主要用来指定编码格式,如果参数是GBK编码,则需要添加这个请求头,否则会出现乱码

  • tools/call请求的响应如下
shell 复制代码
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8

event:message
data:{"jsonrpc":"2.0","id":"stream-1","result":{"content":[{"type":"text","text":"小雨"}],"isError":false}}
  • tools/call请求另一个工具
shell 复制代码
curl -ik -H "Mcp-Session-Id: d6d67425-2d58-4758-9bbd-9b2c7521e033" -H "Content-Type: application/json;charset=GBK" -H "Accept: application/json" -H "Accept: text/event-stream" -XPOST --url "http://localhost:9090/mcp" -d '
{"id": "stream-1","method": "tools/call", "params": {"name":"helloWithName", "arguments": {"name": "Lucy"}}}
'
  • 响应如下
shell 复制代码
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8

event:message
data:{"jsonrpc":"2.0","id":"stream-1xx","result":{"content":[{"type":"text","text":"五行缺火!"}],"isError":false}}
相关推荐
像少年啦飞驰点、8 小时前
零基础入门 Spring Boot:从“Hello World”到可上线的 Web 应用全闭环指南
java·spring boot·web开发·编程入门·后端开发
有来技术8 小时前
Spring Boot 4 + Vue3 企业级多租户 SaaS:从共享 Schema 架构到商业化套餐设计
java·vue.js·spring boot·后端
东东5169 小时前
xxx医患档案管理系统
java·spring boot·vue·毕业设计·智慧城市
东东5169 小时前
学院个人信息管理系统 (springboot+vue)
vue.js·spring boot·后端·个人开发·毕设
qq_124987075311 小时前
基于Srpingboot心晴疗愈社平台的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·spring·microsoft·毕业设计·计算机毕业设计
大爱编程♡11 小时前
SpringBoot统一功能处理
java·spring boot·后端
小马爱打代码14 小时前
Spring Boot:第三方 API 调用的企业级容错设计
java·spring boot·后端
东东51615 小时前
xxx食堂移动预约点餐系统 (springboot+微信小程序)
spring boot·微信小程序·小程序·毕业设计·个人开发·毕设
csdn2015_15 小时前
springboot task
java·spring boot·后端
czlczl2002092515 小时前
Spring Boot :如何高性能地在 Filter 中获取响应体(Response Body)
java·spring boot·后端