1. 为什么要在 Spring AI 里折腾 Streamable HTTP 类型的 MCP 服务端
如果你正在用 Spring AI 1.x 做智能体或者工具调用相关的服务,大概率已经听过 MCP(Model Context Protocol)这个词。简单说,MCP 就是一套让「模型」和「外部工具/资源」之间用统一协议对话的规范。以前我们写工具调用,每个模型厂商的格式都不一样,换一个模型就得改一遍代码;有了 MCP,工具端只要按协议暴露能力,客户端按协议调用,模型侧换谁都能接。
而 Streamable HTTP 是 MCP 协议在 2025-03-26 版本里正式引入的传输方式,它取代了早期的 SSE 传输。它最大的特点是:MCP 服务端可以作为一个独立的 HTTP 进程跑起来,客户端通过 POST/GET 请求跟它交互,需要推送多条消息时再走 SSE 流式通道。这意味着你可以把「工具服务」和「模型调用」拆成两个独立部署的单元,服务端统一管理工具、资源、提示词,客户端只管连上来用。
这篇要解决的问题很具体:在 Spring AI 1.x 里,把 MCP 服务端配成 Streamable HTTP 类型,同时让服务端里所有需要调用大模型的地方,统一走 TaoToken 的 Key/API 通道。适合谁?适合那些不想在每个业务模块里散落一堆模型 Key、希望服务端集中管理模型调用出口的开发者。我会给出可复制的application.yml、MCP 服务端骨架、启动日志,以及怎么验证流式响应真的通了。
2. 前置准备:TaoToken 通道与依赖选型
在动手写配置之前,先把两件事定下来:模型调用通道用谁,以及 MCP 服务端用哪个 starter。
模型调用通道这块,我选的是 TaoToken。它的定位是统一的模型 API 入口,你拿到一个 Key,就能在服务端统一配置模型调用,不用在代码里到处硬编码不同厂商的地址和密钥。对 MCP 服务端这种「工具里可能还要调模型」的场景特别合适——工具执行逻辑里如果需要模型补全,直接复用同一套通道配置就行。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ,API 入口是 https://taotoken.net/api ,注意 API 地址后面不加任何参数。
依赖选型上,Spring AI 1.x 提供了两个 Streamable HTTP 的 starter:
| starter | 底层 | 适用场景 |
|---|---|---|
spring-ai-starter-mcp-server-webmvc | Spring MVC | 传统阻塞式,团队熟悉 MVC 的优先 |
spring-ai-starter-mcp-server-webflux | WebFlux | 响应式、非阻塞、连接数高的场景 |
两者能力基本对齐,都支持工具、资源、提示词、补全、日志、进度、心跳、根路径变更。区别只在底层线程模型。我下面用 WebMVC 版本演示,因为大多数 Spring Boot 项目本来就是 MVC 栈,迁移成本最低;如果你项目已经是 WebFlux,把依赖名换掉即可,配置项完全一样。
依赖这样引:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency>版本跟随你项目里的 Spring AI BOM,1.x 系列即可。引进来之后,MCP 服务端的自动配置就生效了,接下来全靠application.yml控制行为。
3. 可复制的 application.yml 与 MCP 服务端骨架
3.1 核心配置:protocol 必须设为 STREAMABLE
这是最容易踩的坑:不显式设置protocol: STREAMABLE,服务端不会按 Streamable HTTP 模式启动。默认值不是它。完整配置如下,可以直接抄:
server: port: 8080 spring: ai: mcp: server: enabled: true protocol: STREAMABLE name: streamable-mcp-server version: 1.0.0 type: SYNC instructions: "This streamable server provides real-time notifications" resource-change-notification: true tool-change-notification: true prompt-change-notification: true capabilities: tool: true resource: true prompt: true completion: true streamable-http: mcp-endpoint: /api/mcp keep-alive-interval: 30s几个关键项解释一下。protocol: STREAMABLE是总开关,决定传输类型。type: SYNC表示同步服务端,工具处理器用McpSyncServerExchange;如果你的工具里有大量阻塞 IO 想改成异步,设成ASYNC,处理器换成McpAsyncServerExchange。mcp-endpoint: /api/mcp是客户端要连的路径,默认是/mcp,我改成/api/mcp是为了跟业务接口区分开。keep-alive-interval: 30s是保活心跳,默认禁用,开了之后服务端会定期给已连接的客户端发心跳,注意目前这个保活只对 SSE 那条「Listening for Messages from the Server」连接生效。
3.2 模型调用通道配置
MCP 服务端本身不强制你配模型,但工具执行逻辑里如果要调模型,就得有通道。把 TaoToken 的配置单独放一段,方便统一管理:
spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini这里base-url指向 TaoToken 的 API 入口,api-key用环境变量注入,别写死在 yml 里。模型名按你实际开通的填。这样服务端里任何需要模型补全的地方,都走这一条通道,换模型只改这一处。
3.3 MCP 服务端骨架:工具 + 资源 + 提示词
光有配置不够,得有实际暴露的能力。下面是一个最小可用的服务端骨架,包含一个工具、一个资源、一个提示词:
@SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } } @Service public class WeatherService { @Tool(description = "根据城市名称获取天气信息") public String getWeather(String cityName) { return "城市 " + cityName + " 当前晴,气温 22 摄氏度"; } }@Tool注解的方法会被自动扫描并注册成 MCP 工具,ToolCallbackProvider这个 Bean 负责把工具对象转成 MCP 声明。自动配置会检测所有ToolCallback、ToolCallback列表、ToolCallbackProvider类型的 Bean,合并注册,重名工具以首次出现的为准。如果你想关掉自动转换,设spring.ai.mcp.server.tool-callback-converter=false。
资源注册用底层 API 长这样:
@Bean public List<McpServerFeatures.SyncResourceSpecification> myResources() { var systemInfoResource = new McpSchema.Resource( "system://info", "system-info", "系统信息", "application/json", null); var spec = new McpServerFeatures.SyncResourceSpecification( systemInfoResource, (exchange, request) -> { String json = "{\"os\":\"linux\",\"jdk\":\"21\"}"; return new McpSchema.ReadResourceResult( List.of(new McpSchema.TextResourceContents( request.uri(), "application/json", json))); }); return List.of(spec); }提示词注册类似,用SyncPromptSpecification包一个McpSchema.Prompt加处理器即可。这三类能力默认全开,想关哪个就把capabilities下对应项设成false,服务端就不再注册和暴露它。
4. 启动验证与流式响应确认
4.1 看启动日志确认协议生效
启动应用后,日志里应该能看到 MCP 服务端初始化的痕迹。重点确认两件事:协议是 STREAMABLE,端点是/api/mcp。如果日志里出现类似Registered MCP tool(s)或者工具数量统计,说明工具注册成功。如果没看到任何 MCP 相关日志,八成是protocol没设对,或者 starter 没引进来。
4.2 用 curl 验证端点
先确认端点活着。Streamable HTTP 的交互是 POST 请求带 JSON-RPC 消息体:
curl -i -X POST http://localhost:8080/api/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": {"name": "curl-client", "version": "1.0"} } }'注意Accept头必须同时包含application/json和text/event-stream,否则服务端可能拒绝。返回里应该能看到服务端的能力声明,包括 tools、resources、prompts 这些。
4.3 验证流式响应
初始化之后,调用工具并观察流式返回。用tools/call方法:
curl -N -X POST http://localhost:8080/api/mcp \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "getWeather", "arguments": {"cityName": "杭州"} } }'-N关闭 curl 缓冲,这样你能实时看到 SSE 事件逐条推过来。如果返回里出现event: message加data:的格式,并且内容是工具执行结果,说明流式通道通了。这一步是判断 Streamable HTTP 是否真正工作的关键——普通 HTTP 一次性返回和 SSE 流式推送,行为完全不同。
5. 本篇常见错误排查
启动报错说找不到 MCP 端点或者 404:先检查mcp-endpoint配的路径和你 curl 的路径是否一致。默认是/mcp,我改成了/api/mcp,如果你抄配置时只抄了一半,很容易对不上。另外确认server.port没被其他配置覆盖。
客户端连上但工具列表为空:检查capabilities.tool是不是被设成了false,以及tool-callback-converter是不是被关了。还有一种情况是工具方法没加@Tool注解,或者ToolCallbackProviderBean 没被扫描到——确认它在@SpringBootApplication所在包或子包下。
流式响应收不到,只有一次性返回:检查请求头Accept是否包含text/event-stream。Streamable HTTP 服务端会根据 Accept 头决定用普通 JSON 还是 SSE 返回。另外keep-alive-interval只影响 SSE 长连接的心跳,不影响单次请求的流式行为,别把它当成流式开关。
模型调用报 401 或地址错误:检查base-url是不是https://taotoken.net/api,注意 API 地址后面不要带多余路径或参数。api-key确认环境变量注入成功,可以在启动日志里打印一下配置(别打印 Key 本身)。
保活心跳没生效:目前 Streamable HTTP 的保活只对 SSE 那条监听连接生效,普通 POST 请求不会触发。如果你期望每个请求都有心跳,那是理解偏差,不是配置问题。
6. 把通道和 Key 管起来
服务端跑通之后,下一步就是把模型调用的 Key 和通道真正管起来。TaoToken 的控制台可以创建和管理 API Key,建议按环境(开发/测试/生产)分开建 Key,别一个 Key 到处用。创建入口在 https://taotoken.net/api-keys ,接入文档在 https://taotoken.net/doc ,里面有各语言和框架的接入示例,Spring AI 的配置方式也能对上。
如果你后面要做长期编码任务或者 Agent 类的持续调用,可以看下 Coding Plan,它更适合高频、长周期的模型调用场景:https://taotoken.net/coding-plan 。想先在网页上直接验证模型通不通,用模型对话页面最快:https://taotoken.net/chat 。控制台总入口是 https://taotoken.net/console 。
我自己的习惯是:MCP 服务端里所有模型调用都走同一套base-url+ 环境变量 Key,工具逻辑里不出现任何硬编码密钥。这样换模型、换 Key、加环境,都只动配置不动代码。流式这块,先用 curl 把initialize和tools/call两步跑通,再去接客户端,能省掉一大半联调时间。