MCP中間件和MCP鑒權:用TaoToken統(tǒng)一Key打通FastMCP鑒權鏈路)
1. 自建 FastMCP Server 為什么必須補上鑒權中間件如果你已經用 FastMCP 跑通了一個 HTTP 模式的 MCP Server大概率會經歷這樣一個階段本地streamablehttp_client連上去list_tools一列工具全出來了調用也正常于是順手把端口映射到內網想著先給同事用著。問題就出在這里——FastMCP 默認不校驗任何身份任何能訪問到/mcp/這個路徑的客戶端都能直接發(fā) JSON-RPC 請求把你的工具全調一遍。我試過在一個只做了防火墻 IP 白名單的 MCP Server 上做壓力測試只要請求源在允許網段內tools/call完全不設防。這意味著一旦有人把內網地址泄露出去或者某臺被允許的機器被當成跳板你的數(shù)據(jù)庫查詢工具、文件操作工具就全部暴露了。MCP 基于 JSON-RPC 規(guī)范運行請求體長這樣{ jsonrpc: 2.0, id: 1, method: tools/call, params: { name: execute_mysql_sql, arguments: { sql: select * from shares_day_info limit 3 } } }注意這里沒有任何身份字段。JSON-RPC 本身是傳輸無關的協(xié)議鑒權信息只能掛在傳輸層——HTTP 模式下就是請求頭。所以正確的做法是在 FastMCP 的中間件管道里插一層攔截所有進入的 MCP 消息從 HTTP header 里取出憑證做校驗不合法就直接拋錯讓請求根本到不了工具執(zhí)行階段。FastMCP 中間件采用管道模型請求按添加順序流經每個中間件每個中間件可以檢查請求、修改請求、調用call_next()交給下一個、再檢查響應。它提供了從通用到具體的鉤子層級——on_message管所有消息on_request只管需要響應的請求on_call_tool只管工具調用。鑒權這種所有請求都要過的邏輯用on_message或直接在__call__里做最穩(wěn)妥因為工具發(fā)現(xiàn)list_tools本身也是一次請求不攔的話別人照樣能枚舉你有哪些工具。這一篇要解決的就是給自建 FastMCP Server 加一層鑒權中間件并且用 TaoToken 的統(tǒng)一 Key 體系把憑證管理收斂到一處避免每個 MCP Server 各寫一套 token 表。適合正在把 MCP Server 從局域網自用推向團隊共享的開發(fā)者。下面從環(huán)境準備、中間件配置、請求驗證到報錯排查一步步給可復制的片段。2. TaoToken 統(tǒng)一 Key 接入 FastMCP 鑒權鏈路的前置準備在寫中間件之前先把憑證來源理清楚。最原始的做法是在自己庫里建一張mcp_server_oauth_tokens表存用戶名、token、過期時間中間件查庫比對。這個方案能跑但有幾個現(xiàn)實問題每個 MCP Server 都要連一次庫、token 輪換要手動改表、多個服務之間憑證不互通。當你有三四個 MCP Server 時維護成本就上來了。更省事的思路是把簽發(fā)和校驗憑證這件事交給一個統(tǒng)一入口MCP Server 只負責拿請求頭里的 Key 去問一句這個 Key 有效嗎。TaoToken 在這里扮演的就是這個統(tǒng)一 Key 層——你可以在它的控制臺里生成和管理 API KeyMCP Server 側只需要配置 Base URL、Key、Model ID 三件套里的前兩件用于鑒權校驗工具本身要調模型時再補上 Model ID。先做前置準備。打開 TaoToken 官網 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注冊并進入控制臺在 API Keys 頁面創(chuàng)建一個 Key。這個 Key 就是你后面要寫進 MCP 客戶端 header 的憑證??刂婆_地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Keys 管理頁在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到 Key 之后本地環(huán)境需要確認幾件事。Python 側裝好 FastMCP 和 HTTP 相關依賴pip install fastmcp httpx starlette uvicornFastMCP 的中間件基類在fastmcp.server.middleware下HTTP 請求對象通過get_http_request()獲取。注意這個函數(shù)只在 HTTP 傳輸下有效標準 I/O 傳輸拿不到 header——這也是為什么鑒權中間件只對 HTTP 模式有意義。如果你同時支持兩種傳輸中間件里要先判斷傳輸類型否則 stdio 模式下會直接報錯。配置層面建議把 TaoToken 的校驗地址和你的 Key 放進環(huán)境變量別硬編碼export TAOTOKEN_API_BASEhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的Key export MCP_SERVER_PORT18088這里TAOTOKEN_API_BASE用不帶 UTM 的 API 地址 https://taotoken.net/api 因為它是程序調用的端點不需要追蹤參數(shù)。Key 從環(huán)境變量讀中間件里用os.environ.get()取這樣換 Key 不用改代碼。還有一點要提前想清楚鑒權中間件校驗的是調用方有沒有資格訪問這個 MCP Server而 TaoToken 的 Key 校驗的是這個 Key 有沒有資格用 TaoToken 的服務。兩者可以合一——直接把 TaoToken 的 Key 當作 MCP Server 的訪問憑證中間件拿它去調一次 TaoToken 的接口驗證有效性。這樣團隊里每個人用自己的 TaoToken Key你不需要再維護一張 token 表。下面第三節(jié)就給這個方案的完整配置。3. FastMCP 鑒權中間件可復制配置與 TaoToken Key 校驗片段這一節(jié)是核心直接給能跑的代碼。先看中間件本體它攔截所有 MCP 消息從 HTTP header 取Authorization解析出 Bearer token然后校驗。import os import logging from fastmcp import FastMCP from fastmcp.server.middleware import Middleware, MiddlewareContext from fastmcp.server.dependencies import get_http_request logger logging.getLogger(mcp.auth) TAOTOKEN_API_BASE os.environ.get(TAOTOKEN_API_BASE, https://taotoken.net/api) TAOTOKEN_API_KEY os.environ.get(TAOTOKEN_API_KEY, ) class AuthMiddleware(Middleware): async def __call__(self, context: MiddlewareContext, call_next): # stdio 傳輸沒有 HTTP 請求對象直接放行 try: request get_http_request() except Exception: return await call_next(context) authorization request.headers.get(authorization) if not authorization or not authorization.startswith(Bearer ): raise PermissionError(401 Authorization Required) access_token authorization.split( , 1)[1].strip() if not access_token: raise PermissionError(401 Authorization Required) # 校驗 token 是否有效這里用 TaoToken 的 Key 作為統(tǒng)一憑證 if not await self._verify_token(access_token): raise PermissionError(403 Forbidden) logger.info(auth passed for method%s, context.method) return await call_next(context) async def _verify_token(self, token: str) - bool: # 簡單策略與配置的 Key 比對生產環(huán)境可換成調用 TaoToken 校驗接口 if TAOTOKEN_API_KEY and token TAOTOKEN_API_KEY: return True # 也可以在這里調用 TaoToken 的接口做在線校驗 return False把中間件掛到 FastMCP 實例上mcp FastMCP(secure-mcp-server) mcp.add_middleware(AuthMiddleware()) mcp.tool() def execute_mysql_sql(sql: str) - str: # 你的工具邏輯 return fexecuted: {sql} if __name__ __main__: mcp.run(transportstreamable-http, host0.0.0.0, port18088)如果你更習慣用配置文件管理FastMCP 支持從 JSON 讀取服務定義。下面是一個mcp_config.json片段把鑒權相關的環(huán)境變量和傳輸方式寫進去{ mcpServers: { secure-mysql: { transport: streamable-http, url: http://127.0.0.1:18088/mcp/, headers: { Authorization: Bearer ${TAOTOKEN_API_KEY} }, env: { TAOTOKEN_API_BASE: https://taotoken.net/api } } } }注意headers里的Authorization就是客戶端要帶的憑證${TAOTOKEN_API_KEY}從環(huán)境變量注入避免明文寫進配置文件。這個 JSON 結構可以直接被支持 MCP 配置的客戶端讀取。如果你用的是 Cline 或 Claude Code 這類工具它們的 MCP 配置通常放在settings.json或claude_desktop_config.json里結構類似{ mcpServers: { secure-mysql: { command: python, args: [-m, your_mcp_server], env: { TAOTOKEN_API_BASE: https://taotoken.net/api, TAOTOKEN_API_KEY: sk-你的Key } } } }這里要強調三件套的完整性Base URL 用https://taotoken.net/apiKey 用你在控制臺生成的sk-開頭字符串Model ID 在工具內部需要調模型時再指定比如claude-sonnet-4-5之類以控制臺實際可用的為準。鑒權中間件只用到前兩件第三件是工具執(zhí)行階段的事別混在一起。中間件里_verify_token目前是簡單比對生產環(huán)境建議改成調用 TaoToken 的校驗接口這樣 Key 的吊銷和過期由 TaoToken 側統(tǒng)一管理你的 MCP Server 不用重啟。調用方式就是拿access_token去請求 TaoToken 的 API返回 200 即有效。具體接口路徑參考接入文檔 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。配置寫完后啟動服務python your_mcp_server.py看到 uvicorn 監(jiān)聽 18088 端口就說明起來了。下一節(jié)驗證請求。4. 帶鑒權頭的 JSON-RPC 請求驗證與成功結果確認服務起來后先驗證未授權被攔截。用 curl 直接發(fā)一個不帶 header 的 JSON-RPC 請求curl -X POST http://127.0.0.1:18088/mcp/ \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 1, method: tools/list, params: {} }預期返回 401 或 403body 里能看到Authorization Required或Forbidden。這一步確認中間件確實攔住了沒有憑證的請求。再驗證錯誤憑證被拒絕curl -X POST http://127.0.0.1:18088/mcp/ \ -H Content-Type: application/json \ -H Authorization: Bearer wrong-token-12345 \ -d { jsonrpc: 2.0, id: 2, method: tools/list, params: {} }預期返回 403。如果這里返回了 200 并且列出了工具說明你的_verify_token邏輯有問題檢查是不是把空 token 也放行了。最后驗證正確憑證正常返回curl -X POST http://127.0.0.1:18088/mcp/ \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key \ -d { jsonrpc: 2.0, id: 3, method: tools/list, params: {} }成功的話會返回工具列表類似{ jsonrpc: 2.0, id: 3, result: { tools: [ { name: execute_mysql_sql, description: 執(zhí)行 SQL 查詢, inputSchema: { type: object, properties: { sql: { type: string } } } } ] } }再發(fā)一個tools/call驗證工具能真正執(zhí)行curl -X POST http://127.0.0.1:18088/mcp/ \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key \ -d { jsonrpc: 2.0, id: 4, method: tools/call, params: { name: execute_mysql_sql, arguments: { sql: select 1 } } }返回result.content里有執(zhí)行結果就說明整條鏈路通了??蛻舳藗萈ython 的streamablehttp_client加 header 的方式from mcp.client.streamable_http import streamablehttp_client from mcp import ClientSession async def connect(): headers {Authorization: Bearer sk-你的Key} async with streamablehttp_client( urlhttp://127.0.0.1:18088/mcp/, headersheaders ) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() print([t.name for t in tools.tools])跑通后你會看到工具名打印出來。如果客戶端報連接錯誤先確認 header 拼寫是Authorization而不是authorizationHTTP header 大小寫不敏感但有些客戶端庫會嚴格匹配以及 Bearer 后面有一個空格。5. FastMCP 鑒權中間件常見報錯排查401、local proxy failed 與 reading choices實際接入時踩的坑集中在幾個報錯上逐個對照。401 Authorization Required中間件拋出的第一個錯誤說明請求頭里沒有Authorization字段或者格式不是Bearer xxx。檢查客戶端配置里 header 的 key 是不是寫成了Auth、Token之類的自定義名。FastMCP 的get_http_request().headers.get(authorization)只認標準名。另外注意有些客戶端會把 header 嵌套在headers對象里別寫成頂層字段。403 Forbiddenheader 格式對但 token 校驗沒過。常見原因是環(huán)境變量沒注入——TAOTOKEN_API_KEY在服務進程里是空字符串導致任何 token 都比對失敗。用echo $TAOTOKEN_API_KEY確認或者在中間件里加一行日志打印收到的 token 前幾位。還有一種情況是 Key 復制時帶了首尾空格strip()一下。local proxy failed這個報錯通常出現(xiàn)在客戶端側說明客戶端嘗試連接 MCP Server 時網絡層就失敗了根本沒到鑒權中間件。檢查三件事服務是否真的在監(jiān)聽netstat -tlnp | grep 18088、URL 路徑是否帶對了/mcp/結尾的斜杠、防火墻是否放行。如果服務在容器里127.0.0.1要換成容器實際 IP 或host.docker.internal。reading choices 相關報錯這類錯誤一般出現(xiàn)在工具執(zhí)行階段模型返回的響應結構不符合預期比如choices字段為空或格式變了。它和鑒權中間件沒有直接關系但容易被誤判成鑒權問題。排查方法是先確認鑒權已通過日志里有auth passed再單獨測工具邏輯。如果工具內部調用了模型接口檢查 Model ID 是否寫對、Base URL 是否是https://taotoken.net/api。Model ID 寫錯時接口通常返回 404 或 400而不是 401。OAuth 相關報錯如果你在中間件里接了 OAuth 流程報invalid_grant或token expired說明憑證過期了。用 TaoToken 統(tǒng)一 Key 的好處就在這里——過期和吊銷在控制臺處理MCP Server 側不用改代碼。重新生成 Key 后更新環(huán)境變量重啟即可。中間件不生效請求沒帶 header 卻返回了 200。檢查mcp.add_middleware(AuthMiddleware())是否在mcp.run()之前調用以及是否真的走了 HTTP 傳輸。stdio 模式下get_http_request()會拋異常如果你的代碼里把這個異常吞掉了直接call_next那 stdio 請求就繞過了鑒權——這是設計如此但如果你只想要 HTTP 模式就別開 stdio。排查時建議在中間件里加結構化日志把context.method、請求來源 IP、校驗結果都打出來。這樣出問題時一眼能看出是沒收到請求、還是收到了但校驗失敗。6. 把統(tǒng)一 Key 接進你的 MCP 工作流鑒權中間件跑通之后你的 FastMCP Server 就從局域網裸奔變成了憑證準入。團隊里每個人用自己的 TaoToken Key你在控制臺統(tǒng)一管理簽發(fā)和吊銷MCP Server 側只保留一段校驗邏輯。工具本身要調模型時同一套 Key 直接復用Base URL 用 https://taotoken.net/api Model ID 按控制臺實際可用的填。如果你還在本地調試階段想先驗證模型對話鏈路可以去模型對話頁 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 試一下 Key 是否可用。長期跑編碼類 Agent、需要穩(wěn)定額度的看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入過程中遇到鑒權或配置問題接入文檔 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里有完整的參數(shù)說明API Keys 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 管理。最后留一個實用技巧中間件里校驗通過后可以把access_token對應的用戶身份塞進context的擴展字段這樣工具執(zhí)行時能拿到是誰在調方便做審計日志和按用戶限流。FastMCP 的MiddlewareContext支持附加數(shù)據(jù)具體字段名參考文檔別硬編碼。