- Spring AI 开发 MCP Server(Streamable HTTP)完整开发与测试:把 endpoint 改到 TaoToken)
1. 从 stdio 到 Streamable HTTP为什么要把 MCP Server 的 endpoint 改到 TaoToken如果你已经用 Spring AI 写过 stdio 版本的 MCP Server大概率会遇到一个尴尬本地 IDE 能跑但一旦想让远程客户端、团队其他成员或者 CI 环境调用stdio 的进程间通信模型就完全不够用了。stdio 的本质是 MCP Client 和 MCP Server 跑在同一台主机上靠标准输入输出做 JSON-RPC 2.0 的消息交换适合本地工具接入但没法跨网络。Streamable HTTP 就是来解决这个问题的。它基于 HTTP 协议MCP Client 可以远程调用 MCP Server 暴露的 endpoint服务端通过流式响应把工具调用结果推回来。相比早期的 SSE 方案Streamable HTTP 的通信模型更统一新版本的客户端和服务端都更偏向它。Spring AI 从 1.1.x 开始对 Streamable HTTP 的支持已经比较完整依赖是spring-ai-starter-mcp-server-webmvc底层走 Servlet 栈。那为什么要把 endpoint 改到 TaoToken因为 MCP Server 本身只负责暴露工具真正要验证工具调用链路是否正常需要一个能发起模型请求的客户端。TaoToken 提供统一的 API endpoint兼容主流模型调用格式你可以把 MCP Client 的模型请求指向 TaoToken让模型来决定调用哪个工具、传什么参数然后观察 Streamable HTTP 的流式返回是否正常。这样整条链路——模型决策、工具调用、流式响应——就都能在真实请求下验证。这篇面向的是已经了解 MCP 基本概念、想动手把 Spring AI MCP Server 跑起来并完成端到端测试的 Java 开发者。我会从依赖引入开始给出可复制的application.yml、工具注册代码、MCP 客户端配置片段然后演示把 endpoint 改到 TaoToken 后用 curl 和单元测试验证工具调用与流式返回。整个过程我试过几轮踩过的坑会放在排障章节里。2. TaoToken 前置准备API Key、Base URL 与模型 ID 三件套在写代码之前先把 TaoToken 这边的接入信息准备好。不管你是用 Cline、Claude Code 还是自己写的 MCP Client核心就是三件套Base URL、API Key、Model ID。这三个东西缺一个请求就会在鉴权或路由阶段挂掉。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数直接作为 API 根路径。API Key 需要到控制台创建路径是 console 页面下的 api-keys 管理。创建的时候建议按项目命名比如spring-ai-mcp-demo方便后面排查是哪个 key 在调用。Model ID 根据你要用的模型填比如claude-sonnet-4-20250514这类标识具体以模型列表页为准。如果你用的是 Claude Code 这类工具配置方式会略有不同。Claude Code 的接入文档在 doc 页面有详细说明核心是把 Base URL 指向 TaoToken 的 API 地址然后填入 Key。对于 Spring AI 的 MCP Client我们是在application.yml里配置spring.ai.openai.base-url和spring.ai.openai.api-key模型名通过spring.ai.openai.chat.options.model指定。这里有个容易混淆的点MCP Server 的 endpoint 和模型 API 的 endpoint 是两个不同的东西。MCP Server 暴露的是/mcp这个路径供 MCP Client 调用工具而 TaoToken 的/api是模型推理接口供 MCP Client 内部的 ChatClient 调用。两者不要搞混。我们在application-http.yml里配置的mcp-endpoint: /mcp是前者TaoToken 的 Base URL 是后者。另外如果你打算长期跑编码类 Agent 任务可以了解下 Coding Plan它针对高频编码场景做了额度优化。单纯验证工具调用的话按量调用模型对话接口就够了。需要先拿到 Key 的话直接去 API Keys 页面创建然后对照接入文档确认参数格式。3. 可复制配置application.yml、工具注册与 MCP 客户端片段这一节给出完整的可复制配置。工程结构是标准的 Spring Boot Maven 项目Java 17Spring Boot 3.4.xSpring AI 1.1.4。pom 里需要引入spring-ai-starter-mcp-server-webmvc来启用 Streamable HTTP 模式同时引入spring-ai-starter-model-openai作为 MCP Client 侧的模型调用依赖。先看application.yml的主配置spring: application: name: java-mcp-server-demo profiles: active: http ai: mcp: server: name: java-mcp-server-demo version: 0.0.1 type: SYNC instructions: | This server provides demo tools for Java MCP development. Use getWeather for city weather lookup and sumNumbers for calculation.然后是application-http.yml这是 Streamable HTTP 模式的核心配置spring: config: activate: on-profile: http main: web-application-type: servlet ai: mcp: server: protocol: STREAMABLE streamable-http: mcp-endpoint: /mcp server: port: 8080注意protocol: STREAMABLE和mcp-endpoint: /mcp这两行前者指定传输协议后者定义 MCP Client 要访问的路径。启动后完整的 endpoint 是http://localhost:8080/mcp。工具类用Tool和ToolParam注解标记Service public class WeatherService { Tool(name getWeather, description 根据城市名称获取天气信息) public String getWeather( ToolParam(description 城市名称, required true) String city) { return 城市: city , 温度: 25°C, 天气: 晴; } Tool(name sumNumbers, description 计算两个数字之和) public String sumNumbers( ToolParam(description 第一个数字) double a, ToolParam(description 第二个数字) double b) { return String.valueOf(a b); } }工具注册配置类把WeatherService里的Tool方法注册为 MCP 工具Configuration public class ToolRegistrationConfiguration { Autowired private WeatherService weatherService; Bean public ToolCallbackProvider toolCallbackProvider() { return MethodToolCallbackProvider.builder() .toolObjects(new Object[]{weatherService}) .build(); } }接下来是 MCP Client 侧的配置把模型请求指向 TaoToken。在application-client.yml里spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-sonnet-4-20250514 temperature: 0.7 mcp: client: streamable-http: connections: demo-server: url: http://localhost:8080/mcp这里base-url指向 TaoToken 的 API 地址api-key从环境变量读取避免硬编码。mcp.client.streamable-http.connections下面配置 MCP Server 的地址demo-server是连接名可以自定义。这样 MCP Client 启动时会自动连接本地 MCP Server拉取工具列表然后在模型请求中把工具定义一起发给 TaoToken。如果你用的是 Cline 或 Claude Code 这类外部客户端配置方式类似核心还是 Base URL、Key、Model ID 三件套加上 MCP Server 的 URL。Claude Code 的配置在 doc 里有说明Cline 的 MCP 配置在 settings 里填http://localhost:8080/mcp即可。4. 验证请求curl 与单元测试检查工具调用和流式返回配置写完之后先启动 MCP Server。用mvn spring-boot:run -Dspring-boot.run.profileshttp启动看到 start输出说明服务起来了。然后分两步验证先用 curl 直接打 MCP endpoint确认 JSON-RPC 握手和工具列表正常再用单元测试走完整的模型调用链路。curl 验证 MCP 初始化请求curl -X POST http://localhost:8080/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2024-11-05, capabilities: {}, clientInfo: {name: curl-test, version: 1.0} } }正常返回会包含serverInfo和capabilities说明 Streamable HTTP 链路通了。注意Accept头必须同时包含application/json和text/event-stream否则服务端可能拒绝流式响应。接着查工具列表curl -X POST http://localhost:8080/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 2, method: tools/list, params: {} }返回里应该能看到getWeather和sumNumbers两个工具的定义包括参数 schema。如果这里返回空列表说明工具注册配置没生效检查ToolCallbackProvider是否被 Spring 扫描到。单元测试用 Spring AI 的ChatClient发起带工具的请求验证模型是否会调用工具SpringBootTest ActiveProfiles(client) class McpClientIntegrationTest { Autowired private ChatClient.Builder chatClientBuilder; Test void shouldCallWeatherTool() { ChatClient chatClient chatClientBuilder.build(); String response chatClient.prompt() .user(北京今天天气怎么样) .call() .content(); System.out.println(模型返回: response); assertThat(response).isNotBlank(); } }跑这个测试之前确保TAOTOKEN_API_KEY环境变量已设置。测试通过的标准是模型返回了包含天气信息的文本并且日志里能看到工具调用的记录。如果模型直接回答而没有调用工具可能是工具描述不够清晰或者模型不支持 function calling。流式返回的验证用stream()方法Test void shouldStreamResponse() { ChatClient chatClient chatClientBuilder.build(); FluxString stream chatClient.prompt() .user(帮我算一下 12 加 30 等于多少) .stream() .content(); ListString chunks stream.collectList().block(); System.out.println(流式片段数: chunks.size()); assertThat(chunks).isNotEmpty(); }流式返回正常的话chunks会有多个片段而不是一次性返回完整文本。这一步验证的是 Streamable HTTP 的流式推送能力如果只收到一个片段说明流式配置有问题。5. 常见报错排查401、local proxy failed、reading choices 与 OAuth这一节对照几个真实报错给出排查路径。这些错误我在调试过程中都遇到过按顺序检查基本能定位。401 Unauthorized最常见的原因是 API Key 没设置或设置错了。检查TAOTOKEN_API_KEY环境变量是否在当前 shell 会话里生效echo $TAOTOKEN_API_KEY确认一下。如果 Key 是对的检查base-url是否写成了https://taotoken.net/api少写/api或者多写斜杠都会导致鉴权失败。另外注意 Key 有没有多余空格从控制台复制的时候容易带上换行。local proxy failed / connection refused这个报错通常出现在 MCP Client 连接 MCP Server 的阶段。检查 MCP Server 是否真的在 8080 端口监听curl http://localhost:8080/mcp能不能通。如果 Server 没起来检查application-http.yml里的web-application-type: servlet有没有配配成none的话 Web 容器不会启动。还有一种情况是端口被占用换个端口或者杀掉占用进程。reading choices 相关报错这个一般出现在模型返回解析阶段说明请求发出去了但响应格式不对。检查model参数是否填了 TaoToken 支持的模型 ID填错模型名会导致返回体里没有choices字段。另外检查spring.ai.openai.chat.options下的配置有没有拼写错误比如temperature写成temprature这种。OAuth / 鉴权头缺失如果你用的是 Claude Code 或 Cline 这类客户端报 OAuth 相关错误通常是客户端配置里没填 Key或者填到了错误的字段。Claude Code 的配置参考 doc 页面Cline 的 MCP 配置里 Base URL 和 Key 要分开填。对于 Spring AI 的 MCP Client鉴权是通过spring.ai.openai.api-key走的不需要额外配 OAuth。工具列表为空MCP Server 起来了但tools/list返回空。检查ToolRegistrationConfiguration类是否在SpringBootApplication的扫描路径下WeatherService是否被Service标记。还有一个坑是Tool注解的name属性如果和已有工具重名后面的会覆盖前面的导致看起来少了工具。流式返回中断stream()调用收到部分片段后断开。检查Accept头是否包含text/event-stream以及服务端有没有设置响应超时。Spring MVC 的异步请求超时默认是 30 秒长任务需要调spring.mvc.async.request-timeout。6. 把 endpoint 改到 TaoToken 后的完整链路与后续方向整条链路跑通之后回顾一下数据流MCP Client 启动时连接本地 MCP Server 的/mcpendpoint拉取工具列表用户发起请求时ChatClient 把用户消息和工具定义一起发给 TaoToken 的/api接口TaoToken 返回模型决策如果决定调用工具MCP Client 通过 Streamable HTTP 把工具调用请求发给 MCP ServerMCP Server 执行工具方法把结果流式返回给 MCP ClientMCP Client 再把工具结果发给模型生成最终回答。这个链路里TaoToken 承担的是模型推理角色MCP Server 承担的是工具执行角色两者通过 MCP Client 串联。把 endpoint 改到 TaoToken 的意义在于你可以用统一的 API 接口验证不同模型对工具调用的支持情况而不需要为每个模型单独适配。后续如果要扩展几个方向可以考虑一是增加更多工具比如数据库查询、文件操作注意 MCP 的安全边界不要让工具直接访问生产库二是把 MCP Server 部署到远程用真实的 HTTP 地址替代 localhost验证跨网络场景下的流式稳定性三是接入多个 MCP Server在 MCP Client 配置里加多个 connection让模型自己选择调用哪个服务的工具。需要创建 API Key 的话去 API Keys 页面配置细节对照接入文档想先试试模型对话效果可以直接在模型对话页面发几条带工具调用的请求观察返回结构。长期跑编码 Agent 任务的话Coding Plan 的额度模型更适合高频调用场景。