MCP Server 如何代表使用者呼叫你的 API:生產級 token 策略實務
說明 MCP 伺服器的對外權杖策略:為什麼權杖直通和 M2M 會失敗,以及權杖交換如何讓權限與用戶保持一致。
說明 MCP 伺服器的對外權杖策略:為什麼權杖直通和 M2M 會失敗,以及權杖交換如何讓權限與用戶保持一致。
在上一篇文章裡,我們分享了 Logto 建置 Remote MCP Server 的整體經驗, 這篇文章我們將講解 Remote MCP Server 架構設計和 OAuth 認證流程的實作細節。
以 MCP Server 為分界,Remote MCP Server 的認證分**入站(inbound)和出站(outbound)**兩段:
入站這部分,MCP 規範定義得很清楚,社群的討論和實作都很豐富,我們之前寫過一篇實作指南。出站的討論就少多了:MCP Server 手裡只有一張「用於存取 MCP Server」的 token,它要如何代表使用者去呼叫業務 API?
這正是我們在建置 Logto MCP Server 時反覆糾結的問題。Logto Cloud 是典型的 B2B 多租戶產品:一個使用者可以屬於多個 tenant,權限由使用者在每個 tenant 裡的角色決定,AI 不僅要代表使用者行事,還要落到正確的 tenant。
這篇文章會沿著我們實際的決策過程展開:
在 OAuth 的視角裡,Remote MCP Server 同時扮演兩個角色:
也就是說,MCP Server 在兩側扮演不同的角色,各自對應的是不同的 token:MCP client 透過 OAuth 拿到的 token,audience 是 MCP Server,業務 API 驗證 audience 時會直接拒絕它。
所以出站的本質是一個身分委託(delegation)問題:MCP Server 如何在不持有使用者憑證的前提下,以使用者的身分、在使用者的權限範圍內呼叫下游 API。
把 MCP endpoint 做進業務 API 服務,共用同一個程序、同一套認證,看起來是最直接的做法。但經過考量,我們還是選擇了獨立部署:
MCP Server 部署在獨立的網域 mcp.logto.io 下,和主服務之間沒有任何私有耦合,未來想把它開源出去都沒有障礙。
內嵌方案也繞不開 token 問題:MCP endpoint 和業務 API 共享同一個 resource identifier,MCP client 拿到的 token 直接攜帶完整 API 的權限;「這次呼叫以誰的權限執行」從跨服務的 token 交換變成程序內的權限傳遞,問題本身還在。獨立部署要求我們把權限邊界顯式設計出來,這也是這篇文章接下來的內容。
出站的討論要從一個更基礎的問題開始:MCP client 拿到的 token,audience 應該是誰?
直接複用業務 API 的 resource identifier 是最直接的做法:Logto 的 Management API 本身就是標準的 OAuth protected resource,讓 MCP client 直接申請它的 token,MCP Server 原樣轉發。不少 MCP Server 的早期實作就是這麼做的。
代價在於:如果 MCP token 的 audience 是業務 API,權限邊界就不存在了。
所以我們的第一個決定是:MCP Server 是一個獨立的 protected resource,有自己的 resource identifier(https://mcp.logto.io)和對應的 scope。
這個決定讓權限邊界乾淨了,出站的問題也隨之變得具體:token 只能存取 MCP Server,那 MCP Server 拿什麼去呼叫 API?
下面進入方案演進。
第一個想法來自一個很自然的類比。
Logto Console 是一個 SPA,它呼叫 Management API 的方式很簡單:使用者在瀏覽器裡 OAuth 登入,拿到 audience 為 Management API 的 token,前端直接帶著它呼叫 API。
那 MCP Server 是不是也可以當作另一種 Console?讓 MCP client 在登入時直接申請業務 API 的 token,MCP Server 不做任何轉換,原樣轉發:
這個方案的誘人之處就在於它極其簡單,MCP Server 只負責轉發 token。但是它有顯而易見的問題:
第一,它和上一節的權限邊界決定直接衝突。 Token 透傳要求 MCP client 持有業務 API 的 token,這正是我們剛剛否決掉的事情。
第二,MCP Server 不再是真正的 protected resource。 它收到的 token audience 不是自己,audience 驗證形同虛設,只能退化成「驗證簽章和 issuer」。這不符合 MCP 規範對 authorization 的定義(MCP Server 應作為 resource server,透過 RFC 9728 的 Protected Resource Metadata 宣告自己),本質上是把一個後端服務偽裝成了瀏覽器裡的 SPA。
第三,MCP client 不配合。 規範的 MCP client 按 Protected Resource Metadata 的指引申請 token,audience 就是 MCP Server。沒有標準的方式讓它去申請業務 API 的 token,這條路在用戶端側走不通。
既然不能用使用者的 token,那用 MCP Server 自己的身分呢?
給 MCP Server 建一個 M2M(machine-to-machine)應用程式,用 client credentials 拿 token,去呼叫業務 API。這也是很多內部服務之間的標準做法。
入站認證也順了:MCP client 的 token audience 是 MCP Server,MCP Server 正常驗證,然後用自己的 M2M token 做事。
這個方案的致命缺陷是,M2M token 的權限和使用者的權限沒有任何關係:
M2M 適合排程任務、系統間同步這類沒有使用者上下文的場景。MCP Server 不一樣,每次呼叫都由具體的使用者發起,就應該以這個使用者的身分和權限去執行。
前兩個方案的教訓彙總起來,就是理想方案的要求:
這指向一個標準機制:token exchange(RFC 8693)。MCP Server 拿一張代表使用者的憑證去 auth server 交換下游 API 的 token,使用者的身分和權限在交換中保留。
在 Logto 裡,這張「代表使用者的憑證」由 user impersonation 能力提供:subject token。它是伺服器端為指定使用者申請的短期憑證,語義是「接下來這次 token exchange,以這個使用者的身分進行」。使用者不需要手動建立或設定任何東西,全程自動化;subject token 短期、一次性,用完即失效。
整個架構裡有四個角色,MCP Server 獨立部署在 mcp.logto.io:
一次 tool 呼叫背後的完整 token 流轉:
逐步拆解:
① 入站驗證。MCP client 帶著 user token 呼叫 tool。這個 token 的 audience 是 MCP Server 自己的 resource identifier,scope 是 mcp:all。MCP Server 驗證簽章、issuer、audience、scope,拿到使用者身分。入站到此為止,這個 token 不會再往下游走。
② 服務身分認證。MCP Server 用自己的 M2M 憑證換一個 access token,scope 是專設的 access:mcp:api,它只有一個用途:呼叫下一步的專用介面。
③ 申請 subject token,整條流程的關鍵一步。MCP Server 呼叫 Cloud 專門為 MCP 開的介面 POST /api/mcp/subject-tokens,同時出示兩個憑證:
Authorization 標頭:M2M token,證明「我是官方 MCP Server」x-mcp-user-token 標頭:使用者的 user token,證明「這個使用者確實授權過我,且授權仍然有效」Cloud 這邊會對 user token 做完整驗證:簽章、issuer、有效期限,audience 必須是 MCP Server 的 resource identifier,scope 必須包含 mcp:all。驗證通過後,userId 直接取自 user token 的 sub claim,介面沒有「指定使用者」的參數。
這樣設計是為了防止 M2M 憑證被濫用:如果介面接受任意 userId,拿到 M2M 憑證就能冒充任何使用者;在這個設計下,MCP Server 只有在某個使用者的有效授權在場時,才能為這個使用者兌換憑證。
簽發出來的 subject token 短期、一次性,實作裡不做快取,每次使用都現場申請。
④ 換出工作用的 token。拿 subject token 走標準 token exchange,視需要換兩種 token:
⑤ 出站呼叫。帶著換來的 token 呼叫業務 API,做完事把結果回傳給 MCP client。
多租戶的 tenant 上下文也在交換這一步解決:tenant 不在連線層,而在交換層。list_tenants 用 Cloud API token 列出選項,使用者在對話裡選一個,MCP Server 再為選中的 tenant 換 org token。一個 endpoint 服務所有 tenant,不需要為每個 tenant 單獨部署,對話中途新建的 tenant 也即刻可用。
把前面每個方案的失敗點拿回來對照,會發現它們都被逐一堵上了:
x-mcp-user-token 驗證失敗,出站流程當場斷掉回頭看,M2M 在最終方案裡有了正確的分工:它負責證明「我是誰」,而「以使用者身分行事」的資格,必須由使用者的有效授權當場兌換。
回顧整條流程,Remote MCP Server 的 token 策略可以歸納成幾條:
一個簡單的檢驗方法:假設 MCP client 手裡的 token 洩漏,攻擊者能做的應該只有「呼叫 MCP Server 上那幾個受控的 tools,且仍受使用者權限約束」。如果能直接呼叫到完整的 API,表示權限邊界有問題。
MCP 生態還在快速演進,入站認證已經被規範覆蓋得很好,「MCP Server 如何呼叫下游」目前還是各家自己發揮的地帶。希望我們的實務經驗能給你一些參考。
如果你在給自己的產品做 MCP Server,可以看看 Logto 為 AI 場景提供的完整方案:Auth for AI apps, agents, and MCP servers。想看這套 token 流程的實際效果,可以直接連線 Logto MCP Server 體驗。
文中涉及的技術文件: