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

【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 Boot4.0.1
spring-ai-bom2.0.0-M1
spring-ai-starter-mcp-server-webflux2.0.0-M1

pom核心依赖

<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个工具方法

packagecom.wen3.demo.ai.mcp.server.tools;importlombok.extern.slf4j.Slf4j;importorg.springaicommunity.mcp.annotation.McpTool;importorg.springaicommunity.mcp.annotation.McpToolParam;importreactor.core.publisher.Mono;importjava.util.Map;/** * @author tangheng */@Slf4jpublicclassDemoTool{@McpTool(name="hello", description ="根据城市名称获取天气预报")publicMono<String>hello(@McpToolParam(description ="城市名称,比如:广州")String city ){ log.info("city: {}", city);Map<String,String> mockData =Map.of("西安","晴天","北京","小雨","上海","大雨");returnMono.just(mockData.getOrDefault(city,"抱歉:未查询到对应城市!"));}@McpTool(description ="根据名字算八字")publicMono<String>helloWithName(@McpToolParam(description ="姓名")String name ){ log.info("name: {}", name);returnMono.just("五行缺火!");}}

创建自动配置类

packagecom.wen3.demo.ai.mcp.server.autoconfigure;importcom.wen3.demo.ai.mcp.server.tools.DemoTool;importorg.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * @author tangheng */@ConfigurationclassMcpServerAutoConfiguration{@ConditionalOnMissingBean@BeanDemoTooldemoTool(){returnnewDemoTool();}}

创建启动类

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

packagecom.wen3.demo.ai.mcp.server.autoconfigure;importcom.wen3.demo.ai.mcp.server.tools.DemoTool;importorg.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * @author tangheng */@ConfigurationclassMcpServerAutoConfiguration{@ConditionalOnMissingBean@BeanDemoTooldemoTool(){returnnewDemoTool();}// @ConditionalOnMissingBean// @Bean// public ToolCallbackProvider dateTimeTools() {// return MethodToolCallbackProvider.builder().toolObjects(demoTool()).build();// }}
spring.ai.mcp.server.annotation-scanner.enabled=true默认是true,所以会自动扫描@McpTool标注的类,并注册为工具对象。不需要再使用ToolCallbackProvider进行注册,否则会重复注册。

配置文件

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

必须配置项

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

注意配置项

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.McpServerStreamableHttpProperties

前缀为spring.ai.mcp.server.streamable-http的配置项,请求端点默认是/mcp

org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerProperties

前缀为spring.ai.mcp.server.annotation-scanner的配置项,默认为扫描@McpTool注解进行工具的注册

org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties

前缀为spring.ai.mcp.server的配置项

核心逻辑

  • 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请求
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
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请求
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请求的响应如下
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请求
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请求的响应如下
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请求另一个工具
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"}}} '
  • 响应如下
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}}
Could not load content