CodeGym /コース /ChatGPT Apps /MCP Jam を使ったログインとアクセスのテスト: None, Bearer, OAuth with crede...

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 フローを実行し、トークンをリクエストに付与し、何がうまくいかなかったのかを可視化してくれます。

実務的にとても重要なポイントです。もし MCP Jam で Default OAuth フローを成功させられたなら、実際の ChatGPT App との統合準備はおよそ 80% 完了したと言えます。アカウント連携時に ChatGPT が行うことは(リンク操作も含めて)Jam ですでに再現可能で、しかもログや操作がより透明です。

前回の講義では、学習用 MCP サーバー GiftGenius に基本的な認可を設定しました。トークン検証方式(JWT か introspection)を選び、.well-known/oauth-protected-resource を実装し、ツールを保護するミドルウェアを用意しました。今回は、これらが MCP Jam のさまざまな認可モードでどう振る舞うかを見ていきます。

この講義の目標:

  • Jam の認可モード(NoneBearerOAuth with credentialsDefault OAuth)を意図通りに切り替えられるようにする;
  • 各モードで Jam が MCP サーバーに何を送るかを理解する;
  • どの部分が壊れているのか(MCP Server・Auth Server・メタデータ)を診断できるようにする;
  • 保護されたツールはトークン必須で動き、公開ツールはトークンなしでも動くことを確認する。

2. 学習用 MCP サーバー: 何をテストするのか

具体的に話すため、コンテキストを簡単に振り返ります。学習用アプリ GiftGenius の話を続けます。これは ChatGPT App で、ギフト選びを支援し、ユーザーの注文やウィッシュリストを表示します。

MCP サーバー側では既に以下があります:

  • 公開ツール(例: search_gifts)— 匿名で呼び出し可能;
  • 保護されたツール(例: list_user_orders)— 認証済みユーザーのみが利用でき、mcp:tools スコープを要求。

サーバーができること:

  • .well-known/oauth-protected-resource を公開する;
  • トークンを検証する(JWT または introspection — 前回の講義でどちらかを選択済み);
  • トークンから sub(user id)、scopeaud を取り出し、ツールのハンドラーに渡す。

Node.js/TypeScript における典型的なトークン検証ミドルウェアは次のようになります:

// 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;
    }

    // ここでトークン(署名、exp、aud、scope など)を検証し、
    // 結果を req.user に格納します
    next();
  };
}

このミドルウェアは MCP の保護ツールの前で使用します。トークンがなければ 401 と、仕様で求められている WWW-Authenticateresource_metadata を含む)を返します。トークン検証の詳細や補助関数の実装は前回の講義で扱いました。ここでは既知の前提として利用します。

3. MCP Jam の認可モード: 概観

MCP Jam には MCP サーバーに接続するための複数の認可モードがあります。これらは典型的な OAuth パターンに対応しており、トークンなしから本格的な Authorization Code + PKCE までカバーします。

概要:

  1. None (No Auth) — Jam は Authorization ヘッダーを一切追加しません。匿名アクセスです。公開 MCP サーバーの利用や、保護リソースが 401WWW-Authenticate で正しく拒否するかの確認に適しています。
  2. Bearer Token — Jam は Authorization: Bearer <トークン> を追加します。トークンは UI で手動入力します。すでに取得済みのトークン(curl や Keycloak UI など)で MCP リソースの挙動を手早く検証するのに適しています。
  3. OAuth with credentials (Client Credentials) — Jam が Auth Server に対して指定の Client ID/Secret を使い、client_credentials でトークンを自動取得します。ユーザー不在のサーバー間認可に近い「機密クライアント」モードです。
  4. Default OAuth (Authorization Code + PKCE) — ChatGPT のようなクライアント(secret を持たない public client)の主力モード。Jam は resource_metadata を読み、Auth Server を見つけ、/authorize にブラウザで遷移し、PKCE フローでユーザートークンを取得します。

分かりやすく表にまとめます。

Jam のモード Jam が送るもの トークン取得者 典型的シナリオ
None Authorization なし なし 匿名ツール、401 の確認
Bearer Token Bearer <手動> あなた(curl, UI IdP) 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)を取得するか、および期待スコープが何かを理解します。None モードではエラー表示のみですが、Default OAuth モードでは、ここで指定された resource_metadata を自動的に参照して OAuth フローを起動します。

デバッグ観点で、None モードでは次を確認します:

  • 公開ツールはトークンなしで動作する;
  • 保護ツールは匿名では絶対に実行されない;
  • WWW-Authenticate ヘッダーが仕様通り(Bearerresource_metadata を含む)。

一見単純ですが、多くの問題の発端は、401WWW-Authenticate が付いていなかったり、パラメータが不正(例えば、現在は非推奨の resource_metadata_uri を使い、最新の resource_metadata を使っていない)だったりすることです。

5. モード Bearer Token: Resource Server ロジックのクイックテスト

次は、すでに有効なトークン(Jam 外部で取得)を持ち、Resource Server ロジックそのものを確認したい場合のモードです。トークンの受け入れ/拒否、scope と audience の扱い、sub と自サービスのユーザーの紐づけが正しいかを検証します。

MCP Jam を Bearer Token に切り替え、トークン欄に例えば次のように貼り付けます:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

以降、Jam はすべての MCP リクエストにこのヘッダーを付与します:

Authorization: Bearer eyJhbGciOi...

MCP サーバーはリクエストを受け取り、ミドルウェア requireScope("mcp:tools") を通し、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 };
}

これをミドルウェアで利用します:

// 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 がないトークンを入れて、サーバーが 403/401 を返すことを確認;
  • aud が不正なトークンを入れて、サーバーが拒否することを確認;
  • 有効期限切れのトークンを入れて、invalid_token エラーを確認。

これは UI ログインや PKCE を伴わない、Resource Server ロジックのローカルな負荷テストのようなものです。ここで検証することは、そのまま Default OAuth で ChatGPT や Jam が取得するトークンにも適用されます。

6. モード OAuth with credentials(Client Credentials): 「アプリとして」のトークン

次は頻度は低いものの、理解に役立つモードです。OAuth with credentials、つまり client_credentials グラントです。Jam には以下を設定します:

  • Client ID
  • Client Secret
  • 必要なスコープ(例: 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 はトークンを返します。このとき sub は通常、特定ユーザーではなくクライアント自身(例: sub = "mcp-jam-test-client")を表します。Jam は以後、このトークンを Bearer として使用します。

MCP の世界で何に有用か:

  • 特定ユーザーに紐づかないサービス/管理ツール(例: ログ出力、ヘルスチェック、サポート);
  • ビジネスロジック上、ユーザートークンと「クライアント」トークンを区別する必要がある場合の検証。

ChatGPT Apps の文脈ではこのモードは通常使いません。ChatGPT はパブリッククライアントでシークレットを保持せず(public client には client_secret がそもそもありません)。しかし Jam では次の違いを把握するのに便利です:

  • 「準備済みのトークンを差し込んだだけ」(Bearer モード);
  • 「Jam がクライアント資格情報で自らトークンを取りに行った」(OAuth with credentials)。

学習用サーバーでは、例えば特別な MCP ツール admin_list_all_orders を用意し、grant_type=client_credentials のトークンかつ所定ロールでのみ利用可能にする、といった実験も可能です。今日の必須課題ではありませんが、有益な試みです。

7. モード Default OAuth: ChatGPT 同等の Authorization Code + PKCE

いよいよ主役の Default OAuth です。これは ChatGPT があなたの App のアカウント連携で行う動作に最も近いモードです。クライアントは resource_metadata を読み、Auth Server に進み、ログイン画面を開き、authorization code を取得し、Authorization Code + PKCE S256 で access token に交換します。

手順を分解します。理解しやすいよう、次のシーケンス図を参照してください。

sequenceDiagram
    participant Jam as MCP Jam (クライアント)
    participant RS as MCP Server (リソース)
    participant PRM as /.well-known/oauth-protected-resource
    participant AS as Auth Server (Keycloak/Auth0)
    
    Jam->>RS: 保護ツールの呼び出し(トークンなし)
    RS-->>Jam: 401 + WWW-Authenticate(resource_metadata=PRM)
    Jam->>PRM: GET /.well-known/oauth-protected-resource
    PRM-->>Jam: resource、authorization_servers、scopes_supported などの JSON
    Jam->>AS: GET /authorize?client_id=...&code_challenge=...&scope=...
    Note right of AS: ユーザーがログインして同意
    AS-->>Jam: authorization_code でリダイレクト
    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 がトークン取得先や要求すべきスコープを理解できること。
  3. Auth Server の正しい設定。
    • Authorization Code Flow(PKCE S256)を有効化。
    • Client ID が PRM の想定(または DCR による登録)と一致。
    • Auth Server 上の Redirect URI が Jam の使用するものと完全一致。
  4. PKCE S256。 Jam は code_challenge を生成し、Auth Server が S256 をサポートしていることを前提にします。PKCE が無効、または plain のみ対応だとフローは失敗します。
  5. Scopes と audience。 Auth Server は必要な aud と要求スコープ(mcp:tools など)を持つトークンを発行し、MCP サーバーはそれを検証する必要があります。

Default OAuth に成功すると、次が得られます:

  • Jam では MCP サーバーと接続され、保護ツール list_user_orders が、Auth Server にログインしたユーザー本人のデータを正しく返す;
  • Auth Server のログには authorize と token 交換の成功が記録される;
  • MCP サーバーのログにはトークン検証成功と sub の抽出が記録される。

デバッグには、ツールハンドラーに簡単なロガーを追加し、トークンから取得した 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 で、トークンなしでも実行されてしまう — そのツールの前にトークン検証がありません。 ミドルウェアや scope チェックを追加しましょう。
  • 401 だが WWW-Authenticate がない、または resource_metadata が不正 — Jam はどこからメタデータを取るべきか分からず、Default OAuth を開始できません。 上記の例を参考にヘッダーを修正してください。

Bearer Token モードの場合:

  • curl や Postman では有効だと確信できるトークンでも、Jam では常に 401/403 になる。 おそらく Resource Server のロジックに問題があります。aud/scope の検証誤り、または JWT 検証に用いる公開鍵の取り違えなどです。
  • Jam の Bearer トークンは動くのに、Default OAuth では動かない — 問題は MCP サーバーではなく、Auth Server または PRM にある可能性が高いです。Default OAuth で得たトークンの scope/aud が、手動テストで使ったトークンと異なっているはずです。

OAuth with credentials モードの場合:

  • Jam がトークンを取得できない(/token でエラー) — Auth Server 上のクライアント設定を見直してください。secret の誤り、client_credentials 非許可、scope 禁止などが原因です。
  • トークンはあるが MCP サーバーが拒否する — サーバーがユーザーの sub(メール/ユーザー ID)を期待しているのに、トークンにはクライアント ID しかないのかもしれません。 あるいは aud/scope が期待と合っていません。

Default OAuth モードの場合:

最も落とし穴が多いシナリオです。よくある問題:

  • 不正な Redirect URI。 Auth Server が invalid_redirect_uri を返したり、コードを発行しなかったりします。 Jam の URI を IdP のクライアント設定に漏れなく登録し、余計なスラッシュやタイプミスがないか確認してください。
  • PKCE の欠如または非対応。 Auth Server が PKCE を要求しているのに Jam(古い版など)が code_challenge を送らない、あるいは Jam は S256 を送るのに IdP が未対応だと、invalid_request になります。
  • スコープの不一致。 PRM では mcp:tools を宣言しているのに、IdP 側のクライアントには openid しか許可されていない、または Jam が IdP の許容以上のスコープを要求している、など。
  • audience(aud)が不適切。 トークンに付与された aud が MCP サーバーの期待値(例えば別のリソース URL)と異なるため、サーバーが正当に拒否するケースです。

次の三箇所のログを見られるようにしておくのが非常に重要です:

  • MCP Jam — PRM 解析や Auth Server への HTTP リクエスト時のエラー;
  • Auth Server — /authorize/token のログ(拒否理由の手掛かりになります);
  • MCP サーバー — トークン拒否の理由(invalid_tokeninsufficient_scopewrong_audience)。

9. これが実際の ChatGPT App とどう関係するか

なぜすぐに ChatGPT の Developer Mode に行かず、Jam での検証に時間を使うのでしょうか。Jam はまさに実験用スタンドであり、認可モードを手元で切り替え、フローの内部を丸ごと可視化できるからです。

Jam で Default OAuth を成功させられれば、次の点が実質的に確認できます:

  • MCP サーバーの .well-known/oauth-protected-resource が正しい;
  • Auth Server(Keycloak/Auth0/…)の設定が適切;
  • ロール・スコープ・audience・claims が期待通りにそろっている;
  • MCP サーバーがトークンを検証し、ユーザーに紐づけられる。

同じ MCP サーバーに接続した ChatGPT も、同じことを行います。PRM を読み、Auth Server に行き、トークンを取得し、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_orders401 を返し、WWW-Authenticate が正しい。
  2. Bearer Token モード。
    Keycloak から access token を取得(UI または curl)。Jam に貼り付け、list_user_orders を呼び、次を確認:
    • 有効なトークンでツールが実行され、当該ユーザーの注文が返る;
    • mcp:tools のないトークンや異なる aud の場合、サーバーがエラーを返す。
  3. OAuth with credentials モード。
    機密クライアントがあるなら、Jam に client_idclient_secret を設定し、必要スコープを指定。テクニカルツール(例: admin_list_all_orders)を呼び、サービス用トークンでのみ動作することを確認。
  4. Default OAuth モード。
    Default OAuth を有効にして list_user_orders を呼びます。Jam は自動的に:
    • 401 + WWW-Authenticate を受け取り、
    • PRM を読み、
    • ブラウザを開いてあなたが Keycloak でログインし、
    • Authorization Code + PKCE でトークンを取得し、
    • トークン付きで MCP ツールを呼び、レスポンスにあなたの注文が表示される。

この 4 モードが期待通りに動作したなら、単に「Keycloak をなんとなく設定できた」のではなく、認可フロー全体を検証・デバッグできるようになったということです。おめでとうございます。

11. MCP Jam と認可テストでありがちなミス

実務では、次のようなパターンで問題が繰り返し発生します。症状から早期に気づけるよう、典型的な「やってはいけない」シナリオを挙げます。

よくあるミス1: None モードで保護ツールが動くと期待する。
開発者が Jam を None にして list_user_orders を呼び、401 に驚き、ついサーバー側のトークン検証を「念のため」外してしまうことがあります。結果として MCP ツールが匿名で動作し、個人データやコマース系シナリオでは致命的です。None モードは、トークンなしで正しく拒否し、resource_metadata を含む WWW-Authenticate を返すことを確認するためのものです。

よくあるミス2: WWW-Authenticate ヘッダーの付け忘れ・不正。
非常に多いのが、401WWW-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 を無視する。
手でトークンを取得して Jam に貼り、動いたから完了と考えるパターンです。いざ本番で ChatGPT をつなぐと、.well-known が不正、PKCE 非対応、Redirect URI 不一致で、連携が失敗します。Bearer テストは必要ですが十分ではありません。Default OAuth を必ず流し、Auth Server と PRM の重要設定の半分を見落とさないようにしましょう。

よくあるミス4: ユーザー用の場面で client_credentials を使おうとする。
行き詰まって、Jam を OAuth with credentials にして client_credentials でトークンを取得し、それを list_user_orders のようなユーザー向けツールに使ってしまうことがあります。結果としてトークンの sub は client_id であって実ユーザーではないため、ビジネスロジックが不正に(共通データを見せる、該当ユーザーが見つからず落ちるなど)なります。実ユーザーを扱う ChatGPT シナリオでは Authorization Code + PKCE(Default OAuth)が必要で、client_credentials はサービス用途に限ります。

よくあるミス5: PRM・Auth Server・MCP サーバー間での scope と audience の不整合。
.well-known/oauth-protected-resource でリソースを https://giftgenius.example.com、対応スコープを ["mcp:tools"] と宣言しているのに、Auth Server は aud なしでトークンを発行し、MCP サーバーは厳密に aud = "https://giftgenius.example.com"mcp:tools の存在を期待している——この結果、Default OAuth で取得したトークンを MCP サーバーが拒否し、「魔法探し」に半日かかる、という事態になります。PRM・IdP のクライアント設定・MCP サーバーのミドルウェア検証が、audiencescope で一致しているか常に確認しましょう。

よくあるミス6: 古い MCP Jam の使用。
MCP Authorization の仕様は進化中で、resource_metadata や強化された PKCE フロー、補助デバッガなど新項目が追加されています。古い Jam では最新項目を理解できず、古いパラメータ名で動くことがあり得ます。結果として、最新の仕様通りに設定しているのに Jam が対応できず奇妙なバグが出ます。行き詰まる前に、Jam が最新バージョンか確認しましょう。

1
アンケート/クイズ
認証とアクセス、レベル 10、レッスン 4
使用不可
認証とアクセス
認証とアクセス
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION