
1. 從一次 MCP 連接失敗說(shuō)起McpClientManager 到底管什么如果你最近在折騰 Gemini cli 的 MCP 工具鏈大概率遇到過(guò)這種場(chǎng)景配置文件里明明寫(xiě)了三四個(gè) MCP server啟動(dòng)后卻只有一兩個(gè)工具能用日志里飄著Error during discovery for server xxx或者干脆連mcp-client-update事件都沒(méi)觸發(fā)。這類問(wèn)題十有八九不是 MCP server 本身寫(xiě)錯(cuò)了而是McpClientManager在配置加載、權(quán)限校驗(yàn)、異步發(fā)現(xiàn)這幾個(gè)環(huán)節(jié)里做了取舍。McpClientManager是 Gemini cli 里負(fù)責(zé) MCP 客戶端全生命周期的核心類位置在packages/core/src/tools/mcp-client-manager.ts。它要做的事情可以拆成四塊把配置里的 MCP server 拉起來(lái)、連接并發(fā)現(xiàn)工具、把工具注冊(cè)進(jìn)ToolRegistry、在擴(kuò)展加載/卸載時(shí)動(dòng)態(tài)增刪客戶端。它同時(shí)管本地子進(jìn)程stdio 方式和遠(yuǎn)程 MCP 服務(wù)器SSE/HTTP 方式所以你在配置里寫(xiě)的command、args、url、httpUrl這些字段最終都是被它讀進(jìn)去決定怎么連的。對(duì)本地開(kāi)發(fā)調(diào)試來(lái)說(shuō)理解它的行為有兩個(gè)直接好處。第一你能判斷“配置沒(méi)生效”到底是文件路徑不對(duì)、字段名寫(xiě)錯(cuò)還是被isAllowedMcpServer攔了。第二你能把 MCP server 的 endpoint 統(tǒng)一改到一個(gè)穩(wěn)定的 API 通道上比如把遠(yuǎn)程 MCP 的 base URL 指向 TaoToken 的 API 入口這樣 Key 和調(diào)用通道集中管理排查連接問(wèn)題時(shí)不用在多個(gè)服務(wù)之間來(lái)回切換。我試過(guò)在同一個(gè)項(xiàng)目里同時(shí)掛本地 stdio server 和遠(yuǎn)程 HTTP server結(jié)果遠(yuǎn)程那個(gè)一直 discovery 失敗最后發(fā)現(xiàn)是isTrustedFolder()返回 false 導(dǎo)致startConfiguredMcpServers直接 return 了。這個(gè)坑很典型下面按源碼結(jié)構(gòu)一步步拆。2. TaoToken 前置準(zhǔn)備統(tǒng)一 Key 與 API 通道在動(dòng) Gemini cli 的 MCP 配置之前先把上游 API 通道準(zhǔn)備好。TaoToken 在這里的角色是統(tǒng)一的模型/API 入口你可以在它的控制臺(tái)里生成 Key然后把 MCP server 或 Gemini cli 自身的模型請(qǐng)求都指到同一個(gè) base URL減少“這個(gè) Key 對(duì)哪個(gè)服務(wù)”的混亂。需要提前拿到的三樣?xùn)|西后面配置里會(huì)反復(fù)用到Base URLhttps://taotoken.net/apiAPI Key在控制臺(tái)創(chuàng)建形如sk-...Model ID按你實(shí)際要調(diào)的模型填比如claude-sonnet-4-5這類標(biāo)識(shí)控制臺(tái)入口在這里創(chuàng)建 Key 的頁(yè)面在 API Keys 里控制臺(tái)https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_client_manager API Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_client_manager如果你只是想先驗(yàn)證模型通道是否通可以用模型對(duì)話頁(yè)面直接發(fā)一條消息確認(rèn) Key 和 base URL 沒(méi)問(wèn)題再去改 MCP 配置模型對(duì)話https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_client_manager接入文檔在 doc 頁(yè)面里面有各語(yǔ)言 SDK 的 base URL 寫(xiě)法MCP 場(chǎng)景下主要看 HTTP/SSE 那部分接入文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_client_manager這里要強(qiáng)調(diào)一點(diǎn)TaoToken 是正常的 API 服務(wù)入口不是所謂“中轉(zhuǎn)”或灰色通道配置時(shí)按官方文檔的 base URL 和鑒權(quán)頭寫(xiě)就行。MCP server 如果本身要調(diào)模型就把它的OPENAI_BASE_URL或ANTHROPIC_BASE_URL指向https://taotoken.net/apiKey 用同一個(gè)這樣McpClientManager在 discovery 階段觸發(fā)的工具調(diào)用和 Gemini cli 主流程用的是同一套憑證排查 401 時(shí)只需要看一個(gè)地方。3. 可復(fù)制配置MCP server 定義與 endpoint 改寫(xiě)Gemini cli 的 MCP 配置通常寫(xiě)在項(xiàng)目級(jí)或用戶級(jí)的 settings 文件里McpClientManager通過(guò)cliConfig.getMcpServers()讀取。下面給一份可直接復(fù)制的 JSON 片段包含一個(gè)本地 stdio server 和一個(gè)遠(yuǎn)程 HTTP server遠(yuǎn)程那個(gè)的 endpoint 指向 TaoToken API 通道。{ mcpServers: { local-filesystem: { command: npx, args: [-y, modelcontextprotocol/server-filesystem, /Users/me/project], env: { API_BASE_URL: https://taotoken.net/api, API_KEY: sk-your-taotoken-key } }, remote-tools: { httpUrl: https://taotoken.net/api/mcp, headers: { Authorization: Bearer sk-your-taotoken-key, Content-Type: application/json }, env: { MODEL_ID: claude-sonnet-4-5 } } } }幾個(gè)字段和McpClientManager的對(duì)應(yīng)關(guān)系要說(shuō)清楚。commandargs走的是本地子進(jìn)程分支McpClient會(huì)用 stdio 起進(jìn)程httpUrl走遠(yuǎn)程分支連接時(shí)用 HTTP 傳輸。env里的變量會(huì)注入到子進(jìn)程環(huán)境所以本地 server 要調(diào)模型時(shí)API_BASE_URL和API_KEY從這里傳最省事。如果你用的是 TOML 風(fēng)格的配置部分版本或擴(kuò)展里會(huì)出現(xiàn)等價(jià)寫(xiě)法如下[mcpServers.local-filesystem] command npx args [-y, modelcontextprotocol/server-filesystem, /Users/me/project] [mcpServers.local-filesystem.env] API_BASE_URL https://taotoken.net/api API_KEY sk-your-taotoken-key [mcpServers.remote-tools] httpUrl https://taotoken.net/api/mcp [mcpServers.remote-tools.headers] Authorization Bearer sk-your-taotoken-key Content-Type application/json這里有個(gè)容易踩的點(diǎn)McpClientManager在startConfiguredMcpServers里會(huì)先調(diào)populateMcpServerCommand把getMcpServerCommand()的命令行覆蓋合并進(jìn)配置。也就是說(shuō)如果你啟動(dòng) Gemini cli 時(shí)帶了--mcp-server之類的參數(shù)它會(huì)覆蓋文件里的同名 server。調(diào)試時(shí)先確認(rèn)沒(méi)有命令行覆蓋否則你會(huì)以為配置文件沒(méi)被讀取。另外isAllowedMcpServer的白名單/黑名單邏輯要留意。如果getAllowedMcpServers()返回了非空數(shù)組那么只有數(shù)組里的名字才會(huì)被連接其他全部進(jìn)blockedMcpServers。所以配置里 server 的 key 名要和白名單完全一致大小寫(xiě)敏感。4. 驗(yàn)證請(qǐng)求確認(rèn)連接與工具發(fā)現(xiàn)成功配置寫(xiě)完后不要直接上復(fù)雜任務(wù)先用最小步驟驗(yàn)證McpClientManager是否真的把 server 連上并發(fā)現(xiàn)了工具。第一步啟動(dòng) Gemini cli 并打開(kāi) debug 日志。McpClientManager里大量使用debugLogger.log和debugLogger.warn開(kāi)啟后能看到Loading extension: xxx、Error stopping client這類輸出。DEBUG* gemini --debug第二步觀察 discovery 狀態(tài)。McpClientManager內(nèi)部有MCPDiscoveryState從NOT_STARTED到IN_PROGRESS再到COMPLETED。如果一直停在IN_PROGRESS說(shuō)明某個(gè) server 的connect()或discover()卡住了常見(jiàn)原因是遠(yuǎn)程httpUrl不可達(dá)或鑒權(quán)失敗。第三步用一次實(shí)際工具調(diào)用驗(yàn)證。在 Gemini cli 里讓它列一下當(dāng)前可用工具或者直接觸發(fā)一個(gè) MCP 工具。成功的話ToolRegistry里會(huì)多出對(duì)應(yīng)工具mcp-client-update事件也會(huì)帶著更新后的clientsMap 發(fā)出。如果你想繞過(guò) CLI 直接驗(yàn)證 TaoToken 通道可以用 curl 打一次模型接口確認(rèn) Key 和 base URL 正確curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-your-taotoken-key \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-5, messages: [{role: user, content: ping}] }返回里能看到choices數(shù)組就說(shuō)明通道沒(méi)問(wèn)題。這一步能幫你把“MCP 連接失敗”和“上游 API 鑒權(quán)失敗”區(qū)分開(kāi)——前者看McpClientManager日志后者看 HTTP 狀態(tài)碼。第四步檢查工具是否注冊(cè)成功。McpClientManager在disconnectClient和 discovery 完成后都會(huì)調(diào)geminiClient.setTools()前提是geminiClient.isInitialized()為 true。如果工具沒(méi)出現(xiàn)先確認(rèn) Gemini 客戶端已經(jīng)初始化再看toolRegistry里有沒(méi)有對(duì)應(yīng)條目。5. 常見(jiàn)錯(cuò)誤排查401、local proxy failed、reading choices、OAuth這一節(jié)按真實(shí)報(bào)錯(cuò)來(lái)對(duì)照都是McpClientManager場(chǎng)景下高頻出現(xiàn)的。401 Unauthorized遠(yuǎn)程 MCP server 的headers.Authorization沒(méi)帶或 Key 失效。檢查Bearer sk-...是否完整Key 是否在 TaoToken 控制臺(tái)被刪除或過(guò)期。本地 stdio server 如果自己調(diào)模型檢查env.API_KEY是否注入成功可以在 server 啟動(dòng)日志里打印process.env.API_KEY的前幾位確認(rèn)。local proxy failed這個(gè)通常出現(xiàn)在本地 stdio server 啟動(dòng)階段command找不到或args路徑錯(cuò)誤。McpClientManager會(huì)捕獲connect()的異常并通過(guò)coreEvents.emitFeedback報(bào)出來(lái)。先手動(dòng)在終端跑一遍command args確認(rèn)能啟動(dòng)再放回配置。reading choices 報(bào)錯(cuò)這是上游返回體解析失敗多半是 base URL 拼錯(cuò)導(dǎo)致返回了 HTML 或空 body。確認(rèn)API_BASE_URL是https://taotoken.net/api不要多寫(xiě)或少寫(xiě)/v1具體路徑以接入文檔為準(zhǔn)。用第 4 節(jié)的 curl 先驗(yàn)證一次。OAuth 相關(guān)錯(cuò)誤部分遠(yuǎn)程 MCP server 要求 OAuth 流程McpClientManager本身不處理 OAuth 交互它只負(fù)責(zé)連接和發(fā)現(xiàn)。如果 server 配置里需要 token 刷新得在 server 側(cè)或通過(guò)靜態(tài) header 解決。調(diào)試階段建議先用靜態(tài) Bearer token 跑通再考慮動(dòng)態(tài)憑證。排查順序建議固定成先看isTrustedFolder()是否為 true再看isAllowedMcpServer是否放行然后看connect()是否成功最后看discover()是否返回工具。這四步對(duì)應(yīng)McpClientManager里maybeDiscoverMcpServer的主流程按順序查能省很多時(shí)間。6. 把通道固定下來(lái)長(zhǎng)期編碼與 Agent 場(chǎng)景的配置建議如果你打算長(zhǎng)期用 Gemini cli 跑編碼或 Agent 任務(wù)MCP server 數(shù)量會(huì)越來(lái)越多這時(shí)候統(tǒng)一通道的價(jià)值就體現(xiàn)出來(lái)了。所有需要調(diào)模型的 MCP server 都指向同一個(gè) TaoToken base URLKey 只維護(hù)一份McpClientManager的 discovery 日志里出現(xiàn)鑒權(quán)問(wèn)題時(shí)也只需要查一個(gè)來(lái)源。對(duì)于需要長(zhǎng)時(shí)間運(yùn)行的編碼任務(wù)可以用 Coding Plan 把模型調(diào)用額度固定下來(lái)避免調(diào)試到一半 Key 額度耗盡Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_client_manager如果你在用 Claude Code 或類似的 Agent 工具鏈Anthropic 兼容通道的配置也在同一套體系里base URL 和 Key 復(fù)用即可ClaudeCodeAnthropichttps://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_client_manager最后給一個(gè)實(shí)操建議把 MCP 配置里的 server 名、endpoint、Key 來(lái)源做成一張對(duì)照表貼在項(xiàng)目 README 里每次改配置先對(duì)表。McpClientManager的行為是確定性的配置對(duì)了它就能連上連不上一定是某個(gè)字段或權(quán)限環(huán)節(jié)出了問(wèn)題按第 5 節(jié)的順序查就行。