設(shè)計(jì)深度剖析 用 Spring AI + MCP 四步實(shí)現(xiàn) Agent 智能體開(kāi)發(fā)(TaoToken 統(tǒng)一 Key 接入))
1. 為什么 Java 后端需要認(rèn)真對(duì)待 MCP 協(xié)議分層架構(gòu)如果你最近在 Java 技術(shù)群里看到有人討論 MCP大概率會(huì)同時(shí)看到兩個(gè)詞Agent 和 Spring AI。MCP 全稱 Model Context Protocol是一套讓大模型與外部數(shù)據(jù)源、工具之間用標(biāo)準(zhǔn)方式對(duì)話的協(xié)議。它能做什么簡(jiǎn)單說(shuō)就是把「模型調(diào)用工具」這件事從各家私有 Function Call 格式里抽出來(lái)變成一套 Client 與 Server 之間的通用契約。適合誰(shuí)適合手里已經(jīng)有 Spring Boot 服務(wù)、想把內(nèi)部業(yè)務(wù)能力訂單查詢、知識(shí)庫(kù)檢索、工單系統(tǒng)暴露給大模型調(diào)用的 Java 后端開(kāi)發(fā)者。我先把 MCP 的分層講清楚不然后面寫(xiě)配置會(huì)一頭霧水。MCP 在架構(gòu)上分三層Hosts、Clients、Servers。Host 是最終面向用戶的宿主應(yīng)用比如 IDE 插件、桌面客戶端、你自己的 Agent 服務(wù)Client 是協(xié)議客戶端與某個(gè) Server 保持一對(duì)一連接負(fù)責(zé)把 Host 的意圖翻譯成 MCP 消息Server 是輕量程序把一組 Tools、Resources、Prompts 按協(xié)議暴露出來(lái)。傳輸層目前主流兩類Stdio本地進(jìn)程標(biāo)準(zhǔn)輸入輸出通信和 HTTP SSE遠(yuǎn)程服務(wù)走 HTTP 長(zhǎng)連接推送事件。放到 Java 場(chǎng)景里這套分層對(duì)應(yīng)得很自然。你的 Spring Boot 應(yīng)用既可以當(dāng) MCP Server把 Service 方法注冊(cè)成 Tool也可以當(dāng) MCP Client在 Agent 里連接別人的 Server。工作模式是六步閉環(huán)啟動(dòng)時(shí)讀取配置拉起所有 Server 并收集 Tools 清單用戶對(duì)話時(shí)把 Tools 的名稱、描述、參數(shù)定義一起交給大模型模型做意圖識(shí)別返回要調(diào)用的工具名和參數(shù)Client 找到對(duì)應(yīng) Server 執(zhí)行執(zhí)行結(jié)果回填給模型模型輸出最終答復(fù)。你可以把 MCP 理解成給模型配了一個(gè)「跑腿專員」模型只負(fù)責(zé)下指令和驗(yàn)收具體干活的是 Server。真正落地時(shí)還有一個(gè)繞不開(kāi)的問(wèn)題模型側(cè)的統(tǒng)一接入。不同模型對(duì) Function Call 的支持程度參差不齊有的模型干脆不支持工具調(diào)用這時(shí)候 Agent 的工具路由就會(huì)斷。我的做法是把模型通道統(tǒng)一收口到一個(gè)兼容 OpenAI 協(xié)議的服務(wù)上TaoToken 就是這樣一個(gè)入口一個(gè) Key 覆蓋多家模型Agent 側(cè)只認(rèn)一套 Base URL 和 Model ID換模型不用改代碼。官網(wǎng)在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 后面配置里會(huì)反復(fù)用到。這一節(jié)先把認(rèn)知打底MCP 解決的是 Client 與 Server 的數(shù)據(jù)交互標(biāo)準(zhǔn)化模型與 Tool 的對(duì)接質(zhì)量則取決于模型本身和你的接入通道。兩條線分開(kāi)看架構(gòu)就不會(huì)亂。1.1 三層模塊與兩類傳輸?shù)膶?duì)應(yīng)關(guān)系把三層模塊和兩類傳輸畫(huà)成一張對(duì)照表配置時(shí)對(duì)著填就行模塊職責(zé)Java 側(cè)對(duì)應(yīng)傳輸選擇建議Host面向用戶編排對(duì)話你的 Agent 服務(wù) / 客戶端不直接涉及傳輸Client與 Server 1:1 連接Spring AI MCP Client本地調(diào)試用 Stdio線上用 SSEServer暴露 Tools/Resourcesspring-ai-mcp-server starter本地 jar 用 Stdio獨(dú)立服務(wù)用 SSEStdio 的優(yōu)點(diǎn)是零網(wǎng)絡(luò)配置進(jìn)程起來(lái)就能通缺點(diǎn)是只能本機(jī)、生命周期跟著 Host 走SSE 的優(yōu)點(diǎn)是跨機(jī)器、可獨(dú)立部署擴(kuò)容缺點(diǎn)是要處理連接保活和鑒權(quán)。我一般開(kāi)發(fā)階段用 Stdio 快速驗(yàn)證 Tool 定義對(duì)不對(duì)聯(lián)調(diào)階段切 SSE 驗(yàn)證網(wǎng)絡(luò)鏈路。1.2 六步工作模式里最容易出錯(cuò)的環(huán)節(jié)六步里第 2 步和第 3 步是事故高發(fā)區(qū)。第 2 步要把所有 Tools 的描述塞進(jìn)上下文Tool 描述寫(xiě)得含糊模型就選錯(cuò)工具參數(shù)定義類型不嚴(yán)謹(jǐn)模型會(huì)傳錯(cuò)格式。第 3 步模型返回的調(diào)用請(qǐng)求是結(jié)構(gòu)化 JSON如果你的 Client 解析層對(duì)字段名大小寫(xiě)、可選參數(shù)處理不嚴(yán)就會(huì)出現(xiàn)「模型明明調(diào)了但執(zhí)行層沒(méi)反應(yīng)」。所以 Tool 的 description 和 ToolParam 描述要當(dāng)成接口文檔來(lái)寫(xiě)別糊弄。2. TaoToken 統(tǒng)一 Key 的前置準(zhǔn)備與模型通道配置在寫(xiě) MCP Server 之前先把模型通道準(zhǔn)備好否則 Agent 跑起來(lái)沒(méi)有大腦。TaoToken 的定位是統(tǒng)一模型接入層兼容 OpenAI 風(fēng)格的接口協(xié)議你拿到一個(gè) API Key 之后Agent 側(cè)只需要配置 Base URL、Key、Model ID 三件套。這一步不涉及任何復(fù)雜網(wǎng)絡(luò)設(shè)置就是標(biāo)準(zhǔn)的 HTTP 調(diào)用。先去控制臺(tái)創(chuàng)建 Key。打開(kāi) https://taotoken.net/api-keys 登錄后新建一個(gè) Key復(fù)制出來(lái)保存好頁(yè)面只展示一次。然后確認(rèn)你要用的模型 ID模型列表和在線試聊在 https://taotoken.net/models 建議先用一個(gè)支持工具調(diào)用的模型做 Agent 驗(yàn)證別一上來(lái)就挑不支持 Function Call 的模型否則你會(huì)誤以為是 MCP 配置錯(cuò)了。接入文檔在 https://taotoken.net/doc 里面有各語(yǔ)言的調(diào)用示例。Java 側(cè)我習(xí)慣用 Spring AI 的 OpenAI 兼容 starter把 base-url 指向 https://taotoken.net/api api-key 填剛創(chuàng)建的 Keymodel 填模型 ID。這里有個(gè)細(xì)節(jié)base-url 末尾不要多加/v1之外的路徑Spring AI 會(huì)自動(dòng)拼接/chat/completions多寫(xiě)一段就會(huì) 404。配置寫(xiě)進(jìn)application.yml形如spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: your-model-id temperature: 0.3Key 不要硬編碼進(jìn)倉(cāng)庫(kù)用環(huán)境變量注入。本地啟動(dòng)前export TAOTOKEN_API_KEYsk-xxxCI 里用密鑰管理。我踩過(guò)的坑是 Key 復(fù)制時(shí)帶了首尾空格請(qǐng)求直接 401排查了半小時(shí)才發(fā)現(xiàn)是粘貼問(wèn)題所以拿到 Key 后先echo -n檢查一下長(zhǎng)度。模型通道驗(yàn)證很簡(jiǎn)單用 curl 打一發(fā)curl https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:your-model-id,messages:[{role:user,content:回復(fù) ok}]}返回里有choices[0].message.content就說(shuō)明通道通了。這一步過(guò)了再往下寫(xiě) MCP能省掉大量「到底是模型問(wèn)題還是協(xié)議問(wèn)題」的扯皮。2.1 Key 權(quán)限與額度的心智模型一個(gè) Key 對(duì)應(yīng)一個(gè)賬戶下的調(diào)用憑證額度按模型計(jì)費(fèi)控制臺(tái)能看到用量。建議給 Agent 單獨(dú)建一個(gè) Key別和線上業(yè)務(wù)混用出問(wèn)題好定位也好吊銷。如果團(tuán)隊(duì)多人協(xié)作每人一個(gè) Key別共享日志里能區(qū)分調(diào)用來(lái)源。2.2 模型選擇與工具調(diào)用能力不是所有模型都擅長(zhǎng)工具調(diào)用。做 MCP Agent 驗(yàn)證時(shí)優(yōu)先選文檔里標(biāo)注支持 Function Call / Tool Use 的模型。模型 ID 填錯(cuò)的表現(xiàn)是 404 或model not found和 Key 錯(cuò)誤的 401 要區(qū)分開(kāi)。切換模型只改model字段Base URL 和 Key 不動(dòng)這就是統(tǒng)一通道的價(jià)值。3. Spring AI MCP 四步實(shí)現(xiàn) Agent 的可復(fù)制配置這一節(jié)是主體四步走完你本地就能跑起一個(gè)能調(diào)工具的 Agent。依賴用 Spring AI 的 MCP Server WebMVC starter 和 OpenAI 兼容 starterMaven 里加dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-webmvc-spring-boot-starter/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency第一步寫(xiě) Tool。建一個(gè) Service用Tool標(biāo)注方法ToolParam標(biāo)注參數(shù)。描述要寫(xiě)清楚「這個(gè)工具干什么、什么時(shí)候用、參數(shù)是什么含義」模型靠這段文字做路由。Service public class OrderQueryService { Tool(description 根據(jù)訂單號(hào)查詢訂單狀態(tài)用戶詢問(wèn)訂單進(jìn)度時(shí)調(diào)用) public String queryOrderStatus( ToolParam(description 訂單號(hào)純數(shù)字字符串) String orderNo) { // 這里替換成你的真實(shí)業(yè)務(wù)查詢 return 訂單 orderNo 狀態(tài)已發(fā)貨預(yù)計(jì) 2 天內(nèi)送達(dá); } }第二步注冊(cè) ToolCallbackProvider。把 Service 里的 Tool 方法暴露給 MCP 框架Configuration public class McpServerConfig { Bean public ToolCallbackProvider orderTools(OrderQueryService service) { return MethodToolCallbackProvider.builder() .toolObjects(service) .build(); } }第三步配置 MCP Server 傳輸方式。本地調(diào)試用 Stdio在application.yml里開(kāi)spring: ai: mcp: server: name: order-mcp-server version: 1.0.0 stdio: true如果要走 SSE 給遠(yuǎn)程 Client 用改成stdio: false并配置端口Client 側(cè)用 SSE 地址連接。兩種方式 Tool 定義完全一樣只換傳輸。第四步寫(xiě) Client 側(cè) Agent 調(diào)用。在 Agent 服務(wù)里配置 MCP Client 連接上面的 Server同時(shí)把模型通道指向 TaoToken。Client 配置片段JSON 形式供支持 mcpServers 配置的宿主使用{ mcpServers: { order-mcp: { isActive: true, command: java, args: [ -Dspring.ai.mcp.server.stdiotrue, -jar, /path/to/order-mcp-server-0.0.1.jar ] } } }Agent 側(cè)三件套配置Base URL Key Model ID必須齊全缺一個(gè)都跑不通spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: your-model-id四步完成后Agent 的調(diào)用鏈?zhǔn)怯脩籼釂?wèn) → 模型判斷需要查訂單 → 返回工具名和訂單號(hào) → MCP Client 轉(zhuǎn)發(fā)給 Server → Server 執(zhí)行queryOrderStatus→ 結(jié)果回填模型 → 模型組織自然語(yǔ)言答復(fù)。整條鏈路里TaoToken 只負(fù)責(zé)模型推理這一段MCP 負(fù)責(zé)工具執(zhí)行這一段職責(zé)清晰。3.1 Stdio 與 SSE 的配置切換要點(diǎn)Stdio 模式下 Server 是被 Client 拉起的子進(jìn)程jar 路徑必須絕對(duì)路徑相對(duì)路徑在客戶端工作目錄變化時(shí)會(huì)找不到。SSE 模式下 Server 獨(dú)立啟動(dòng)Client 配url而不是command還要注意防火墻和跨域。切換時(shí)只改傳輸段Tool 代碼零改動(dòng)這是 MCP 分層帶來(lái)的好處。3.2 工具描述寫(xiě)不好的典型后果描述寫(xiě)成「查詢訂單」模型可能在你問(wèn)「訂單什么時(shí)候到」時(shí)不調(diào)用它因?yàn)樗恢肋@工具能回答時(shí)效問(wèn)題。改成「根據(jù)訂單號(hào)查詢訂單狀態(tài)和預(yù)計(jì)送達(dá)時(shí)間用戶詢問(wèn)訂單進(jìn)度、物流、到貨時(shí)間時(shí)調(diào)用」命中率立刻上來(lái)。參數(shù)描述同理寫(xiě)「訂單號(hào)純數(shù)字字符串」比寫(xiě)「訂單號(hào)」少很多格式錯(cuò)誤。4. 本地調(diào)用驗(yàn)證與預(yù)期返回結(jié)果配置寫(xiě)完必須驗(yàn)證不然你不知道是模型沒(méi)調(diào)工具還是工具執(zhí)行失敗。驗(yàn)證分兩層先驗(yàn) MCP Server 的 Tool 能被列出再驗(yàn) Agent 端到端能調(diào)通。第一層用 MCP Inspector 或者任意支持 MCP 的客戶端連上你的 Server看 Tools 列表里有沒(méi)有queryOrderStatus參數(shù) schema 是否正確。Stdio 模式下可以直接用官方 inspector 命令拉起 jar 檢查。這一步過(guò)了說(shuō)明 Server 側(cè)沒(méi)問(wèn)題。第二層起 Agent 服務(wù)發(fā)一句自然語(yǔ)言「幫我查一下訂單 20240512001 到哪了」。預(yù)期行為是日志里出現(xiàn)工具調(diào)用記錄參數(shù)orderNo20240512001然后模型返回類似「訂單 20240512001 狀態(tài)已發(fā)貨預(yù)計(jì) 2 天內(nèi)送達(dá)」的答復(fù)。如果模型直接編造答案而沒(méi)調(diào)工具說(shuō)明 Tool 描述或模型工具調(diào)用能力有問(wèn)題如果調(diào)了工具但報(bào)錯(cuò)看 Server 日志里的異常棧。用 curl 直接驗(yàn)證模型通道和工具調(diào)用意圖也可以請(qǐng)求體里帶上 tools 定義觀察返回的tool_calls字段curl https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: your-model-id, messages: [{role:user,content:查訂單 20240512001}], tools: [{ type: function, function: { name: queryOrderStatus, description: 根據(jù)訂單號(hào)查詢訂單狀態(tài), parameters: { type: object, properties: {orderNo: {type: string}}, required: [orderNo] } } }] }返回里出現(xiàn)tool_calls且function.name為queryOrderStatus說(shuō)明模型側(cè)工具路由正常剩下的就是 Client 執(zhí)行層的事。這個(gè) curl 是我常用的「分段定位法」能把模型問(wèn)題和協(xié)議問(wèn)題切開(kāi)。4.1 成功結(jié)果的判定標(biāo)準(zhǔn)端到端成功有三個(gè)標(biāo)志日志有工具調(diào)用入?yún)⒊鰠ⅰ⒛P妥罱K答復(fù)包含工具返回的真實(shí)數(shù)據(jù)、沒(méi)有異常棧。三者缺一按下一節(jié)的排查表定位。4.2 驗(yàn)證順序建議先 curl 驗(yàn)?zāi)P屯ǖ涝?inspector 驗(yàn) Server Tools最后端到端。順序反了會(huì)在多層之間來(lái)回猜浪費(fèi)時(shí)間。5. 常見(jiàn)報(bào)錯(cuò)排查對(duì)照表下面這些是我和周圍朋友實(shí)際撞過(guò)的報(bào)錯(cuò)按現(xiàn)象對(duì)照處理報(bào)錯(cuò)現(xiàn)象大概率原因處理動(dòng)作401 UnauthorizedKey 錯(cuò)誤、帶空格、環(huán)境變量未注入echo -n $TAOTOKEN_API_KEY檢查重新復(fù)制 Keylocal proxy failed / 連接失敗base-url 寫(xiě)錯(cuò)或網(wǎng)絡(luò)不通確認(rèn) base-url 為 https://taotoken.net/api curl 直連測(cè)試reading choices 解析異常返回體不是預(yù)期 JSON多為路徑拼錯(cuò)打到錯(cuò)誤端點(diǎn)檢查 base-url 是否多寫(xiě)/v1/chat/completionsmodel not foundModel ID 拼錯(cuò)或該模型未開(kāi)通到模型列表頁(yè)核對(duì) IDOAuth / 鑒權(quán)失敗用了錯(cuò)誤的鑒權(quán)頭或 Key 類型確認(rèn)Authorization: Bearer格式工具未被調(diào)用Tool 描述含糊、模型不支持工具調(diào)用改描述換支持 Function Call 的模型Stdio 啟動(dòng)失敗jar 路徑非絕對(duì)路徑、Java 版本不符用絕對(duì)路徑核對(duì) JDK 版本reading choices這個(gè)報(bào)錯(cuò)特別典型本質(zhì)是客戶端按 OpenAI 格式解析choices字段但請(qǐng)求打到了非 chat 端點(diǎn)或者返回了錯(cuò)誤頁(yè)。把 base-url 收斂到https://taotoken.net/api讓框架自己拼路徑基本能消掉。OAuth 相關(guān)報(bào)錯(cuò)多出現(xiàn)在你誤用了需要交互登錄的鑒權(quán)方式API Key 場(chǎng)景就是 Bearer Token別混。401 和 403 要分開(kāi)看401 是憑證不對(duì)403 是憑證對(duì)但沒(méi)權(quán)限后者去控制臺(tái)看模型是否開(kāi)通。5.1 工具調(diào)用成功但結(jié)果為空模型調(diào)了工具Server 也執(zhí)行了但返回空字符串。檢查業(yè)務(wù)方法是不是查不到數(shù)據(jù)返回了 nullMCP 層對(duì) null 的處理可能變成空串。在 Tool 方法里做兜底查不到返回「未查詢到該訂單」別返回 null。5.2 連接偶發(fā)斷開(kāi)SSE 模式下長(zhǎng)連接會(huì)被中間設(shè)備掐斷加心跳和重連。Stdio 模式偶發(fā)斷開(kāi)多半是子進(jìn)程崩潰看 Server 的 stderr 日志。6. 從單 Agent 到多工具編排的下一步單工具跑通之后下一步是把多個(gè) Server 掛到同一個(gè) Agent 上比如訂單 Server、知識(shí)庫(kù) Server、工單 Server 各管一攤Client 側(cè)配置多個(gè) mcpServers 條目模型根據(jù)描述自動(dòng)路由。這時(shí)候 Tool 描述的區(qū)分度就格外重要兩個(gè)工具描述都寫(xiě)「查詢信息」模型必然選錯(cuò)。模型通道這邊統(tǒng)一 Key 的好處開(kāi)始顯現(xiàn)換模型只改一個(gè)字段多 Agent 共用一套憑證用量在控制臺(tái)集中看。需要長(zhǎng)期跑編碼類 Agent 或者多輪工具編排的可以看 Coding Plan 頁(yè)面 https://taotoken.net/coding-plan 按套餐走比按次調(diào)用更可控。接入細(xì)節(jié)隨時(shí)查文檔 https://taotoken.net/doc 模型試聊在 https://taotoken.net/models Key 管理在 https://taotoken.net/api-keys 。最后給一個(gè)實(shí)操建議把 Tool 的 description 當(dāng)成給新同事看的接口文檔來(lái)寫(xiě)把每次模型選錯(cuò)工具的 case 記下來(lái)反推描述哪里含糊迭代幾輪之后 Agent 的工具命中率會(huì)明顯穩(wěn)定。MCP 的分層架構(gòu)保證了 Tool 代碼和傳輸方式解耦你可以先用 Stdio 把業(yè)務(wù)邏輯磨順再切 SSE 上生產(chǎn)改動(dòng)量極小。