CodeGym /課程 /ChatGPT Apps /透過 MCP Jam 測試登入與存取:None、Bearer、OAuth with credentials、Def...

透過 MCP Jam 測試登入與存取:None、Bearer、OAuth with credentials、Default OAuth

ChatGPT Apps
等級 10 , 課堂 4
開放

1. 將 MCP Jam 作為授權實驗室

MCP Jam 並不是「又一個奇怪的工具」,而是你的實驗平台,能扮演 MCP 客戶端的角色。從本質上看,這是 ChatGPT 與 MCP 伺服器互動行為的模擬器:它能讀取 .well-known/oauth-protected-resource、啟動 OAuth 流程、把 token 掛到請求上,並清楚地顯示是哪一步出了問題。

非常重要的實務重點:如果你能在 MCP Jam 中跑通 Default OAuth 流程,那你大概已經完成與真實 ChatGPT App 整合的 80%。ChatGPT 在綁定帳號(linking)時會做的事,Jam 都會,而且還提供更透明的日誌與按鈕操作。

在上一堂課,我們為教學用的 MCP 伺服器 GiftGenius 設定了基礎授權:選定 token 驗證方式(JWT 或 introspection)、實作 .well-known/oauth-protected-resource,以及保護工具的 middleware。現在我們來看看,在 MCP Jam 的不同授權模式下,這些設定會如何表現。

本課目標—學會:

  • 在 Jam 中有意識地切換授權模式(NoneBearerOAuth with credentialsDefault OAuth);
  • 理解 Jam 在各模式下到底會送什麼給 MCP 伺服器;
  • 診斷是哪一部分壞了:MCP Server、Auth Server,或是中繼資料;
  • 確認受保護的工具只在有 token 時可用,而開放的工具則可在無 token 下使用。

2. 我們的教學用 MCP 伺服器:要測什麼

為了不空談,我們先回顧一下上下文。延續我們的教學應用 GiftGenius——這是個 ChatGPT‑App,協助挑選禮物,並向使用者顯示其訂單與願望清單。

在 MCP 伺服器端,我們已有:

  • 開放的工具,例如 search_gifts——可匿名呼叫;
  • 受保護的工具,例如 list_user_orders——只能在使用者已驗證的情況下執行,且需要 scope mcp:tools

伺服器具備:

  • 發布 .well-known/oauth-protected-resource
  • 驗證 token(JWT 或透過 introspection——你在上一課已選定其一);
  • 從 token 中擷取 sub(user id)、scopeaud,並傳遞給工具的處理器。

在 Node.js/TypeScript 中,典型的 token 檢查 middleware 可能長這樣:

// middleware/auth.ts
export function requireScope(requiredScope: string) {
  return async (req: any, res: any, next: () => void) => {
    const header = req.headers["authorization"];
    if (!header?.startsWith("Bearer ")) {
      res
        .status(401)
        .set(
          "WWW-Authenticate",
          `Bearer realm="mcp", resource_metadata="${process.env.BASE_URL}/.well-known/oauth-protected-resource", scope="${requiredScope}"`
        )
        .json({ error: "unauthorized" });
      return;
    }

    // 此處你們已經驗證 token(簽章、exp、aud、scope...)
    // 並把結果放到 req.user
    next();
  };
}

這個 middleware 將用於 MCP 的受保護工具之前。若沒有 token——我們回傳 401,並附上正確的 WWW-Authenticateresource_metadata,符合 MCP Authorization 規範的要求。關於 token 驗證與輔助函式的詳解你已在前一堂完成,這裡直接視為已就緒。

3. MCP Jam 的授權模式:概覽

MCP Jam 提供多種連線至 MCP 伺服器的授權模式。它們對應典型的 OAuth 型態:從完全沒有 token,到完整的 Authorization Code + PKCE。

簡述如下:

  1. None (No Auth)——Jam 完全不會加入 Authorization 標頭。這是匿名存取。適用於開放的 MCP 伺服器,以及確認受保護資源能正確回覆 401WWW-Authenticate
  2. Bearer Token——Jam 加上 Authorization: Bearer <token>,而這個 token 由你在介面中手動貼上。適合快速檢查:token 已在其他地方取得(curl、Keycloak UI),你想測試 MCP 資源的行為。
  3. OAuth with credentials (Client Credentials)——Jam 使用你提供的 Client ID 與 Secret,對 Auth Server 以 client_credentials 取得 token。這是「機密型客戶端」模式,更像是伺服器對伺服器、無使用者參與的授權。
  4. Default OAuth (Authorization Code + PKCE)——面向 ChatGPT 類客戶端的主要模式(無密鑰的 public client)。Jam 會讀取 resource_metadata、找到 Auth Server、以 /authorize 啟動瀏覽器、跑完 PKCE 流程並取得使用者 token。

為了直觀,我們整理成表格。

Jam 模式 Jam 發送內容 誰取得 token 典型情境
None Authorization 匿名工具、檢查 401
Bearer Token Bearer <手動> 你(curl、IdP UI) 測試 Resource Server 邏輯
OAuth with cred. Bearer <client token> Jam 透過 client_credentials 服務/管理工具
Default OAuth Bearer <user token> Jam 以 Authorization Code+PKCE 如同 ChatGPT 的使用者登入

接下來我們逐一走過每個模式,看看如何把它們套用到 GiftGenius MCP 伺服器上。

4. 模式 None:確認伺服器正確拒絕

先從最原始的模式開始:完全不做授權。

在 MCP Jam 中,選擇你的伺服器(例如 http://localhost:4000/mcp),並在連線設定裡把授權模式設為 None

此時會發生:

  • Jam 建立 MCP 連線;
  • 呼叫工具時不會加入 Authorization 標頭;
  • 你可以呼叫任何開放的工具(例如 search_gifts);
  • 呼叫受保護的工具(例如 list_user_orders)時,你的伺服器應回覆 401 Unauthorized

關鍵在於,對於這個 401,伺服器必須加上正確的 WWW-Authenticate。以下是接近 OpenAI 與 MCP Authorization 規範建議的回應範例,包含額外欄位 realmscope

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
  resource_metadata="https://giftgenius.example.com/.well-known/oauth-protected-resource",
  scope="mcp:tools"
Content-Type: application/json

{"error": "unauthorized"}

Jam 看見這樣的回應就會明白:資源受保護,從這裡取得中繼資料(resource_metadata),且預期哪些 scopes。在 None 模式下,它會直接顯示錯誤;但在 Default OAuth 模式下,它會自動依該 resource_metadata 啟動 OAuth 流程。

從除錯角度,在 None 模式你要檢查:

  • 開放工具在無 token 時可正常運作;
  • 受保護工具不可在匿名情況下執行;
  • WWW-Authenticate 是否符合規範(包含 Bearerresource_metadata)。

看似是簡單檢查,但很多問題都起因於回傳 401 時沒有帶 WWW-Authenticate,或其中的參數不正確(例如用過時的 resource_metadata_uri,而非正確的 resource_metadata)。

5. 模式 Bearer Token:快速測試 Resource Server 邏輯

下一步——當你已經有可用的 token(在 Jam 之外取得),想測試的重點是 Resource Server 的邏輯:它是否正確接受/拒絕該 token、是否正確處理 scope 與 audience,並把 sub 與你的服務使用者關聯。

在 MCP Jam 中把模式切換為 Bearer Token,並在 token 欄位貼上例如:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

此後 Jam 會在每個 MCP 請求的標頭中加入:

Authorization: Bearer eyJhbGciOi...

你的 MCP 伺服器收到請求後,會先通過 requireScope("mcp:tools") 這段 middleware,解碼 JWT 並檢查 claims。簡化的驗證程式碼如下:

// auth/verifyToken.ts
import jwt from "jsonwebtoken";

export function verifyToken(header: string) {
  const token = header.replace("Bearer ", "");
  const payload = jwt.verify(token, process.env.JWT_PUBLIC_KEY!);
  // 此處可檢查 aud、scope 等
  return payload as { sub: string; scope?: string };
}

並在 middleware 中使用它:

// 在 requireScope 內部
const payload = verifyToken(header);
if (!payload.scope?.includes(requiredScope)) {
  res.status(403).json({ error: "insufficient_scope" });
  return;
}
(req as any).user = { id: payload.sub };
next();

在 Bearer 模式你可以做的實驗:

  • 貼上缺少必要 scope 的 token,確認伺服器回覆 403/401
  • 貼上錯誤 aud 的 token,確認伺服器會拒絕;
  • 貼上過期 token,檢查是否回報 invalid_token

這是免 UI 登入與 PKCE 的本地「壓力測試」Resource Server 邏輯。你在此驗證的一切,之後可直接套用到由 ChatGPT 或 Jam 在 Default OAuth 模式下取得的 token。

6. 模式 OAuth with credentials(Client Credentials):以「應用身分」取得 token

接著是較不常見、但有助於理解的模式:OAuth with credentials,也就是 client_credentials 授權。在 Jam 中你需要提供:

  • Client ID
  • Client Secret
  • 所需 scopes(例如 mcp:tools

Jam 會向你的 Auth Server 的 token_endpoint 發送如下請求:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=<ID>&
client_secret=<SECRET>&
scope=mcp:tools

Auth Server 會簽發 token,其中 sub 通常代表的是客戶端本身(例如 sub = "mcp-jam-test-client"),而非特定使用者。之後 Jam 便像一般 Bearer 一樣使用這個 token。

在 MCP 世界中,這可能的用途:

  • 與特定使用者無關的服務/管理工具(例如匯出日誌、health‑check、技術支援);
  • 檢查 MCP 伺服器是否能分辨使用者 token 與「客戶端」token,若你的商業邏輯有此需求。

在 ChatGPT Apps 的脈絡下通常不使用此模式,因為 ChatGPT 作為 public client 不會保存密鑰(public client 本就不該有 client_secret)。但在 Jam 中,它能幫助你看清以下差異:

  • 「我只是塞一個現成 token」——Bearer 模式;
  • 「Jam 依據客戶端憑證自己去換 token」——OAuth with credentials。

在教學伺服器上,你可以實作一個特別的 MCP 工具 admin_list_all_orders,僅允許以 grant_type=client_credentials 所得的 token 且具有對應角色的情況下使用。這不是本課必做項,但很值得一試。

7. 模式 Default OAuth:完整 Authorization Code + PKCE,與 ChatGPT 相同

接著是主角:Default OAuth。這個模式最接近 ChatGPT 在綁定你的 App 帳號時所做的事。客戶端會讀取 resource_metadata、前往 Auth Server、為使用者開啟登入頁面,取得 authorization code,並依 Authorization Code + PKCE S256 換取 access token。

我們來看步驟順序。為了更直觀,請看下方時序圖。

sequenceDiagram
    participant Jam as MCP Jam (Client)
    participant RS as MCP Server (Resource)
    participant PRM as /.well-known/oauth-protected-resource
    participant AS as Auth Server (Keycloak/Auth0)
    
    Jam->>RS: 呼叫受保護的工具(無 token)
    RS-->>Jam: 401 + WWW-Authenticate(resource_metadata=PRM)
    Jam->>PRM: GET /.well-known/oauth-protected-resource
    PRM-->>Jam: JSON,包含 resource、authorization_servers、scopes_supported...
    Jam->>AS: GET /authorize?client_id=...&code_challenge=...&scope=...
    Note right of AS: 使用者登入並給予同意
    AS-->>Jam: 帶著 authorization_code 的 redirect
    Jam->>AS: POST /token (code + code_verifier)
    AS-->>Jam: { access_token, scope, expires_in, ... }
    Jam->>RS: 以 Authorization: Bearer <access_token> 呼叫工具
    RS-->>Jam: 工具成功的結果

在此模式你需要確認:

  1. MCP 伺服器正確回覆 401/WWW-Authenticate。 若伺服器未提供 resource_metadata 或 URL 錯誤,Jam 將無法讀取 PRM 也無法啟動 OAuth 流程。
  2. 文件 .well-known/oauth-protected-resource 有效且正確。 必須包含正確的 resourceauthorization_serversscopes_supported 等,使 Jam 能知道去哪裡換 token、要請哪些 scopes。
  3. Auth Server 設定正確。
    • 啟用 Authorization Code Flow 與 PKCE S256。
    • Client ID 與 PRM 的期待一致(或透過 DCR——Dynamic Client Registration 註冊)。
    • Auth Server 中設定的 Redirect URI 與 Jam 使用的完全一致。
  4. PKCE S256。 Jam 生成 code_challenge,並預期 Auth Server 支援 S256 方法。若未啟用 PKCE 或僅支援 plain,流程會失敗。
  5. Scopes 與 audience。 Auth Server 應簽發帶有正確 aud 和請求之 scopes(如 mcp:tools)的 token,而 MCP 伺服器要正確驗證它們。

成功跑完 Default OAuth 後,你會得到:

  • 在 Jam——與 MCP 伺服器的連線,其中受保護工具 list_user_orders 會僅針對你在 Auth Server 登入的那位使用者,回傳正確資料;
  • 在 Auth Server 日誌——成功的 authorize 與 token 交換;
  • 在 MCP 伺服器日誌——成功驗證 token 並取出 sub

為了除錯,你可以在工具處理器中加入簡單的 logger,以確認確實看到了來自 token 的 userId

// 在 MCP 工具 list_user_orders 的處理器內
export async function listUserOrders(args: any, context: any) {
  const user = context.user as { id: string };
  console.log("[MCP] listUserOrders for user", user.id);
  // 接著回傳該使用者的訂單
}

8. 哪裡會壞:依模式診斷

現在我們來討論,如何根據 MCP Jam 的症狀判斷問題所在:MCP 伺服器、Auth Server,或是中繼資料。這一節可視作依模式分類的診斷清單。

若在 None 模式:

你呼叫受保護工具,伺服器回覆:

  • 200 OK 並在沒有 token 時就執行動作——表示該工具前沒有做 token 檢查。 需要加上 middleware 或 scope 檢查。
  • 401,但沒有 WWW-Authenticate,或其中的 resource_metadata 有問題——Jam 將無法知道去哪取得中繼資料,也就無法啟動 Default OAuth。 依上方範例修正標頭。

若在 Bearer Token 模式:

  • 即便使用你確定在直接呼叫(curl 或 Postman)時有效的 token,Jam 仍持續拿到 401/403。 很可能是 Resource Server 邏輯有誤:對 aud/scope 的檢查不對,或 JWT 簽章的公鑰用錯。
  • 如果 Bearer token 在 Jam 可用,但之後在 Default OAuth 不可用——問題可能不在 MCP 伺服器,而在 Auth Server 或 PRM:Default OAuth 取得的 token 在 scope/aud 上與你手動測試的不同。

若在 OAuth with credentials 模式:

  • Jam 無法取得 token(在 /token 步驟出錯)——請檢查 Auth Server 的客戶端設定(secret 錯誤、未允許 client_credentials、或要求的 scope 被禁止)。
  • 取得了 token,但 MCP 伺服器拒絕——可能是伺服器預期的是使用者型的 sub(email/使用者 ID),而 token 裡只有客戶端識別。 或者 aud/scope 與預期不一致。

若在 Default OAuth 模式:

這是最容易踩坑的情境。常見問題:

  • Redirect URI 錯誤。 Auth Server 回報 invalid_redirect_uri 或直接不發 code。 請確認 Jam 的 URI 已正確登錄在 IdP 的客戶端設定中,沒有多餘斜線或拼寫錯誤。
  • 缺少或不支援 PKCE。 若 Auth Server 要求 PKCE,但 Jam(或舊版)未送 code_challenge;或反之——Jam 送 S256,但 IdP 不支援該方法,你會看到 invalid_request
  • Scopes 不一致。 你在 PRM 聲明了 mcp:tools,但 IdP 只允許 openid;或 Jam 請求的 scope 超過 IdP 願意發放的範圍。
  • Audience(aud)不對。 Token 的 aud 與 MCP 伺服器的期待不同(例如指向另一個資源的 URL)。伺服器會理所當然地拒絕。

務必學會看以下三處的日誌:

  • MCP Jam——解析 PRM 與對 Auth Server 的 HTTP 請求錯誤;
  • Auth Server——/authorize/token 的日誌可告訴你它拒絕的原因;
  • MCP 伺服器——拒絕 token 的原因(invalid_tokeninsufficient_scopewrong_audience)。

9. 這與真實的 ChatGPT App 有何關係

為什麼我們花這麼多時間在 Jam 上,而不是直接衝 ChatGPT 的 Developer Mode?因為 Jam 就是實驗平台:它讓你掌控授權模式,並把整個流程的內幕細節攤在你面前。

當你在 Jam 中成功跑通 Default OAuth,等於已經確認:

  • MCP 伺服器的 .well-known/oauth-protected-resource 正確;
  • Auth Server(Keycloak/Auth0/…)設定正確;
  • 角色、scopes、audience 與 claims 都符合預期;
  • MCP 伺服器能驗證 token 並將其綁定到使用者。

ChatGPT 連上同一個 MCP 伺服器時,會做同樣的事:讀取 PRM、前往 Auth Server、取得 token,並開始以 Authorization: Bearer 呼叫工具。

差別在於,在 ChatGPT 你只看到最終結果(「成功連結帳號」或「出了點問題」);而在 Jam——你能看到整個協定,並逐步定位「哪裡不對」。

10. 迷你實作:逐步測試我們的 GiftGenius MCP 伺服器

把重點整理成簡單的順序操作,你可以在自己的專案上照做。

先啟動你的 MCP 伺服器(例如 pnpm dev:mcp),並確認:

  • 它在 http://localhost:4000/mcp(或你的 URL)監聽;
  • 端點 /.well-known/oauth-protected-resource 回傳正確的 JSON;
  • Auth Server(Keycloak)運作正常,且已為 Jam/ChatGPT 設好 public client。

接著:

  1. 模式 None。
    在沒有授權的情況下,將 Jam 連上 MCP 伺服器。確認:
    • search_gifts 可以執行;
    • list_user_orders 會回覆帶有正確 WWW-Authenticate401
  2. 模式 Bearer Token。
    透過 Keycloak(UI 或 curl)取得 access token。貼到 Jam,呼叫 list_user_orders 並確認:
    • 在有效 token 下,工具會執行並回傳該使用者的訂單;
    • 若 token 缺少 mcp:toolsaud 不符——伺服器會回傳錯誤。
  3. 模式 OAuth with credentials。
    若你有機密型客戶端:在 Jam 中填入 client_idclient_secret,設定所需 scope,呼叫技術性工具(例如 admin_list_all_orders),並確認僅在此服務型 token 下可用。
  4. 模式 Default OAuth。
    啟用 Default OAuth,呼叫 list_user_orders。Jam 會自動:
    • 收到 401 + WWW-Authenticate
    • 讀取 PRM,
    • 開啟瀏覽器讓你登入 Keycloak,
    • 以 Authorization Code + PKCE 取得 token,
    • 帶著 token 呼叫 MCP 工具,之後你會在回應中看到自己的訂單。

若四種模式都如預期運作——恭喜!你不僅是「把 Keycloak 勉強弄跑了」,而是實際理解如何檢查與除錯整個授權流程。

11. 使用 MCP Jam 測試授權時的常見錯誤

在實務中,這些問題常以重複出現的錯誤模式呈現。以下是幾個「不該這麼做」的典型情境,方便你一眼辨識。

錯誤 №1:期待受保護工具能在 None 模式運作。
有時開發者把 Jam 設為 None 模式,呼叫 list_user_orders,收到 401 就感到驚訝,然後「以防萬一」把伺服器上的 token 檢查移除。結果 MCP 工具開始在匿名下運作,對個資與交易情境來說完全不可接受。None 模式的目的,是要確認伺服器在沒有 token 時會正確拒絕,並回傳帶有 resource_metadataWWW-Authenticate

錯誤 №2:遺漏或錯誤的 WWW-Authenticate 標頭。
非常常見:伺服器回覆 401 時沒有 WWW-Authenticate,或使用了過時的 resource_metadata_uri 參數。Jam(與 ChatGPT)會因此無法找到 Protected Resource Metadata,Default OAuth 也就無法啟動。最小可行版本是 WWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource"realmscope 可選;重點是別忘了 resource_metadata

錯誤 №3:只測 Bearer 模式,忽略 Default OAuth。
開發者手動拿到 token,貼進 Jam,看起來一切正常,就以為完成了。等到要接上真實 ChatGPT 時,才發現 .well-known 不正確、PKCE 未支援、Redirect URI 不一致,導致 linking 失敗。測 Bearer 模式是必要但不充分的步驟。一定要跑 Default OAuth,否則你無法驗證 Auth Server 與 PRM 的半數關鍵設定。

錯誤 №4:在需要使用者 token 的地方用 client_credentials。
有時在絕望之下,開發者啟用 Jam 的 OAuth with credentials 模式,用 client_credentials 拿到 token,然後拿它去呼叫使用者工具,如 list_user_orders。結果 token 中的 sub 是 client_id,而非真實使用者,業務邏輯就會變得古怪(例如顯示「共用」資料,或嘗試以該 ID 找不到使用者而失敗)。對 ChatGPT 的使用者情境而言,需要的是 Authorization Code + PKCE(Default OAuth);client_credentials 僅適用於服務型任務。

錯誤 №5:PRM、Auth Server 與 MCP 伺服器間的 scopes 與 audience 不一致。
.well-known/oauth-protected-resource 中你宣告資源為 https://giftgenius.example.com,且支援的 scopes 是 ["mcp:tools"]。然而 Auth Server 發給客戶端的 token 沒有 aud,而 MCP 伺服器在驗證時又嚴格要求 aud = "https://giftgenius.example.com" 並包含 mcp:tools。結果透過 Default OAuth 取得的 token 被 MCP 伺服器拒絕,你則為此花半天找「玄學」。務必確保 PRM、IdP 的客戶端設定,以及 MCP 伺服器的 middleware 檢查,三者在 audiencescope 上達成一致。

錯誤 №6:使用舊版 MCP Jam。
MCP Authorization 規範仍在積極演進,會加入新欄位(如 resource_metadata、更完整的 PKCE 流程、輔助除錯工具)。如果你使用舊版 Jam,它可能無法識別新欄位,或仍沿用過時的參數名稱。這會導致光怪陸離的 bug:你一切都依最新 RFC 設好,但 Jam 根本不知道該怎麼處理。在陷入絕望之前,先確認 Jam 已更新至最新版本。

1
問卷/小測驗
身分驗證與存取,等級 10,課堂 4
未開放
身分驗證與存取
身分驗證與存取
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION