作實戰(zhàn):工具調(diào)用與上下文管理全解析)
1. 從項目背景說起MCP 到底解決了什么問題說到 MCPModel Context Protocol模型上下文協(xié)議我先講一個痛點(diǎn)場景。假設(shè)你手里有 5 個 AI 應(yīng)用想給每個應(yīng)用接上 3 類外部能力數(shù)據(jù)庫查詢、文件讀寫、第三方 API。如果老一套做法每個 AI 應(yīng)用要分別對接每一類工具那就是 5×3 15 套定制集成每套都要寫專門的適配代碼、維護(hù)獨(dú)立的鑒權(quán)邏輯、處理各自的報錯格式。代碼量上去了出問題的概率也上去了更要命的是只要某個工具端接口一改所有對接方都得跟著改一遍。MCP 就是來解決這個問題的。它的思路很像 USB 接口外部工具實現(xiàn)一個 MCP Server 來對外暴露能力AI 應(yīng)用端通過 MCP Client 連接這樣工具側(cè)只要做一次 MCP 化改造就能被所有支持 MCP 的 AI 應(yīng)用復(fù)用??康牟皇悄Хǘ且惶捉y(tǒng)一協(xié)議來約定“工具怎么描述自己”“請求怎么發(fā)送”“結(jié)果怎么返回”。我在這個項目里同時跑了三個角色基于大語言模型LLM構(gòu)建的智能對話應(yīng)用、一個環(huán)境信息查詢工具的 MCP Server、一個模擬高精度定位工具的 MCP Server。實際跑起來之后對 MCP Client 在整個鏈路中的位置、它跟 LLM 之間來回配合的方式才真正有了體感。先花半分鐘理一下角色分工LLM 是“大腦”負(fù)責(zé)理解用戶意圖、生成回復(fù)MCP Server 是“器官”負(fù)責(zé)提供數(shù)據(jù)和操作能力MCP Client 是“神經(jīng)”負(fù)責(zé)把大腦的指令翻譯成器官能執(zhí)行的操作再把結(jié)果傳回給大腦。這個位置很關(guān)鍵因為 LLM 本身不直接認(rèn)識 MCP Server 的工具Client 夾在中間所有信息必須經(jīng)過它來轉(zhuǎn)譯。這套架構(gòu)適合誰參考只要你在做基于大模型 API 的智能體應(yīng)用需要動態(tài)調(diào)用外部數(shù)據(jù)庫或接口企業(yè)內(nèi)部的知識庫問答需要接入查詢類工具自動化工作流需要讓模型“自己決定”調(diào)哪個工具、按什么順序調(diào)。以下內(nèi)容基于我實際跑通的一個模擬項目某跨平臺系統(tǒng)的 MCP 接入。核心就是 Client 跟一個環(huán)境查詢服務(wù)和一個模擬定位服務(wù)走了完整的“工具發(fā)現(xiàn) - 調(diào)用決策 - 結(jié)果回傳”鏈路。下面把流程拆開細(xì)講重點(diǎn)放在設(shè)計取舍和實操坑點(diǎn)上。2. 整體鏈路拆解Client、LLM、Server 三方怎么配合2.1 一次完整請求的生命周期把整個流程拉到時間軸上一次“用戶問問題 → 模型給出最終回答”的完整周期可以拆成下面七個步驟用戶提問 ↓ ① 應(yīng)用把用戶消息發(fā)給 LLM同時攜帶“可用工具清單”由 MCP Client 從各 Server 收集 ↓ ② LLM 判斷是否需要調(diào)用工具 ├─ 不需要 → 直接生成回答流程結(jié)束 └─ 需要 → 輸出一個結(jié)構(gòu)化指令工具名 參數(shù) ↓ ③ MCP Client 拿到結(jié)構(gòu)化指令 ↓ ④ Client 找到對應(yīng) MCP Server發(fā)起初始化執(zhí)行工具調(diào)用 ↓ ⑤ Server 執(zhí)行完將結(jié)果返回給 Client ↓ ⑥ Client 把工具結(jié)果打包成一條“工具回復(fù)消息”再次發(fā)給 LLM ↓ ⑦ LLM 基于工具結(jié)果生成最終回答這個模型跟普通對話不同不是“一問一答”而是“一問多輪”。第②步可能執(zhí)行多次比如模型先調(diào)了環(huán)境查詢接口拿到結(jié)果后覺得數(shù)據(jù)不夠又調(diào)了定位接口再綜合兩者輸出答案。這種多輪能力是 Agent 類應(yīng)用跟普通聊天機(jī)器人最大的區(qū)別。2.2 兩個必須理解的協(xié)議層概念MCP 協(xié)議在邏輯上分層實際代碼里我們需要區(qū)分清楚兩件事第一層客戶端到服務(wù)器Client ? Server這一層就是 MCP 協(xié)議本身負(fù)責(zé)工具注冊、調(diào)用、結(jié)果返回。通信格式是 JSON-RPC 2.0。你可以把它理解成 Client 和 Server 在“說 MCP 語言”。第二層客戶端到大模型Client ? LLM這一層走的是大模型供應(yīng)商自己的接口規(guī)范目前最常用的是 OpenAI 兼容格式。MCP Client 要把 MCP 工具元數(shù)據(jù)“翻譯”成 LLM 能理解的工具描述也就是 functions 或 tools 參數(shù)。我剛開始實現(xiàn)時踩過坑在 Client 里直接把 MCP 的工具描述原樣塞給 LLM結(jié)果模型經(jīng)常理解偏。后來才明白兩個協(xié)議層的字段映射需要做“翻譯”。MCP 工具描述里有 semanticVersion、schema 等字段而 LLM 的 tools 接口一般只需要 name、description、parameters。翻譯這一步如果少了后面協(xié)作就全是問題。2.3 為什么要把流程拆成“兩次上下文傳遞”跟只做一次 API 調(diào)用比MCP 協(xié)作流程本質(zhì)上是“多個上下文片段的有效拼接”第一段上下文用戶提問 工具清單第二段上下文第一段上下文 工具執(zhí)行結(jié)果 模型自我判斷。我在這套模擬項目里體會最深的一點(diǎn)是MCP Client 承擔(dān)的不是純轉(zhuǎn)發(fā)而是上下文管理。每一次工具調(diào)用后Client 把結(jié)果拼回對話歷史LLM 才能在這個“新上下文”基礎(chǔ)上繼續(xù)推理。如果 Client 不做這個拼接模型就成了只會發(fā)指令的瞎子這是很多簡化實現(xiàn)失敗的原因。3. 工具發(fā)現(xiàn)與元數(shù)據(jù)管理Client 的“軍火庫”3.1 從服務(wù)端拉取工具清單MCP Client 啟動后的第一件事就是跟每個已配置的 MCP Server 建立連接然后通過tools/list拉取該 Server 支持的工具信息。這一步可以對照數(shù)據(jù)庫的表結(jié)構(gòu)描述來理解Server 返回的不只是“工具名字”而是一個個描述清楚“叫什么、干什么用、需要哪些參數(shù)”的 JSON 結(jié)構(gòu)。實際項目中我配置了兩個 ServermcpServers: env-query: command: python args: [path/to/env_query_server.py] transport: stdio geo-simulator: command: python args: [path/to/geo_simulator_server.py] transport: stdio這是 MCP 官方常用的 stdio 傳輸方式Client 以子進(jìn)程方式啟動 Server 程序通過標(biāo)準(zhǔn)輸入輸出交換 JSON-RPC 消息。好處是本地部署沒有端口暴露進(jìn)程生命周期由 Client 托管缺點(diǎn)是如果 Server 有日志輸出別用 print要用專門的日志通道否則會污染協(xié)議棧。我剛開始調(diào)試時Server 里寫了個 print 調(diào)試信息結(jié)果 Client 解析 JSON 解析失敗排查了好一陣子。拉取到的工具描述長這樣{ tools: [ { name: get_environment_status, description: 獲取目標(biāo)區(qū)域的綜合環(huán)境狀態(tài)包括空氣質(zhì)量與氣候條件, inputSchema: { type: object, properties: { area_code: { type: string, description: 目標(biāo)區(qū)域代碼 } }, required: [area_code] } }, { name: simulate_geolocation, description: 基于指定條件生成模擬定位信息, inputSchema: { type: object, properties: { lat: { type: number, description: 緯度 }, lng: { type: number, description: 經(jīng)度 }, radius: { type: number, description: 半徑米, default: 100 } }, required: [lat, lng] } } ] }3.2 Client 側(cè)構(gòu)建“LLM 可用工具列表”拉取到 MCP 工具清單后Client 要做四步處理清洗無用字段MCP 返回的元數(shù)據(jù)往往比 LLM 需要的豐富得多直接把全部字段丟給 LLM 會浪費(fèi) Token還容易干擾理解。只保留 name、description、parameters 三個核心。重寫描述MCP Server 里的 description 可能寫得比較“開發(fā)者向”不適合模型決策。我實際改了這樣一句話原描述“獲取目標(biāo)區(qū)域的綜合環(huán)境狀態(tài)包括空氣質(zhì)量與氣候條件”改寫后變成“當(dāng)用戶詢問某地區(qū)的天氣、空氣、環(huán)境健康狀況時使用此工具獲取實時狀態(tài)。”改完后模型在“什么情況下該用這個工具”上的命中率明顯提升。轉(zhuǎn)換參數(shù)結(jié)構(gòu)LLM 的 tools 接口通常要求用 JSON Schema 描述 parameters這就直接透傳。注冊到對話系統(tǒng)把處理好的工具數(shù)組傳給 LLM 接口的 tools 參數(shù)。這一步的核心經(jīng)驗工具描述是寫給 LLM 看的不是寫給開發(fā)者看的。寫清楚觸發(fā)場景、返回內(nèi)容是短數(shù)據(jù)還是長數(shù)據(jù)、什么時候慎用都能顯著影響模型調(diào)工具的準(zhǔn)確率。4. 核心調(diào)用時序MCP Client 跟 LLM 的兩次握手4.1 第一次握手模型決定“我要調(diào)工具”在收到用戶問題后Client 將“用戶消息 工具清單”合在一起發(fā)給 LLM。此時 LLM 有兩種輸出可能普通文本回答說明不需要工具結(jié)構(gòu)化工具調(diào)用請求說明需要調(diào)工具。在 OpenAI 兼容接口里工具調(diào)用請求形如{ tool_calls: [ { id: call_abc123, type: function, function: { name: get_environment_status, arguments: {\area_code\:\A1001\} } } ] }注意這里的 arguments 雖然看起來是個 JSON但在協(xié)議里往往是字符串類型。Client 必須自己JSON.parse一下再傳給 MCP Server否則參數(shù)透傳必出錯。這個細(xì)節(jié)我一直強(qiáng)調(diào)因為太隱蔽了。4.2 Client 把請求轉(zhuǎn)譯給 Server發(fā)起調(diào)用MCP Client 在收到 LLM 的 tool_calls 后需要根據(jù) function.name 找到之前注冊的對應(yīng)工具然后組裝一條 MCP 層的tools/call請求{ jsonrpc: 2.0, id: 1, method: tools/call, params: { name: get_environment_status, arguments: { area_code: A1001 } } }然后通過傳輸通道發(fā)給目標(biāo) Server。需要注意一個 LLM 回復(fù)里可能包含多個 tool_callsClient 必須支持并發(fā)調(diào)用并在結(jié)果回來之后保持 tool_call_id 的對應(yīng)關(guān)系不能弄混上下文。我第一次做的時候圖省事串行跑結(jié)果兩個工具來回調(diào)了三輪響應(yīng)時間爆炸。4.3 第二次握手把執(zhí)行結(jié)果“翻譯”回給 LLM服務(wù)端返回結(jié)果后Client 需要把 MCP 協(xié)議格式的結(jié)果包裝成 LLM 能讀的消息。關(guān)鍵是要把 tool_call_id 跟結(jié)果配對放入消息列表的合適位置{ role: tool, tool_call_id: call_abc123, content: {\status\:\normal\,\air_quality\:\良好\,\temperature\:22} }把這條消息添加進(jìn)對話上下文再一次請求 LLM。此時 LLM 會基于“用戶原始問題 工具結(jié)果”生成最終回復(fù)。到這里一次完整工具協(xié)作結(jié)束。4.4 多輪協(xié)作的 Loop 設(shè)計真實場景往往不是一次就結(jié)束。比如用戶問“某區(qū)域環(huán)境怎么樣適合戶外跑步嗎”模型可能先調(diào)環(huán)境接口發(fā)現(xiàn)沒數(shù)據(jù)然后決定調(diào)定位接口最后綜合兩者作答。這時候 Client 就得寫一個循環(huán)邏輯循環(huán)條件LLM 返回中有 tool_calls 1. 執(zhí)行所有 tool_calls 2. 把結(jié)果作為 tool 消息追加進(jìn)上下文 3. 再次請求 LLM 跳出循環(huán)LLM 返回純文本結(jié)束這里最核心的“上下文累積”邏輯就是下一輪請求要把之前所有輪次的用戶消息、助手消息、工具消息全部帶上不能只帶最新一條。否則模型失去了“我為什么調(diào)這個工具”的來龍去脈推理質(zhì)量會斷崖式下降。這也是 MCP 協(xié)作流程中最常見的實現(xiàn)失誤。5. 上下文管理實踐Active Context 與 Working Context5.1 兩段式上下文結(jié)構(gòu)在實現(xiàn)中我把 MCP 協(xié)作中的信息分成了兩類Active Context當(dāng)前活躍上下文包括用戶最新問題、系統(tǒng)提示詞、工具清單。這塊是每一輪請求都要帶上的基礎(chǔ)信息。Working Context工作上下文包括多輪工具調(diào)用產(chǎn)生的中間結(jié)果、模型自己的判斷、歷史工具調(diào)用記錄。這塊是動態(tài)增長的但很快就會把 Token 撐爆。具體實現(xiàn)中我維護(hù)了一個上下文字典結(jié)構(gòu)context { system_prompt: CONFIG_SYSTEM_PROMPT, messages: [], tools: tools_list, tool_history: [] }每輪對話后把 assistant 消息、tool 消息依次 append 進(jìn) messages。注意順序不能亂尤其不能把 tool 消息插到 assistant 消息前面否則模型在部分 API 下會報 400 錯誤兼容性很差。5.2 上下文膨脹怎么處理多輪工具調(diào)用后messages 越來越長。我的策略是輪次多但每輪結(jié)果短比如幾十 Token直接全保留。當(dāng)前 LLM 上下文窗口普遍夠用沒必要過早裁剪工具結(jié)果特別長比如 Server 返回一個大 JSON把原始內(nèi)容截斷只保留摘要和關(guān)鍵字段。累積超過上下文安全線例如 80% 窗口把最早的一輪用戶消息 工具歷史壓縮成一句概括把新內(nèi)容保留完整。關(guān)于“工具結(jié)果特別長”我試過直接把大 JSON 塞給模型結(jié)果模型反而答非所問。后來把返回結(jié)構(gòu)做了裁剪只保留 result 里的核心字段忽略掉一堆元數(shù)據(jù)再給到模型回答質(zhì)量立刻上來。MCP Server 返回結(jié)果的格式可以給我們留“摘要化”的機(jī)會不要把原始大字段全量透傳。6. 工具調(diào)用鏈的容錯與回退策略6.1 錯誤分類與拿回控制權(quán)MCP Server 不是無腦執(zhí)行工具調(diào)用可能失敗。失敗原因可以分三類錯誤類型典型場景處理策略參數(shù)錯誤模型沒給必填參數(shù)把錯誤返回給 LLM附帶參數(shù)修正建議讓模型重試服務(wù)執(zhí)行錯誤Server 內(nèi)部異常直接返回錯誤不讓模型盲目重試調(diào)用限流Server 拒絕請求等待后重試或提示模型稍后再試實操中發(fā)現(xiàn)最關(guān)鍵的處理原則不要讓 LLM 成為錯誤恢復(fù)的“白癡循環(huán)機(jī)”。如果 Server 返回異常把“服務(wù)暫時不可用”這類語義直接作為最終回答返回給用戶而不是讓模型猜來猜去反復(fù)調(diào)同一個工具那樣既燒 Token 又沒效果。6.2 給 LLM 的工具結(jié)果加上“確定性錨點(diǎn)”在把 Server 結(jié)果回傳給 LLM 之前我在 content 前面固定添加一行工具“get_environment_status”已成功執(zhí)行。以下為返回原文這看似“污染”了數(shù)據(jù)實際上作用很大。因為模型面對一段裸 JSON 時經(jīng)常不確定“這到底是外部數(shù)據(jù)還是上一步自己的話”。加上這一行“錨點(diǎn)”模型就能清楚知道“這是外部地面事實”然后基于它做推理而不是把它當(dāng)成對話內(nèi)容來回復(fù)或復(fù)述。在我測試?yán)锛由襄^點(diǎn)后模型“復(fù)讀機(jī)”現(xiàn)象明顯降低。6.3 兜底 fallback模型“忘記”調(diào)工具怎么辦還有一種情況模型明明說了“我需要查一下數(shù)據(jù)”但輸出里沒有 tool_calls直接開始編答案。這類問題要靠兩招一是系統(tǒng)提示詞里加上“如果涉及實時數(shù)據(jù)必須調(diào)用對應(yīng)工具不得編造。”二是在 Client 端做關(guān)鍵詞檢測比如用戶問題里帶“天氣、環(huán)境、定位”等字樣但 LLM 回復(fù)卻沒觸發(fā)任何調(diào)用就自動追加一條“你確定不需要調(diào)用工具嗎請再次確認(rèn)”的提示消息引導(dǎo)模型修正。但要注意頻控這種“二次確認(rèn)”一多用戶會覺得體驗拖沓我只在語義強(qiáng)觸發(fā)場景才啟用。7. 多工具協(xié)作編排的實際模式7.1 鏈?zhǔn)絽f(xié)作Sequential場景用戶問“A 區(qū)域環(huán)境怎么樣適合戶外跑步嗎”模型需要先查區(qū)域環(huán)境再基于結(jié)果判斷是否適合運(yùn)動。這時模型會在一次響應(yīng)里輸出兩次 tool_calls先查環(huán)境再模擬定位Client 要支持并發(fā)或串行執(zhí)行兩個調(diào)用然后把兩個結(jié)果按 tool_call_id 配對后一起回傳。我這里實際用的運(yùn)行邏輯是CV 環(huán)境信息 Server 返回了“空氣良好、氣溫 22°C”模擬定位 Server 返回了“目標(biāo)區(qū)域半徑 500 米內(nèi)有大型公園”。模型最后綜合兩者給出了“適合跑步推薦去公園區(qū)域”的結(jié)論。沒有這兩個工具的協(xié)作模型只能憑空回答。7.2 并行協(xié)作Parallel兩個工具之間無依賴時同時調(diào)用能顯著降低總時延。我在 Client 實現(xiàn)里用線程池并發(fā)調(diào)用然后統(tǒng)一匯總結(jié)果。測試對比下來串行總耗時 620ms并行總耗時 380ms差距明顯。7.3 條件協(xié)作Conditional模型先調(diào)工具 A拿到結(jié)果為真才去調(diào)工具 B。這類決策邏輯完全由模型主導(dǎo)Client 只負(fù)責(zé)“執(zhí)行”。Client 做不了預(yù)測只能保證每一輪都保留完整上下文。7.4 帶護(hù)欄的循環(huán)Loop-with-guidance有些場景需要模型反復(fù)調(diào)用同一個工具來“試探”不同參數(shù)比如先按默認(rèn)范圍模擬定位發(fā)現(xiàn)結(jié)果不符合預(yù)期再縮小半徑重試。這時必須在系統(tǒng)提示里加一條“請勿超過 3 次相同工具調(diào)用”防止模型陷入無限循環(huán)。否則你可能會看到一個模型反復(fù)調(diào)同一個接口既燒錢又沒意義。8. 工具注冊與參數(shù)映射不可忽略的細(xì)節(jié)8.1 注冊工具到 MCP ClientMCP 官方的 Python SDK 提供了McpClient類。核心注冊流程如下from mcp import ClientSession, StdioServerParameters async def register_tool(session, tool_name, handler): session.register_tool(tool_name, handler)同時需要維護(hù)一張“工具名 → 處理器函數(shù)”的映射表在收到 LLM 的 tool_calls 時快速找到對應(yīng)的 handlertool_registry {} def register_tool(name, handler): tool_registry[name] handler8.2 參數(shù)類型轉(zhuǎn)換與校驗?zāi)P徒o的參數(shù)一律是 JSON 字符串Server 端拿到的 arguments 可能是字符串也可能已是對象取決于 SDK 實現(xiàn)。穩(wěn)妥做法是在 handler 里強(qiáng)制做類型注解并單獨(dú)寫一層校驗def handle_geolocation(args): required [lat, lng] missing [k for k in required if k not in args] if missing: raise ValueError(f缺少必填參數(shù): {missing}) lat float(args[lat]) lng float(args[lng])不要指望 LLM 一定按 schema 給參模型偶爾會漏參數(shù)、傳錯類型甚至把數(shù)字寫成字符串。校驗層是必須的不是可選項。8.3 參數(shù)“越界”處理有一次模型傳了一個 lat999, lng999 的坐標(biāo)Server 沒報錯返回了一個詭異定位點(diǎn)。后來我加了一層邊界檢查凡是超出合法經(jīng)緯度范圍的參數(shù)直接返回“參數(shù)非法”不讓錯誤數(shù)據(jù)流入業(yè)務(wù)邏輯。這類錯誤不處理模型會拿錯誤數(shù)據(jù)一本正經(jīng)地編答案那整條鏈路就失效了。9. 傳輸層選型stdio 與 SSE 怎么選9.1 本地專用場景用 stdiostdio 傳輸適合 Server 與 Client 在同一臺機(jī)器上。MCP Client 啟動子進(jìn)程冒煙測試快、無端口沖突、權(quán)限好控制。項目里兩個 Server 都是本地服務(wù)用 stdio 就夠了。注意點(diǎn)stdio 模式下 Server 進(jìn)程生命周期由 Client 管理如果 Client 異常退出Server 進(jìn)程容易成為孤兒進(jìn)程。我后來加了退出清理邏輯避免長時間掛著一堆殘留進(jìn)程。9.2 遠(yuǎn)程服務(wù)場景用 SSE 或 Streamable HTTP如果 Server 部署在另一臺機(jī)器stdio 就走不通。此時用 SSEServer-Sent Events或 Streamable HTTP。SSE 本質(zhì)是單向事件推送通道配合 HTTP 請求組成雙向通信。選型經(jīng)驗輕量內(nèi)網(wǎng)服務(wù) SSE 夠用要支持?jǐn)嗑€重連、多客戶端共享連接SSE 的復(fù)雜度比自定義 WebSocket 低不少。但 SSE 沒有請求超時語義需要自己在 Client 層加超時控制否則一個慢接口可能把整個會話拖死。9.3 傳輸方式對比速查傳輸方式適用場景優(yōu)點(diǎn)注意點(diǎn)stdio本地開發(fā)/單機(jī)簡單、安全、無端口進(jìn)程生命周期需管理SSE遠(yuǎn)程、輕量實現(xiàn)簡單、兼容性好超時控制待自主實現(xiàn)Streamable HTTP遠(yuǎn)程、需流式輸出雙向流式傳輸服務(wù)端實現(xiàn)復(fù)雜度更高10. 常見問題與排障記錄10.1 工具結(jié)果被 JSON 轉(zhuǎn)義“玩壞”現(xiàn)象Server 返回的文本含有引號Client 直接拼接進(jìn) tool 消息后模型收到的是被轉(zhuǎn)義后的一堆\解析失敗。解決凡是把工具結(jié)構(gòu)體傳給 LLM一律先json.dumps再傳入不要在字符串里手工拼 JSON。有一天我為圖快手工拼了兩層嵌套結(jié)果轉(zhuǎn)義錯亂排查了半小時。后來統(tǒng)一用模板封裝再沒出現(xiàn)。排查口訣看到\就想到“多拼了一層引號”看到\\就想到“轉(zhuǎn)義吃掉了反斜杠”。10.2 Agent “不再調(diào)工具”開始胡編現(xiàn)象模型一直輸出純文本不顧系統(tǒng)提示里的“必須調(diào)工具”。原因系統(tǒng)提示太長我把整本說明塞進(jìn)去了模型注意力被沖淡。解決精簡系統(tǒng)提示把工具觸發(fā)描述放在對話開頭最顯眼位置。另外一個原因可能是用戶問題太泛比如“你好”模型當(dāng)然不調(diào)工具。這種情況不屬于 bug不必強(qiáng)拉。10.3 多工具調(diào)用時參數(shù)串位現(xiàn)象模型同時發(fā)起 get_environment_status 和 simulate_geolocationClient 并發(fā)執(zhí)行結(jié)果把兩個結(jié)果放到對方的 tool_call_id 下模型收到后邏輯全亂了。解決在并發(fā)調(diào)用前為每個 tool_call 生成獨(dú)立 key執(zhí)行完成后用 key 回填。這個坑特別隱蔽因為輸出看起來只是“內(nèi)容有點(diǎn)詭異”但模型可能因此編出一個邏輯不通的答案。10.4 同一工具連續(xù)調(diào)用導(dǎo)致上下文堆積現(xiàn)象Agent 為了找到合適半徑連續(xù)調(diào)了 5 次 simulate_geolocation每次結(jié)果都完整放進(jìn) messagesToken 消耗暴漲。解決同一工具的連續(xù)調(diào)用只保留最后一次結(jié)果中間的中間過程在回填時覆蓋。這是“工作上下文壓縮”最常見的手段效果立竿見影。10.5 調(diào)試 MCP Server 時覺得“協(xié)議都通了但結(jié)果不對”先檢查 JSON-RPC 消息格式然后檢查 tools/call 里的 name 是否與 tools/list 返回完全一致大小寫、空格都算。有一次我工具名寫成了simulateGeoLocationServer 注冊的是simulate_geolocationLLM 發(fā)的名字對不上導(dǎo)致一直在報“method not found”。統(tǒng)一用小寫加下劃線命名可以避免這種大小寫不一致問題。11. 項目沉淀與個人體會整個 MCP Client 與 LLM 協(xié)作流程本質(zhì)上是三個角色的“翻譯-執(zhí)行-回傳”循環(huán)。比較核心的幾條經(jīng)驗我總結(jié)一下工具描述要面向 LLM 寫說清觸發(fā)場景、返回內(nèi)容形態(tài)而不是羅列代碼細(xì)節(jié)。上下文的拼接順序絕不能亂assistant 消息要在 tool 消息之前tool 消息必須對應(yīng)正確的 tool_call_id。參數(shù)校驗要在 Client 側(cè)做不能指望 LLM 100% 按 schema 給參。工具結(jié)果回傳時加錨點(diǎn)說明能讓模型區(qū)分“外部事實”和“自我生成”。并發(fā)執(zhí)行多工具時務(wù)必按 key 回填結(jié)果否則上下文錯配很難排查。這個項目讓我對 Agent 應(yīng)用的架構(gòu)有了更清晰的判斷LLM 負(fù)責(zé)“決定做什么”Client 負(fù)責(zé)“保證做對”Server 負(fù)責(zé)“真正做成”。三者各有邊界配合的關(guān)鍵全在 Client 這一層。以后再做類似項目我會優(yōu)先把 Client 的工具注冊、上下文管理、超時重試三塊做成通用的橫向模塊換 Server 只改配置不改邏輯。這樣即使工具從 2 個擴(kuò)展到 20 個代碼量和維護(hù)成本也不會跟著線性增長。如果你也在接 MCP我建議先跑通一個最小閉環(huán)再逐步加多工具和并發(fā)邏輯。別一上來就追求“全能力編排”那樣調(diào)試復(fù)雜度會瞬間拉滿。先用一個簡單查詢工具跑通全流程再去擴(kuò)展會讓整個過程順暢很多。