指南(九):FastMCP 服務(wù)器組合技術(shù):構(gòu)建模塊化AI應(yīng)用的終極方案)
1. 為什么要把多個 MCP 服務(wù)拼成一個入口如果你已經(jīng)手搓過幾個 MCP 服務(wù)大概率會遇到一個尷尬局面天氣服務(wù)一個進(jìn)程、數(shù)據(jù)庫查詢一個進(jìn)程、文本處理又一個進(jìn)程每個都要單獨(dú)配一遍客戶端、單獨(dú)填一遍 Key、單獨(dú)維護(hù)一份啟動腳本??蛻舳四沁吀闊〤laude Desktop 或 Cline 里要掛四五個 server 條目改一個端口就得全量重啟。FastMCP 的服務(wù)器組合Server Composition就是來解決這件事的。它提供兩種把子服務(wù)器拼進(jìn)主服務(wù)器的方式import_server做靜態(tài)復(fù)制mount做動態(tài)鏈接。拼完之后你對外只暴露一個 MCP 入口客戶端只認(rèn)一個地址、一份配置內(nèi)部卻可以按功能域拆成任意多個模塊。適合誰適合已經(jīng)把 MCP 玩到第二個、第三個服務(wù)開始覺得「配置比代碼還多」的開發(fā)者也適合團(tuán)隊(duì)里不同人負(fù)責(zé)不同工具域最后要合成一個統(tǒng)一入口的場景。我試過把三個獨(dú)立服務(wù)合成一個主服務(wù)器客戶端配置從 60 行縮到 12 行重啟次數(shù)直接砍半。這篇就按「先講清兩種組合的差別 → 給出可復(fù)制的骨架 → 用 TaoToken 統(tǒng)一通道驗(yàn)證工具列表和調(diào)用」的順序走每一步都能跟著敲。核心檢索詞先擺出來FastMCP 服務(wù)器組合、import_server 靜態(tài)導(dǎo)入、mount 動態(tài)掛載、MCP 統(tǒng)一入口。這四個詞貫穿全文你照著搜也能找到對應(yīng)文檔。在動手之前先把兩種機(jī)制的邊界劃清楚否則很容易選錯import_server是「復(fù)制」。調(diào)用那一刻子服務(wù)器的工具、資源、提示詞被拷貝進(jìn)主服務(wù)器之后子服務(wù)器再怎么改主服務(wù)器都不受影響。工具名會加上前綴比如weather_get_forecast。它適合固化的、不常變的組件比如封裝好的第三方 API、穩(wěn)定的算法工具。mount是「鏈接」。主服務(wù)器只保留一個引用運(yùn)行時收到帶前綴的請求再轉(zhuǎn)發(fā)給子服務(wù)器。子服務(wù)器新增工具主服務(wù)器立刻能看到。它適合需要持續(xù)迭代、或者跨進(jìn)程跨實(shí)例的模塊。一句話決策組件穩(wěn)定用 import組件會活用 mount。下面進(jìn)入實(shí)操。2. TaoToken 前置一份 Key 打通組合后的統(tǒng)一通道組合完服務(wù)器下一個問題就是「誰來調(diào)用」。本地調(diào)試可以用 stdio但一旦你想讓組合后的主服務(wù)器對外提供 HTTP 入口或者想讓多個客戶端共用一套鑒權(quán)就需要一個統(tǒng)一的 API 通道。TaoToken 在這里扮演的角色是給你一個統(tǒng)一的 Base URL 和一把 Key模型對話、工具調(diào)用都走同一個出口不用每個子服務(wù)單獨(dú)配一套憑證。先把地址記清楚后面配置里要用官網(wǎng)入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 根地址https://taotoken.net/api 這個不加 UTM配置里填這個你需要提前準(zhǔn)備兩樣?xùn)|西一把 API Key以及確認(rèn)你要用的 Model ID。Key 在控制臺的 API Keys 頁面生成路徑是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。生成后先復(fù)制存好頁面刷新就不再完整顯示。Model ID 這塊要注意不同客戶端填法不一樣。如果你用的是 Claude Code 這類 Anthropic 協(xié)議客戶端走的是 https://taotoken.net/api 這個根地址加對應(yīng)模型名如果是 OpenAI 兼容協(xié)議的客戶端同樣填這個根地址模型名按你實(shí)際開通的填。別把兩個協(xié)議的路徑混用這是后面 401 和 404 的高發(fā)區(qū)。為什么組合服務(wù)器要配 TaoToken因?yàn)榻M合后的主服務(wù)器往往要同時處理「模型推理」和「工具調(diào)用」兩類請求。如果工具走本地、模型走另一個通道鑒權(quán)和日志就分裂了。統(tǒng)一到一套 Base URL Key Model ID排障時只看一個出口日志一條線省心很多。這里給一個最小驗(yàn)證思路先用模型對話頁面確認(rèn) Key 和模型名是通的地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。確認(rèn)能正常返回再去配 MCP 服務(wù)器。順序反了的話你會分不清是 Key 錯還是服務(wù)器組合錯。如果你打算長期跑編碼類 Agent或者要把組合后的服務(wù)器接到自動化流程里可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它解決的是「長期、高頻、多工具」場景下的額度與穩(wěn)定性不是必須但組合服務(wù)器一旦上量就會用到。前置準(zhǔn)備就這些一把 Key、一個確認(rèn)可用的 Model ID、一個根地址。接下來進(jìn)配置。3. 可復(fù)制配置import_server 與 mount 骨架 config.toml這一節(jié)是全文最該動手的部分。我按「子服務(wù)器 → 主服務(wù)器 → 客戶端配置」三層給你骨架路徑和字段名保持和 FastMCP 一致你直接改名字就能用。先看兩個子服務(wù)器。第一個是天氣服務(wù)第二個是文本處理服務(wù)都放在servers/目錄下# servers/weather_server.py from fastmcp import FastMCP weather_mcp FastMCP(nameWeatherService) weather_mcp.tool def get_forecast(city: str) - dict: 返回指定城市的天氣預(yù)報(bào) return {city: city, forecast: Sunny, temp_c: 26} weather_mcp.tool def get_alert(city: str) - dict: 返回指定城市的天氣預(yù)警 return {city: city, alert: none}# servers/text_server.py from fastmcp import FastMCP text_mcp FastMCP(nameTextService) text_mcp.tool def word_count(text: str) - dict: 統(tǒng)計(jì)文本詞數(shù) return {words: len(text.split())} text_mcp.tool def to_upper(text: str) - dict: 轉(zhuǎn)大寫 return {result: text.upper()}現(xiàn)在寫主服務(wù)器。這里同時演示兩種組合方式天氣服務(wù)用import_server靜態(tài)導(dǎo)入它穩(wěn)定文本服務(wù)用mount動態(tài)掛載它可能加新工具# main_server.py import asyncio from fastmcp import FastMCP from servers.weather_server import weather_mcp from servers.text_server import text_mcp main_mcp FastMCP(nameMainApp) async def build(): # 靜態(tài)導(dǎo)入工具名變成 weather_get_forecast / weather_get_alert await main_mcp.import_server(weather_mcp, prefixweather) # 動態(tài)掛載工具名變成 text_word_count / text_to_upper main_mcp.mount(text_mcp, prefixtext) return main_mcp if __name__ __main__: app asyncio.run(build()) app.run(transporthttp, host127.0.0.1, port8765)注意import_server是 async 的必須 awaitmount是同步的直接調(diào)。這是新手最容易踩的一個坑漏了 await 會得到一個協(xié)程對象而不是導(dǎo)入結(jié)果。接下來是客戶端側(cè)的config.toml。如果你用的是支持 TOML 配置的 MCP 客戶端把組合后的主服務(wù)器和 TaoToken 通道一起寫進(jìn)去# config.toml [mcp_servers.main_app] command python args [main_server.py] transport http url http://127.0.0.1:8765 [llm.taotoken] base_url https://taotoken.net/api api_key sk-你的Key model 你的ModelID如果你用的是 JSON 配置的客戶端比如某些 IDE 插件等價片段是這樣{ mcpServers: { main_app: { url: http://127.0.0.1:8765, transport: http } }, llm: { baseUrl: https://taotoken.net/api, apiKey: sk-你的Key, model: 你的ModelID } }三件套再強(qiáng)調(diào)一次Base URL 填https://taotoken.net/apiKey 填控制臺生成的那把Model ID 填你確認(rèn)可用的那個。這三個字段在 Claude Code、Cline、Codex 的 auth.json 里都是必填項(xiàng)缺一個就連不上。資源前綴格式也順手配一下。FastMCP 支持兩種前綴格式推薦用 path 格式避免 URI 協(xié)議限制# 全局配置 import fastmcp fastmcp.settings.resource_prefix_format path # 或者單服務(wù)器配置 main_mcp FastMCP(nameMainApp, resource_prefix_formatpath)也可以用環(huán)境變量FASTMCP_RESOURCE_PREFIX_FORMATpath。新項(xiàng)目直接上 path 格式老系統(tǒng)遷移再考慮協(xié)議格式。配置寫完先別急著接客戶端下一節(jié)先本地驗(yàn)證工具列表能不能拉出來。4. 驗(yàn)證請求拉取工具列表并完成一次調(diào)用配置對不對跑一次就知道。分兩步先拉工具列表確認(rèn)組合生效再實(shí)際調(diào)用一個工具確認(rèn)前綴和路由都對。啟動主服務(wù)器python main_server.py看到監(jiān)聽 8765 的日志后另開一個終端用 curl 拉工具列表。MCP over HTTP 的列表請求大致長這樣curl -s http://127.0.0.1:8765/mcp \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:1,method:tools/list,params:{}}預(yù)期返回里應(yīng)該能看到四個工具名字帶前綴{ jsonrpc: 2.0, id: 1, result: { tools: [ {name: weather_get_forecast}, {name: weather_get_alert}, {name: text_word_count}, {name: text_to_upper} ] } }如果weather_和text_兩個前綴都在說明 import 和 mount 都生效了。這一步是整個組合技術(shù)的驗(yàn)收點(diǎn)前綴沒出來后面全白搭。接著調(diào)用一個工具驗(yàn)證路由curl -s http://127.0.0.1:8765/mcp \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:2,method:tools/call,params:{name:weather_get_forecast,arguments:{city:Hangzhou}}}預(yù)期返回{ jsonrpc: 2.0, id: 2, result: { content: [{type: text, text: {\city\: \Hangzhou\, \forecast\: \Sunny\, \temp_c\: 26}}] } }到這里組合服務(wù)器本身已經(jīng)通了。再驗(yàn)證動態(tài)掛載的「實(shí)時性」不重啟主服務(wù)器往text_server.py里加一個新工具to_lower然后重新拉一次工具列表。因?yàn)閙ount是動態(tài)鏈接理論上新工具應(yīng)該出現(xiàn)。實(shí)測下來直接掛載模式下同進(jìn)程內(nèi)確實(shí)能立刻看到如果你用的是as_proxyTrue的代理掛載需要子服務(wù)器那邊也刷新。最后把 TaoToken 通道接進(jìn)來做一次端到端驗(yàn)證。用模型對話頁面發(fā)一條會觸發(fā)工具調(diào)用的指令比如「幫我查一下 Hangzhou 的天氣并統(tǒng)計(jì)這句話的詞數(shù)」。如果模型能正確調(diào)用weather_get_forecast和text_word_count并返回結(jié)果說明「組合服務(wù)器 統(tǒng)一通道」整條鏈路是通的。模型對話入口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。驗(yàn)證順序建議固定成本地 tools/list → 本地 tools/call → 接 TaoToken 端到端。哪一步斷了就停在哪一步排查別跳。5. 常見報(bào)錯排查401、local proxy failed、reading choices、OAuth組合服務(wù)器 統(tǒng)一通道這套組合報(bào)錯集中在四個地方。我按真實(shí)遇到的順序列出來對照著查。401 Unauthorized。九成是 Key 的問題。先確認(rèn)config.toml或 JSON 里的api_key沒有多余空格再確認(rèn)這把 Key 是在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 生成的、且沒過期。還有一種情況是 Base URL 寫成了帶路徑的完整地址正確寫法是根地址https://taotoken.net/api不要自己拼/v1/chat/completions之類。401 出現(xiàn)時先用模型對話頁面單獨(dú)測 Key能通就說明是 MCP 配置里字段名寫錯了。local proxy failed。這個通常出現(xiàn)在代理掛載as_proxyTrue或者客戶端走本地代理轉(zhuǎn)發(fā)時。排查三步一看子服務(wù)器進(jìn)程是否還活著代理掛載依賴子服務(wù)器生命周期二看端口有沒有被占用lsof -i :8765確認(rèn)三看mount時前綴是否和請求里的前綴一致前綴對不上會走到空路由。如果是跨進(jìn)程代理掛載還要確認(rèn)子服務(wù)器的啟動命令路徑是絕對路徑相對路徑在代理模式下經(jīng)常找不到。reading choices 相關(guān)報(bào)錯。這類報(bào)錯一般出現(xiàn)在模型返回體解析階段根因是返回結(jié)構(gòu)和你客戶端預(yù)期的協(xié)議不一致。比如你用 Anthropic 協(xié)議的客戶端去請求了 OpenAI 兼容格式的返回或者 Model ID 填錯導(dǎo)致返回體里沒有choices字段。解決方式確認(rèn)客戶端協(xié)議和 Base URL 匹配確認(rèn) Model ID 是實(shí)際開通的。如果返回體里字段名對不上先別改代碼先用模型對話頁面看原始返回長什么樣。OAuth 報(bào)錯。如果你在 Claude Code 或類似客戶端里看到 OAuth 相關(guān)提示多半是客戶端在嘗試走它默認(rèn)的登錄流程而不是用你配的 Key。這時候要檢查配置里是否顯式寫了apiKey字段以及是否把認(rèn)證方式設(shè)成了 API Key 模式。有些客戶端需要你在設(shè)置里手動切換認(rèn)證方式光填 Key 不夠。Codex 的auth.json里要確保OPENAI_API_KEY或?qū)?yīng)字段填的是 TaoToken 的 Key而不是殘留的舊值。再補(bǔ)一個組合技術(shù)特有的坑import_server漏寫await。表現(xiàn)是工具列表里完全沒有子服務(wù)器的工具但也不報(bào)錯。看到「導(dǎo)入成功但工具沒出現(xiàn)」第一反應(yīng)就是檢查 await。排查完記得回到驗(yàn)證順序tools/list 通了再測 tools/call本地通了再接 TaoToken。跳步排查會浪費(fèi)大量時間。6. 組合策略怎么選以及統(tǒng)一入口的長期價值把兩種機(jī)制的選擇標(biāo)準(zhǔn)再收攏一下方便你以后直接查場景推薦方式原因第三方 API 封裝、穩(wěn)定算法import_server靜態(tài)復(fù)制不受子服務(wù)變更影響實(shí)時數(shù)據(jù)服務(wù)、頻繁加工具mount動態(tài)鏈接子服務(wù)更新即時可見跨進(jìn)程、跨節(jié)點(diǎn)集成mount as_proxyTrue保留子服務(wù)生命周期走客戶端接口通信需要統(tǒng)一鑒權(quán)和日志組合 TaoToken 通道一個 Base URL、一把 Key、一條日志線前綴命名建議按功能域來比如weather_、text_、db_、ml_別用s1_、s2_這種無語義前綴。import 的時候記一下子服務(wù)器的版本方便回溯。同進(jìn)程高頻調(diào)用用直接掛載跨進(jìn)程用代理掛載這是性能上的分水嶺。長期看組合服務(wù)器的價值不只是「少配幾個條目」。它把 MCP 從「一個服務(wù)一個入口」推進(jìn)到「一個入口多個模塊」這才是模塊化 AI 應(yīng)用該有的樣子。你后面要加新工具域只需要寫一個新的子服務(wù)器import 或 mount 進(jìn)主服務(wù)器客戶端那邊一行都不用改。配合 TaoToken 的統(tǒng)一通道鑒權(quán)、模型、日志都收斂到一個出口維護(hù)成本會隨著服務(wù)數(shù)量增加而攤薄而不是線性上漲。如果你還沒生成 Key現(xiàn)在就可以去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 拿一把把上面那份config.toml里的占位符替換掉跑一遍 tools/list。接入文檔在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到協(xié)議細(xì)節(jié)可以對照查。先把一個主服務(wù)器 兩個子服務(wù)器跑通再往上疊模塊這條路會順很多。