教程

MCP Server 如何添加 OAuth 2.1 身份認證?從 Authorization Server 到 Access Token 的 Remote MCP API 實戰教程

無狀態 MCP 解決的是會話,不是誰能調你的工具。Remote MCP 一旦掛到公網,身份認證必須從 Authorization Server 發現走到 Access Token,而不是在 JSON-RPC 裏塞一把共享密鑰。

2026-07-28 的 MCP 把 HTTP 傳輸上的授權寫成可選;一旦要保護資源,就必須按 OAuth 2.1 子集落地。Cursor、Claude Desktop、OpenAI Responses 遠程 MCP、自建 Agent Host,都會先對 Remote MCP 發一次不帶 token 的請求。Server 回 401,並在 WWW-Authenticate 裏指出 Protected Resource Metadata。客戶端再去發現 Authorization Server、完成登記、走授權碼 + PKCE、帶上 resource 參數,換到 Access Token,然後在每一跳 HTTP 上放 Authorization: Bearer。這篇按這條 Remote MCP API 鏈路寫實戰:角色怎麼分、metadata 長什麼樣、token 怎麼驗 audience、scope 不夠怎麼 step-up。規範原文見 MCP Authorization。站內已有 MCP 是什麼、Stateless MCP、inputSchema 與 Tool Calling,本文不重複那些層,只補「誰被允許調用」。STDIO 傳輸不要走這套瀏覽器跳轉,憑證從環境變量取。

三方角色:MCP Server 不負責發 Token

最容易寫錯的第一件事,是把 MCP Server 當成發 token 的人。規範裏,受保護的 MCP Server 是 OAuth 2.1 Resource Server:它接受、校驗、按 audience 消費 Access Token。MCP Client 是 OAuth 2.1 客戶端,代表資源所有者去要 token,再帶 token 調工具。Authorization Server(AS)負責登錄、同意、簽發。AS 可以和 Resource Server 同機,也可以是公司已有的 IdP(Okta、Keycloak、Auth0、自建 OIDC)。規範不規定你怎麼實現 AS,只規定 MCP Server 如何把它的位置告訴客戶端——見 Authorization Server Discovery。

授權對 MCP 實現是可選的。HTTP 傳輸一旦要保護資源,就應該遵守這套規範。STDIO 明確不應該跟這套走。把共享 API Key 塞進 tools/call 的 params,或把 token 放進 JSON-RPC _meta,都不是 2026-07-28 的 Remote MCP 認證。認證在 HTTP 傳輸層,不在方法信封裏。

對照站內分層:MCP 是發現與調用協議;無狀態化拿掉的是 Session,不是鑑權;inputSchema 管 arguments 形狀,驗過 Schema 也不等於調用者有權。OAuth 回答的是「這個 Bearer 是不是籤給本資源、scope 夠不夠」。Agent 循環本身見 AI Agent 是什麼。

角色 OAuth 2.1 身份 你要交付什麼
MCP Client / HostOAuth Client發現、PKCE、存 token、每跳帶 Bearer
MCP ServerResource Server401 + PRM、驗 token、audience 綁定
Authorization Server簽發方 / IdP登錄、同意、授權碼、Access Token

發現 Authorization Server:401 與 RFC 9728

實戰從一次失敗的調用開始。Client 對 https://mcp.example.com/mcp 發 tools/list 或任意 JSON-RPC,不帶 Authorization。Server 必須回 HTTP 401,並在 WWW-Authenticate 裏帶 Bearer 與 resource_metadata。scope 建議帶上,告訴客戶端這次最少要申請什麼。客戶端必須能解析這個頭:有 resource_metadata 就用它,沒有再按 RFC 9728 探測 well-known。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
  scope="invoices:read"

MCP Server 必須實現 OAuth 2.0 Protected Resource Metadata(RFC 9728)。文檔裏 authorization_servers 至少寫一個 AS issuer,還可以寫 resource(本 Server 的規範 URI)、scopes_supported、bearer_methods_supported。客戶端拿到後再去拉 AS metadata:無路徑的 issuer 先試 /.well-known/oauth-authorization-server,再試 OpenID openid-configuration;帶 tenant 路徑的按規範優先級插入路徑。拉回來的文檔裏 issuer 必須與用來構造 URL 的 issuer 字符串完全一致,否則視爲攻擊,整份 metadata 丟掉。

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": [
    "https://auth.example.com"
  ],
  "bearer_methods_supported": ["header"],
  "scopes_supported": [
    "invoices:read",
    "invoices:write"
  ],
  "resource_documentation": "https://mcp.example.com/docs"
}

上面是 401 與一份最小可用的 Protected Resource Metadata。resource 不要帶 fragment,也不要省略 scheme。帶不帶尾斜線要全站統一,規範建議無語義時不要尾斜線。多寫幾個 AS 時,每一個都是獨立簽發方,客戶端必須分開存登記狀態。

登記客戶端:CIMD、預註冊與 DCR

發現完 AS,Client 必須先有 client_id,才能發授權請求。三種機制,按規範優先級:① Client ID Metadata Documents(CIMD)——client_id 本身是一個 HTTPS URL,AS 去拉這份 JSON,校驗 redirect_uris;② 預註冊,控制檯裏發的機密或公開客戶端;③ Dynamic Client Registration(RFC 7591)POST /register。規範寫明 DCR 已棄用,只爲兼容還不支持 CIMD 的 AS 保留。新項目優先 CIMD 或預註冊。

多個 authorization_servers 時,每一個都是獨立的 AS。client_id、密鑰、token 按 AS 分開存,絕不能拿 A 的憑證去打 B 的 token 端點。這是 mix-up / confused deputy 的常見入口。

公開客戶端(桌面 Host、瀏覽器擴展)必須按 OAuth 2.1 做 PKCE。機密客戶端也建議做。不要自己發明「把 client_secret 寫進 MCP Server 環境變量、再轉發給 AS」這種代理登記——MCP Server 是 Resource Server,不是 Client。

授權碼 + PKCE + resource:換到 Access Token

登記之後纔是授權碼流。Client 在打開瀏覽器之前必須:生成 PKCE code_verifier / code_challenge;在授權請求和後續 token 請求裏都帶 resource,值是 MCP Server 的規範 URI(RFC 8707);按 scope 策略選權限(優先 401 裏的 scope,否則 scopes_supported);把已校驗的 AS issuer 記在與 code_verifier 同一條請求記錄裏。用戶同意後,回調裏的 iss 按 RFC 9207 與記錄值做簡單字符串比較,不要做大小寫摺疊或去尾斜線。

Token 請求帶 code、code_verifier、resource。成功響應是 JSON:access_token、token_type 爲 Bearer、expires_in,可能有 refresh_token 與 scope。不要假設一定有 refresh token。MCP Server 的 scopes_supported 和 401 的 scope 裏不要放 offline_access——那是 Client 向 AS 要的,不是資源需求。AS metadata 的關鍵字段如下;authorization_response_iss_parameter_supported 爲 true 時,缺少 iss 的回調必須拒絕。

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/oauth/authorize",
  "token_endpoint": "https://auth.example.com/oauth/token",
  "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
  "registration_endpoint": "https://auth.example.com/oauth/register",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "response_types_supported": ["code"],
  "token_endpoint_auth_methods_supported": [
    "none",
    "client_secret_basic"
  ],
  "authorization_response_iss_parameter_supported": true,
  "scopes_supported": [
    "invoices:read",
    "invoices:write",
    "offline_access"
  ]
}

Access Token 對 Client 常常是不透明字符串;排障時它經常是 JWT。先看 iss、aud(或 resource)、scope、exp、client_id。aud 必須綁到本 MCP Server,不能拿着「給支付 API 的 token」來調 tools/call。下面這枚 payload 的 aud 正確;若改成別的 API 的 URI,Resource Server 必須 401。

{
  "iss": "https://auth.example.com",
  "sub": "user_1842",
  "aud": "https://mcp.example.com/mcp",
  "client_id": "https://host.example.com/oauth/client.json",
  "scope": "invoices:read",
  "exp": 1790000000,
  "iat": 1789996400
}

Bearer 調 Remote MCP API,並校驗 audience

拿到 token 之後,每一跳 Client → MCP Server 的 HTTP 都必須帶 Authorization: Bearer,包括髮現、tools/list、tools/call、resources/read。Token 禁止放進 query。JSON-RPC 方法名與 arguments 仍按原樣放在 body 裏——認證頭和信封是兩層。無狀態 MCP 下沒有 Session 可以「登錄一次後面免檢」;每次請求自帶鑑權。對照 Stateless MCP。

POST /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Accept: application/json, text/event-stream

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "searchInvoices",
    "arguments": {
      "startDate": "2026-01-01",
      "endDate": "2026-01-31",
      "status": "paid"
    }
  }
}

Server 按 OAuth 2.1 §5.2 驗 token:簽名或 introspection、exp、iss、以及 RFC 8707 的 audience。失敗一律 401。驗過 audience 仍要確認這個 token 是本 AS 簽發的。Server 不得接受或轉發「給別人用的」token。Client 也不得把別的資源的 token 發給這個 MCP Server。這是 confused deputy 的硬約束。

鑑權通過不等於 arguments 合法。Bearer 只回答「誰、對哪個資源、哪些 scope」。searchInvoices 的日期和枚舉仍要 parse + Schema + 業務規則。不要把「token 有效」寫成 tools/call 的唯一門衛。形狀契約見 MCP 與 JSON Schema。

Scope、403 與 JSONVue 聯調

已有 token 但 scope 不夠時,Server 應回 403,WWW-Authenticate 帶 error="insufficient_scope"、本次操作需要的 scope、以及同樣的 resource_metadata。一次挑戰裏給齊本次所需 scope,不要一次缺一個逼用戶點兩次同意。Client 做 step-up 時要取「舊 scope ∪ 挑戰 scope」,避免丟掉其他工具已經拿到的權限。

HTTP 含義 下一步
401未認證或 token 無效 / 過期 / audience 不對讀 PRM,重新走授權或換 token
403 + insufficient_scopetoken 有效,權限不夠step-up:合併 scope 再授權
400授權請求畸形先修 Client 的請求,不要重試業務
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
  scope="invoices:write",
  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
  error_description="Write scope required to create a credit note"

聯調時你會經手至少四份 JSON:Protected Resource Metadata、AS metadata、token 響應、(若是 JWT)payload。再加一份 tools/call 的 params.arguments。它們職責不同,不要揉進一個文件做「通用校驗」。失敗樣本各留一條:缺 authorization_servers 的 PRM、issuer 不一致的 AS metadata、aud 綁錯的 JWT、缺字段的 arguments。

瀏覽器裏即可完成:JSON 格式化看 metadata 是否能 parse;JWT 解碼看 aud / scope / exp;JSON Schema 校驗對你爲 PRM 寫的契約;JSON Diff對比刷新前後的 payload,或模型 arguments 與 MCP params。數據不離開本機。

延伸閱讀:MCP 是什麼、Stateless MCP、MCP 與 JSON Schema、A2A vs MCP、AI Agent 是什麼。

常見問題 FAQ

本地 STDIO MCP 也要上 OAuth 2.1 嗎?

不要。規範寫明 STDIO 不應走這套瀏覽器授權,憑證從環境變量取。OAuth 2.1 是給 HTTP Remote MCP 的。同一套工具可以同時提供 STDIO 與 Streamable HTTP,但只有 HTTP 面需要 401、PRM 和 Bearer。

能不能把 Access Token 放進 JSON-RPC params 或 _meta?

不能當作規範實現。Access Token 必須放在 HTTP Authorization 頭,且不得出現在 query。信封裏的 _meta 管協議版本等,不管登錄。把 token 寫進 body,等於把它送進日誌、代理和模型上下文。

自己簽發 JWT、Server 用共享密鑰驗,算實現了 MCP OAuth 嗎?

只做驗籤不夠。Client 發現的是 Authorization Server,不是「Server 內嵌的密鑰」。你仍要提供 RFC 9728 metadata、authorization_servers、帶 resource 的授權/token 請求、audience 綁定,以及 401 / 403 挑戰。自建 AS 可以發 JWT,但發現與 resource 參數不能省。

前面已經有 API Gateway 在驗 JWT,還要再做一遍嗎?

網關可以替你驗簽名和過期,但 MCP Host 還要能發現 AS、帶 resource、讀 WWW-Authenticate。只在網關驗「任意合法 JWT」、不綁 audience,會把發給其他 API 的 token 放進來。把 MCP 當普通 REST 護着,Host 往往連不上。

總結與下一步

OAuth 2.1 在 Remote MCP 裏不是「再加一個登錄頁」,而是一條從 401 發現、AS metadata、客戶端登記、PKCE、resource、Access Token 到每跳 Bearer 的傳輸層合同。MCP Server 是 Resource Server;發 token 的是 Authorization Server。

落地順序:先讓 401 + Protected Resource Metadata 能被現有 MCP Client 解析,再接 AS,最後才寫 tools/call 的業務。用 JSONVue 把 metadata 和 JWT 樣本留在本地,和 Schema 校驗分開。需要協議入口讀 MCP 是什麼;需要無狀態傳輸讀 Stateless MCP。