13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

mcpサーバーの認可ログを取得してみた

13
Last updated at Posted at 2025-12-16

はじめに

2025年シー・エーアドバンスアドベントカレンダーの記事です!
通信フローは色々なところで紹介があるのですが、実際のログはどのようになっているのか?という事で記事にしてみました。

以下では通信ログを時系列で読み解き、どこで何が起きているかを整理します。

環境と前提

Keycloak + MCP サーバー構成で、MCP Inspector から OAuth 2.1 / OIDC フローを叩いたときの HTTP 通信を記録しました。

  • OS / ローカル: WSL2 Ubuntu 24.04、Node.js v24.0.2(npm 11.6.4)
  • 認可サーバー: Keycloak v26.2 http://localhost:9001(Realm: mcp
  • リソース/MCP サーバー TypeScript v5.0.0: http://localhost:3001
  • クライアント: MCP Inspector v0.17.5 http://localhost:6274
  • 通信ログの取得:Burp Suite v2025.10.6

シーケンス解説(ログ順)

全体の大まかな流れ

  1. ブラウザ(Inspector)が MCP リソースのメタデータを取得(.well-known/oauth-protected-resource)。
  2. Keycloak の OIDC Discovery を正しいパスで取得(/realms/mcp/.well-known/openid-configuration)。
  3. Dynamic Client Registration で一時クライアントを発行(201)。
  4. Authorization Code フロー開始 → Keycloak の同意画面へリダイレクト。
  5. 同意フォーム POST → 認可コード取得。
  6. 認可コード + PKCE の code_verifier でトークンエンドポイントに POST → アクセストークン/IDトークンを受領。

1. リソース(MCP)メタデータ取得(OPTIONS/GET)

  • OPTIONS /.well-known/oauth-protected-resource で CORS を確認(204)。
  • 続く GET /.well-known/oauth-protected-resource でメタデータ取得(200)。

リクエスト例

OPTIONS /.well-known/oauth-protected-resource HTTP/1.1
Host: localhost:3001
Access-Control-Request-Method: GET
Access-Control-Request-Headers: mcp-protocol-version
Origin: http://localhost:6274

レスポンス例

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Headers: mcp-protocol-version

GET 例

GET /.well-known/oauth-protected-resource HTTP/1.1
Host: localhost:3001
MCP-Protocol-Version: 2025-11-25
Origin: http://localhost:6274
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
{
  "resource": "http://localhost:3001/",
  "authorization_servers": ["http://localhost:9001/realms/mcp"],
  "scopes_supported": ["profile"],
  "resource_name": "Test MCP Server"
}

2. 認可サーバーのメタデータ取得

  • ここで取得しているメタデータ(OIDC Discovery)は、認可サーバーが公開する「設定カタログ」です。issuerauthorization_endpointtoken_endpointregistration_endpoint など、クライアントが動的に接続先を知るための一覧が含まれます。
  • 動的取得が必要な理由: 環境ごとにホスト名/パス(例: /realms/mcp)やプロキシ構成が変わるため、クライアント側でハードコードせず「どこに認可/トークン/登録を投げるか」を毎回 Discovery から解決します。
  • GET /realms/mcp/.well-known/openid-configuration → 200
    → OIDC Discovery を取得し、authorization_endpointregistration_endpoint などを解決。
GET /realms/mcp/.well-known/openid-configuration HTTP/1.1
Host: localhost:9001
Origin: http://localhost:6274
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Access-Control-Allow-Origin: http://localhost:6274
{
  "issuer": "http://localhost:9001/realms/mcp",
  "authorization_endpoint": ".../protocol/openid-connect/auth",
  "token_endpoint": ".../protocol/openid-connect/token",
  "registration_endpoint": ".../clients-registrations/openid-connect",
  ...
}

3. MCP サーバーが返す認可サーバーメタデータ

MCP サーバー自身の /.well-known/oauth-authorization-server も取得され、ここで Keycloak のエンドポイントが示される(issuer は Keycloak)。

GET /.well-known/oauth-authorization-server HTTP/1.1
Host: localhost:3001
Origin: http://localhost:6274
HTTP/1.1 200 OK
{
  "issuer": "http://localhost:9001/realms/mcp",
  "authorization_endpoint": "http://localhost:9001/protocol/openid-connect/auth",
  "token_endpoint": "http://localhost:9001/protocol/openid-connect/token",
  "registration_endpoint": "http://localhost:9001/clients-registrations/openid-connect",
  "response_types_supported": ["code"],
  "code_challenge_methods_supported": ["S256"],
  ...
}

4. Dynamic Client Registration(DCR)

  • POST /realms/mcp/clients-registrations/openid-connect → 201 Created
    • Body: redirect_uris=["http://localhost:6274/oauth/callback/debug"]
    • token_endpoint_auth_method: none(パブリッククライアント)
    • 応答に client_idregistration_access_token が付与される。

補足: DCR で何をしているか

  • 事前に Keycloak へクライアント定義を用意せず、その場で一時的なクライアント(client_id)を発行する。
  • token_endpoint_auth_method: none によりパブリッククライアントとして登録し、PKCE(code_challenge/code_verifier)で保護する前提。
  • 応答に含まれる registration_access_token で、このクライアント設定を後から更新/削除できる。
  • ここで発行された client_id を後続の認可リクエスト(/auth)やトークンエンドポイントで使用する。

ポイント:

  • Nginx で CORS ヘッダを付け、Access-Control-Allow-Origin: http://localhost:6274 が返っているのがポイント。
  • はまりポイント(MCP Inspector × Keycloak): Keycloak の DCR レスポンスはデフォルトで CORS が付かない。ブラウザ(Inspector)はクロスオリジンの POST /clients-registrations/openid-connect を拒否するため、間に置く Nginx などのプロキシで Access-Control-Allow-Origin: http://localhost:6274 を明示的に付与する必要がある。Nginx を噛ませる理由は「CORS を後付けするための中継役」と捉えると分かりやすい。

注釈:

  • パブリッククライアントとは: クライアントシークレットを安全に保持できない(ブラウザ/モバイル等)前提のクライアント。client_secret を使わず、PKCE やリダイレクトURI検証で保護する。

リクエスト

POST /realms/mcp/clients-registrations/openid-connect HTTP/1.1
Host: localhost:9001
Content-Type: application/json
Origin: http://localhost:6274

{
  "redirect_uris": ["http://localhost:6274/oauth/callback/debug"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code","refresh_token"],
  "response_types": ["code"],
  "client_name": "MCP Inspector",
  "client_uri": "https://github.com/modelcontextprotocol/inspector",
  "scope": "profile"
}

レスポンス

HTTP/1.1 201 Created
Location: http://localhost:9001/realms/mcp/clients-registrations/openid-connect/<client-id>
Access-Control-Allow-Origin: http://localhost:6274
{
  "client_id": "<client-id>",
  "registration_access_token": "<registration-access-token>",
  "redirect_uris": ["http://localhost:6274/oauth/callback/debug"],
  "response_types": ["code","none"],
  "post_logout_redirect_uris": ["http://localhost:6274/oauth/callback/debug"],
  ...
}

5. 認可リクエスト(Authorization Code Flow)

  • GET /realms/mcp/protocol/openid-connect/auth?... → 302 / 200
    • パラメータ: response_type=code, client_id=<DCRで発行>, code_challenge, redirect_uri=http://localhost:6274/oauth/callback/debug, scope=profile, resource=http://localhost:3001/
    • 302 で login-actions/required-action にリダイレクトされ、同意画面(Grant Access to MCP Inspector)が 200 で表示される。
    • Cookie (AUTH_SESSION_ID, KC_AUTH_SESSION_HASH 等) がセットされ、以後のログイン/同意フローに使われる。

補足: 認可リクエストで何をしているか

  • ブラウザから認可サーバー(Keycloak)の /auth にリダイレクトし、Authorization Code Flow を開始する。
  • code_challenge/code_challenge_method=S256 で PKCE を利用し、後続のトークン交換時に code_verifier を突き合わせる。
  • resource=http://localhost:3001/ でアクセスしたいリソース(MCP サーバー)を指定し、発行されるトークンを紐付ける。
  • scope=profile で要求するスコープを指定。同意画面に反映される。
  • ここではまだトークンは発行されず、同意画面を経て認可コードを受け取るためのステップ。

リクエスト

GET /realms/mcp/protocol/openid-connect/auth
  ?response_type=code
  &client_id=<client-id>
  &code_challenge=<code-challenge>
  &code_challenge_method=S256
  &redirect_uri=http%3A%2F%2Flocalhost%3A6274%2Foauth%2Fcallback%2Fdebug
  &state=<state>
  &scope=profile
  &resource=http%3A%2F%2Flocalhost%3A3001%2F HTTP/1.1
Host: localhost:9001
Origin: http://localhost:6274
Cookie: AUTH_SESSION_ID=<cookie>; KC_AUTH_SESSION_HASH=<hash>; ...

302 レスポンス(抜粋)

HTTP/1.1 302 Found
Set-Cookie: AUTH_SESSION_ID=<cookie>; ...
Location: http://localhost:9001/realms/mcp/login-actions/required-action?... 
Access-Control-Allow-Credentials: true

同意画面取得

GET /realms/mcp/login-actions/required-action
  ?execution=OAUTH_GRANT
  &client_id=<client-id>
  &tab_id=<tab-id>
  &client_data=<client-data> HTTP/1.1
Host: localhost:9001
Cookie: AUTH_SESSION_ID=<cookie>; KC_AUTH_SESSION_HASH=<hash>; ...
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8

6. 同意フォーム送信(accept/cancel)

同意画面のフォームは login-actions/consent に POST され、hidden code と各種クエリを含みます。

POST /realms/mcp/login-actions/consent
  ?client_id=<client-id>
  &tab_id=<tab-id>
  &client_data=<client-data> HTTP/1.1
Host: localhost:9001
Content-Type: application/x-www-form-urlencoded
Cookie: AUTH_SESSION_ID=<cookie>; KC_AUTH_SESSION_HASH=<hash>; ...

code=<authorization-code-from-form>
accept=Yes  # 「Yes」ボタンを押した場合。キャンセルなら cancel=No

レスポンス(ログではフォーム送信後のリダイレクト/トークン交換は未取得)

HTTP/1.1 302 Found
Location: <token 取得や redirect_uri への遷移先>
Set-Cookie: AUTH_SESSION_ID=<cookie>; ...

7. トークンエンドポイントでの認証情報取得(Authorization Code → Token)

同意後、Inspector がコードを用いてトークンを取得します(PKCE 使用)。client_secret は使わず code_verifier を送信。

POST /realms/mcp/protocol/openid-connect/token HTTP/1.1
Host: localhost:9001
Content-Type: application/x-www-form-urlencoded
Origin: http://localhost:6274

grant_type=authorization_code
&code=<authorization-code>
&code_verifier=<code-verifier>
&redirect_uri=http%3A%2F%2Flocalhost%3A6274%2Foauth%2Fcallback%2Fdebug
&resource=http%3A%2F%2Flocalhost%3A3001%2F
&client_id=<client-id>

レスポンス(アクセストークン/IDトークン等はマスク)

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
  "access_token": "<access-token>",
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": "<refresh-token>",
  "token_type": "Bearer",
  "id_token": "<id-token>",
  "session_state": "<session-state>",
  "scope": "profile"
}

ここから読み取れること・ハマりポイント

今回の環境/構成に限った話ではあるのですが、

  • メタデータのパス: Keycloak の OIDC Discovery は /realms/<realm>/.well-known/openid-configuration。プレフィックスを間違えると 404 を踏む。
  • DCR の成功条件: Trusted Hosts 設定が通っているので 201 が返っている。もし 403 (insufficient_scope / Host not trusted) が出る場合は、Trusted Hosts のホスト/IP/ポートを再確認する。
  • リソース指定: 認可リクエストで resource=http://localhost:3001/ を送っており、MCP リソースへのアクセスをトークンに紐付ける動きが見える。

まとめ

httplogs_noAuth.log の時系列を見ると、

  1. MCP 側メタデータ取得 →
  2. Keycloak の OIDC Discovery →
  3. DCR で一時的なクライアントを発行 →
  4. Authorization Code Flow に入り、同意画面まで到達
    という流れが確認できました。エラーは「誤った Discovery パス」と「CORS/Trusted Hosts 周り」が主な落とし穴で、正しいパスとヘッダを揃えれば 201/302/200 のハッピーケースに乗ることがわかります。
13
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?