- Spring AI 開發(fā) MCP Server(Streamable HTTP)完整開發(fā)與測(cè)試:把 endpoint 改到 TaoToken)
1. 從 stdio 到 Streamable HTTP為什么要把 MCP Server 的 endpoint 改到 TaoToken如果你已經(jīng)用 Spring AI 寫過 stdio 版本的 MCP Server大概率會(huì)遇到一個(gè)尷尬本地 IDE 能跑但一旦想讓遠(yuǎn)程客戶端、團(tuán)隊(duì)其他成員或者 CI 環(huán)境調(diào)用stdio 的進(jìn)程間通信模型就完全不夠用了。stdio 的本質(zhì)是 MCP Client 和 MCP Server 跑在同一臺(tái)主機(jī)上靠標(biāo)準(zhǔn)輸入輸出做 JSON-RPC 2.0 的消息交換適合本地工具接入但沒法跨網(wǎng)絡(luò)。Streamable HTTP 就是來解決這個(gè)問題的。它基于 HTTP 協(xié)議MCP Client 可以遠(yuǎn)程調(diào)用 MCP Server 暴露的 endpoint服務(wù)端通過流式響應(yīng)把工具調(diào)用結(jié)果推回來。相比早期的 SSE 方案Streamable HTTP 的通信模型更統(tǒng)一新版本的客戶端和服務(wù)端都更偏向它。Spring AI 從 1.1.x 開始對(duì) Streamable HTTP 的支持已經(jīng)比較完整依賴是spring-ai-starter-mcp-server-webmvc底層走 Servlet 棧。那為什么要把 endpoint 改到 TaoToken因?yàn)?MCP Server 本身只負(fù)責(zé)暴露工具真正要驗(yàn)證工具調(diào)用鏈路是否正常需要一個(gè)能發(fā)起模型請(qǐng)求的客戶端。TaoToken 提供統(tǒng)一的 API endpoint兼容主流模型調(diào)用格式你可以把 MCP Client 的模型請(qǐng)求指向 TaoToken讓模型來決定調(diào)用哪個(gè)工具、傳什么參數(shù)然后觀察 Streamable HTTP 的流式返回是否正常。這樣整條鏈路——模型決策、工具調(diào)用、流式響應(yīng)——就都能在真實(shí)請(qǐng)求下驗(yàn)證。這篇面向的是已經(jīng)了解 MCP 基本概念、想動(dòng)手把 Spring AI MCP Server 跑起來并完成端到端測(cè)試的 Java 開發(fā)者。我會(huì)從依賴引入開始給出可復(fù)制的application.yml、工具注冊(cè)代碼、MCP 客戶端配置片段然后演示把 endpoint 改到 TaoToken 后用 curl 和單元測(cè)試驗(yàn)證工具調(diào)用與流式返回。整個(gè)過程我試過幾輪踩過的坑會(huì)放在排障章節(jié)里。2. TaoToken 前置準(zhǔn)備API Key、Base URL 與模型 ID 三件套在寫代碼之前先把 TaoToken 這邊的接入信息準(zhǔn)備好。不管你是用 Cline、Claude Code 還是自己寫的 MCP Client核心就是三件套Base URL、API Key、Model ID。這三個(gè)東西缺一個(gè)請(qǐng)求就會(huì)在鑒權(quán)或路由階段掛掉。Base URL 用https://taotoken.net/api注意這個(gè)地址不帶任何查詢參數(shù)直接作為 API 根路徑。API Key 需要到控制臺(tái)創(chuàng)建路徑是 console 頁(yè)面下的 api-keys 管理。創(chuàng)建的時(shí)候建議按項(xiàng)目命名比如spring-ai-mcp-demo方便后面排查是哪個(gè) key 在調(diào)用。Model ID 根據(jù)你要用的模型填比如claude-sonnet-4-20250514這類標(biāo)識(shí)具體以模型列表頁(yè)為準(zhǔn)。如果你用的是 Claude Code 這類工具配置方式會(huì)略有不同。Claude Code 的接入文檔在 doc 頁(yè)面有詳細(xì)說明核心是把 Base URL 指向 TaoToken 的 API 地址然后填入 Key。對(duì)于 Spring AI 的 MCP Client我們是在application.yml里配置spring.ai.openai.base-url和spring.ai.openai.api-key模型名通過spring.ai.openai.chat.options.model指定。這里有個(gè)容易混淆的點(diǎn)MCP Server 的 endpoint 和模型 API 的 endpoint 是兩個(gè)不同的東西。MCP Server 暴露的是/mcp這個(gè)路徑供 MCP Client 調(diào)用工具而 TaoToken 的/api是模型推理接口供 MCP Client 內(nèi)部的 ChatClient 調(diào)用。兩者不要搞混。我們?cè)赼pplication-http.yml里配置的mcp-endpoint: /mcp是前者TaoToken 的 Base URL 是后者。另外如果你打算長(zhǎng)期跑編碼類 Agent 任務(wù)可以了解下 Coding Plan它針對(duì)高頻編碼場(chǎng)景做了額度優(yōu)化。單純驗(yàn)證工具調(diào)用的話按量調(diào)用模型對(duì)話接口就夠了。需要先拿到 Key 的話直接去 API Keys 頁(yè)面創(chuàng)建然后對(duì)照接入文檔確認(rèn)參數(shù)格式。3. 可復(fù)制配置application.yml、工具注冊(cè)與 MCP 客戶端片段這一節(jié)給出完整的可復(fù)制配置。工程結(jié)構(gòu)是標(biāo)準(zhǔn)的 Spring Boot Maven 項(xiàng)目Java 17Spring Boot 3.4.xSpring AI 1.1.4。pom 里需要引入spring-ai-starter-mcp-server-webmvc來啟用 Streamable HTTP 模式同時(shí)引入spring-ai-starter-model-openai作為 MCP Client 側(cè)的模型調(diào)用依賴。先看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這兩行前者指定傳輸協(xié)議后者定義 MCP Client 要訪問的路徑。啟動(dòng)后完整的 endpoint 是http://localhost:8080/mcp。工具類用Tool和ToolParam注解標(biāo)記Service public class WeatherService { Tool(name getWeather, description 根據(jù)城市名稱獲取天氣信息) public String getWeather( ToolParam(description 城市名稱, required true) String city) { return 城市: city , 溫度: 25°C, 天氣: 晴; } Tool(name sumNumbers, description 計(jì)算兩個(gè)數(shù)字之和) public String sumNumbers( ToolParam(description 第一個(gè)數(shù)字) double a, ToolParam(description 第二個(gè)數(shù)字) double b) { return String.valueOf(a b); } }工具注冊(cè)配置類把WeatherService里的Tool方法注冊(cè)為 MCP 工具Configuration public class ToolRegistrationConfiguration { Autowired private WeatherService weatherService; Bean public ToolCallbackProvider toolCallbackProvider() { return MethodToolCallbackProvider.builder() .toolObjects(new Object[]{weatherService}) .build(); } }接下來是 MCP Client 側(cè)的配置把模型請(qǐng)求指向 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從環(huán)境變量讀取避免硬編碼。mcp.client.streamable-http.connections下面配置 MCP Server 的地址demo-server是連接名可以自定義。這樣 MCP Client 啟動(dòng)時(shí)會(huì)自動(dòng)連接本地 MCP Server拉取工具列表然后在模型請(qǐng)求中把工具定義一起發(fā)給 TaoToken。如果你用的是 Cline 或 Claude Code 這類外部客戶端配置方式類似核心還是 Base URL、Key、Model ID 三件套加上 MCP Server 的 URL。Claude Code 的配置在 doc 里有說明Cline 的 MCP 配置在 settings 里填http://localhost:8080/mcp即可。4. 驗(yàn)證請(qǐng)求curl 與單元測(cè)試檢查工具調(diào)用和流式返回配置寫完之后先啟動(dòng) MCP Server。用mvn spring-boot:run -Dspring-boot.run.profileshttp啟動(dòng)看到 start輸出說明服務(wù)起來了。然后分兩步驗(yàn)證先用 curl 直接打 MCP endpoint確認(rèn) JSON-RPC 握手和工具列表正常再用單元測(cè)試走完整的模型調(diào)用鏈路。curl 驗(yàn)證 MCP 初始化請(qǐng)求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} } }正常返回會(huì)包含serverInfo和capabilities說明 Streamable HTTP 鏈路通了。注意Accept頭必須同時(shí)包含application/json和text/event-stream否則服務(wù)端可能拒絕流式響應(yīng)。接著查工具列表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: {} }返回里應(yīng)該能看到getWeather和sumNumbers兩個(gè)工具的定義包括參數(shù) schema。如果這里返回空列表說明工具注冊(cè)配置沒生效檢查ToolCallbackProvider是否被 Spring 掃描到。單元測(cè)試用 Spring AI 的ChatClient發(fā)起帶工具的請(qǐng)求驗(yàn)證模型是否會(huì)調(diào)用工具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(); } }跑這個(gè)測(cè)試之前確保TAOTOKEN_API_KEY環(huán)境變量已設(shè)置。測(cè)試通過的標(biāo)準(zhǔn)是模型返回了包含天氣信息的文本并且日志里能看到工具調(diào)用的記錄。如果模型直接回答而沒有調(diào)用工具可能是工具描述不夠清晰或者模型不支持 function calling。流式返回的驗(yàn)證用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(流式片段數(shù): chunks.size()); assertThat(chunks).isNotEmpty(); }流式返回正常的話chunks會(huì)有多個(gè)片段而不是一次性返回完整文本。這一步驗(yàn)證的是 Streamable HTTP 的流式推送能力如果只收到一個(gè)片段說明流式配置有問題。5. 常見報(bào)錯(cuò)排查401、local proxy failed、reading choices 與 OAuth這一節(jié)對(duì)照幾個(gè)真實(shí)報(bào)錯(cuò)給出排查路徑。這些錯(cuò)誤我在調(diào)試過程中都遇到過按順序檢查基本能定位。401 Unauthorized最常見的原因是 API Key 沒設(shè)置或設(shè)置錯(cuò)了。檢查TAOTOKEN_API_KEY環(huán)境變量是否在當(dāng)前 shell 會(huì)話里生效echo $TAOTOKEN_API_KEY確認(rèn)一下。如果 Key 是對(duì)的檢查base-url是否寫成了https://taotoken.net/api少寫/api或者多寫斜杠都會(huì)導(dǎo)致鑒權(quán)失敗。另外注意 Key 有沒有多余空格從控制臺(tái)復(fù)制的時(shí)候容易帶上換行。local proxy failed / connection refused這個(gè)報(bào)錯(cuò)通常出現(xiàn)在 MCP Client 連接 MCP Server 的階段。檢查 MCP Server 是否真的在 8080 端口監(jiān)聽curl http://localhost:8080/mcp能不能通。如果 Server 沒起來檢查application-http.yml里的web-application-type: servlet有沒有配配成none的話 Web 容器不會(huì)啟動(dòng)。還有一種情況是端口被占用換個(gè)端口或者殺掉占用進(jìn)程。reading choices 相關(guān)報(bào)錯(cuò)這個(gè)一般出現(xiàn)在模型返回解析階段說明請(qǐng)求發(fā)出去了但響應(yīng)格式不對(duì)。檢查model參數(shù)是否填了 TaoToken 支持的模型 ID填錯(cuò)模型名會(huì)導(dǎo)致返回體里沒有choices字段。另外檢查spring.ai.openai.chat.options下的配置有沒有拼寫錯(cuò)誤比如temperature寫成temprature這種。OAuth / 鑒權(quán)頭缺失如果你用的是 Claude Code 或 Cline 這類客戶端報(bào) OAuth 相關(guān)錯(cuò)誤通常是客戶端配置里沒填 Key或者填到了錯(cuò)誤的字段。Claude Code 的配置參考 doc 頁(yè)面Cline 的 MCP 配置里 Base URL 和 Key 要分開填。對(duì)于 Spring AI 的 MCP Client鑒權(quán)是通過spring.ai.openai.api-key走的不需要額外配 OAuth。工具列表為空MCP Server 起來了但tools/list返回空。檢查ToolRegistrationConfiguration類是否在SpringBootApplication的掃描路徑下WeatherService是否被Service標(biāo)記。還有一個(gè)坑是Tool注解的name屬性如果和已有工具重名后面的會(huì)覆蓋前面的導(dǎo)致看起來少了工具。流式返回中斷stream()調(diào)用收到部分片段后斷開。檢查Accept頭是否包含text/event-stream以及服務(wù)端有沒有設(shè)置響應(yīng)超時(shí)。Spring MVC 的異步請(qǐng)求超時(shí)默認(rèn)是 30 秒長(zhǎng)任務(wù)需要調(diào)spring.mvc.async.request-timeout。6. 把 endpoint 改到 TaoToken 后的完整鏈路與后續(xù)方向整條鏈路跑通之后回顧一下數(shù)據(jù)流MCP Client 啟動(dòng)時(shí)連接本地 MCP Server 的/mcpendpoint拉取工具列表用戶發(fā)起請(qǐng)求時(shí)ChatClient 把用戶消息和工具定義一起發(fā)給 TaoToken 的/api接口TaoToken 返回模型決策如果決定調(diào)用工具M(jìn)CP Client 通過 Streamable HTTP 把工具調(diào)用請(qǐng)求發(fā)給 MCP ServerMCP Server 執(zhí)行工具方法把結(jié)果流式返回給 MCP ClientMCP Client 再把工具結(jié)果發(fā)給模型生成最終回答。這個(gè)鏈路里TaoToken 承擔(dān)的是模型推理角色MCP Server 承擔(dān)的是工具執(zhí)行角色兩者通過 MCP Client 串聯(lián)。把 endpoint 改到 TaoToken 的意義在于你可以用統(tǒng)一的 API 接口驗(yàn)證不同模型對(duì)工具調(diào)用的支持情況而不需要為每個(gè)模型單獨(dú)適配。后續(xù)如果要擴(kuò)展幾個(gè)方向可以考慮一是增加更多工具比如數(shù)據(jù)庫(kù)查詢、文件操作注意 MCP 的安全邊界不要讓工具直接訪問生產(chǎn)庫(kù)二是把 MCP Server 部署到遠(yuǎn)程用真實(shí)的 HTTP 地址替代 localhost驗(yàn)證跨網(wǎng)絡(luò)場(chǎng)景下的流式穩(wěn)定性三是接入多個(gè) MCP Server在 MCP Client 配置里加多個(gè) connection讓模型自己選擇調(diào)用哪個(gè)服務(wù)的工具。需要?jiǎng)?chuàng)建 API Key 的話去 API Keys 頁(yè)面配置細(xì)節(jié)對(duì)照接入文檔想先試試模型對(duì)話效果可以直接在模型對(duì)話頁(yè)面發(fā)幾條帶工具調(diào)用的請(qǐng)求觀察返回結(jié)構(gòu)。長(zhǎng)期跑編碼 Agent 任務(wù)的話Coding Plan 的額度模型更適合高頻調(diào)用場(chǎng)景。