MCPのOAuth Client Credentials拡張とは — 人間不在の認証
ブラウザリダイレクトなしでMCPサーバーに接続するOAuth Client Credentials拡張の仕組みと、JWT Bearer Assertionとクライアントシークレットの使い分け。
CI/CDパイプラインやバックグラウンドサービスのように、ブラウザで人間がログインできない場面でMCPサーバーに接続するための拡張がOAuth Client Credentialsです。拡張識別子はio.modelcontextprotocol/oauth-client-credentials。OAuth 2.0のclient credentialsフロー(RFC 6749セクション4.4)をMCPに追加し、アプリケーション自身の資格情報だけでアクセストークンを取得できるようにします。
Extension Support Matrixに載る14クライアントのうち、この拡張に✓が付くのは1つだけです。
OAuth Client Credentialsに✓が付くクライアント
✓あり
1
mcpc MCP CLI
✓なし
13
Claude(web)・Claude Desktop・VS Code GitHub Copilot・Cursor・ChatGPTなど
client credentialsフローとは何か
client credentialsフローは、ユーザーに代わってではなく、アプリケーション自身の名義でアクセストークンを得るOAuth 2.0のグラントです。クライアントが認可サーバーの/tokenへ自分の資格情報を送ると、アクセストークンが返ります。ログイン画面も認可コードもありません。
MCPのOAuthは通常、ユーザーが認可画面で承認する認可コードフローです。client credentialsはその対極にあり、トークンが表す権限は「ユーザーの権限」ではなく「クライアント自身に事前登録された権限」になります。
標準のMCP認可フローが機械間通信に向かない理由
標準のMCP認可フローは、ブラウザが開いてユーザーがログインし、mix-up攻撃対策などのセキュリティ要件を満たしながら権限を承認するという手順を前提にしています。人間が操作する対話的な利用には向いていますが、ユーザーがいない場面では成立しません。
この拡張はユーザーの代わりに、アプリケーション自身の資格情報で認証します。使えるのは、クライアントIDとシークレット、または署名付きJWTアサーションです。クライアントは認可サーバーに自分の身元を直接証明し、ブラウザのリダイレクトもユーザー操作も介さずにアクセストークンを受け取ります。
使う場面と使わない場面
想定されるユースケースは4つです。
- バックグラウンドサービスが、ユーザー不在のままスケジュールやイベントに応じてMCPツールを呼び出す
- CI/CDパイプラインが、自動化されたビルド・テスト・デプロイの一部としてMCPサーバーを呼び出す
- サーバー間連携で、エンドユーザーが介在しない2つのバックエンドシステムを接続する
- デーモンプロセスや常駐ワーカーが、MCPリソースへの永続的なアクセスを必要とする
ユーザー本人が明示的に承認すべき統合では、標準のMCP認可フローを使います。人間が関与するかどうかが、この拡張を使うかどうかの分かれ目です。
この拡張は、人間の社員のアクセスを企業のIdPで一元管理する用途には向きません。そちらはEnterprise-Managed Authorization拡張の担当で、詳しくはEnterprise-Managed Authorizationの解説にあります。
接続が確立するまでの流れ
仕様(ext-authリポジトリのspecification/draft)が定める流れは、トークンを持たないリクエストから始まります。
トークン取得までの5段階
- 1
トークンなしでMCPサーバーにリクエストする
MCPサーバーは
401 Unauthorizedを返し、WWW-Authenticateヘッダーにresource_metadataのURLを載せます。 - 2
Protected Resource Metadataを読む
メタデータから、使う認可サーバーを選びます。
- 3
認可サーバーのメタデータを取得する
OAuth 2.0とOpenID Connectのディスカバリーエンドポイントを優先順に試します。
- 4
`/token`にクライアント認証を送る
JWT認証(推奨)かクライアントシークレットのどちらかで認証します。
- 5
アクセストークンでMCPリクエストを送る
以降のMCP通信は、このトークンを付けて続きます。
この流れでは、Dynamic Client Registration(DCR)を使いません。クライアント資格情報は、管理者の窓口などで事前に登録しておく前提です。認可サーバー側のメタデータには、token_endpoint_auth_methods_supportedにprivate_key_jwtかclient_secret_basicの少なくとも一方が必要で、JWT認証を受け付けるならtoken_endpoint_auth_signing_alg_values_supportedも要ります。
JWTアサーションとクライアントシークレットの違い
仕様は、クライアントにこの2つのうちどちらかでの認証を求めます。JWT認証(RFC 7523セクション2.2)は推奨、クライアントシークレットはOAuth 2.1のセクション2.4.1に従う方式です。
トークン要求に載る値の違い
JWT認証
grant_type=client_credentialsに加えて、client_assertion_typeとclient_assertion(署名済みJWT)を送ります。client_idは本文に入れません。RFC 7523セクション3に従い、識別はJWTのsubクレームが担います。
クライアントシークレット
grant_type=client_credentialsに加えて、client_idとclient_secretを送ります。認可サーバーに事前登録した値をそのまま使うため構成は単純ですが、資格情報そのものがネットワークを通ります。
仕様の定義文と例は本文で送る形です。一方、メタデータ要件に挙がるのはHTTP Basic(client_secret_basic)で、mcpcもこちらを使います。
どちらもresourceとscopeを付けて要求します。仕様の例ではresource=https://mcp.example.com、scope=mcp:readです。
公式ページと仕様で食い違うJWTのgrant_type
JWTの場合のgrant_typeは、docsページと仕様で書き方が違います。docsページのシーケンス図はgrant_type=urn:ietf:params:oauth:grant-type:jwt-bearerとassertion=<signed JWT>を送る形です。仕様の例は上の比較にあるclient_credentialsにclient_assertionを足した形です。
docsページの図はRFC 7523セクション2.1(JWTを認可グラントとして使う形)、仕様の例はセクション2.2(private_key_jwtでクライアントを認証する形)にあたります。
アサーションの主なクレームは次のとおりです。
| クレーム | 内容 |
|---|---|
iss | 内容クライアントID(発行者) |
sub | 内容クライアントID(認証対象) |
aud | 内容認可サーバーのトークンエンドポイントURL |
exp | 内容有効期限 |
iat | 内容発行時刻 |
クライアントシークレットの扱い
クライアントシークレットは、ユーザー操作なしにアクセスを許可する長期間有効な資格情報です。漏洩すると、ローテーションされるまで攻撃者がアプリケーションになりすませます。ドキュメントは次の対策を挙げています。
- シークレットマネージャーに保管し、ソースコードやバージョン管理下の環境ファイルに書かない
- 定期的にローテーションし、漏洩が疑われたら即座にローテーションする
- 資格情報のスコープを必要最小限に絞る
- 可能ならJWTアサーションを優先する(短命で、署名鍵自体を送信しない)
クライアントとサーバーが実装すること
MCPクライアント側は、まずリクエストの_meta内で拡張への対応を宣言します。
{
"jsonrpc": "2.0",
"id": 1,
"method": "...",
"params": {
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/oauth-client-credentials": {}
}
}
}
}
}トークンは接続前にclient credentialsグラントで取得し、Authorization: Bearer <access_token>として付けます。client credentialsトークンは通常、ユーザー委任トークンより有効期限が短いので、期限前に取り直す処理も必要です。
MCPサーバー側は、リクエストごとにJWKSエンドポイント経由でJWT署名とクレームを検証し、操作に必要なスコープがトークンにあるかを確認します。server/discoverレスポンスのcapabilities.extensionsにこの拡張を含めておくと、発見しやすくなります(任意ですが推奨)。
SDKでの実装
TypeScript SDKとPython SDKは、client credentials認証の組み込みサポートを持ち、トークンの取得と更新を自動で行います。導入はnpm install @modelcontextprotocol/client、pip install mcpです。
npm install @modelcontextprotocol/clientTypeScriptでは、クライアントシークレットならClientCredentialsProviderにclientIdとclientSecretを渡します。JWT秘密鍵ならPrivateKeyJwtProviderにclientId・privateKey・algorithm(例はRS256)を渡します。どちらもStreamableHTTPClientTransportのauthProviderに設定します。
PythonではClientCredentialsOAuthProviderとPrivateKeyJWTOAuthProviderが対応します。JWT側はSignedJWTParametersでissuer・subject・署名鍵・signing_algorithmを指定し、サンプルのアサーション有効期間はlifetime_seconds=300(5分)です。要求するスコープはscopes="read write"のようにスペース区切りの文字列で渡します。
Pythonのサンプルには、トークンの保存先としてInMemoryTokenStorageが登場します。プロセスのメモリ上に保持するだけの実装なので、再起動をまたいでトークンを使い回したいデーモンでは、ストレージのインターフェース(get_tokens・set_tokens・get_client_info・set_client_info)を自前の永続ストアで実装することになります。
mcpcで試す
Extension Support Matrixで唯一✓が付くmcpc(@apify/mcpc)は、コマンドラインからこの拡張を使えます。mcpc login --helpの出力のうち、機械間認証の部分は次のとおりです(mcpc 0.7.0)。
npx @apify/mcpc@0.7.0 login --helpMachine-to-machine authentication (for CI/CD and daemons):
Pass --grant client-credentials, --client-id, and one credential:
mcpc login mcp.example.com --grant client-credentials \
--client-id my-svc --client-secret s3cr3t --scope "read write"
mcpc login mcp.example.com --grant client-credentials \
--client-id my-svc --client-key ./key.pem
--client-secret uses client_secret_basic; --client-key signs a private_key_jwt
assertion (RFC 7523). The token endpoint is auto-discovered; pin it with
--token-endpoint <url> for servers without discoverable metadata.シークレット認証はclient_secret_basic、鍵ファイルを渡すとprivate_key_jwtで署名します。署名アルゴリズムの既定はRS256(--client-key-alg)です。認可サーバーがメタデータを公開していない場合は、--token-endpointでトークンエンドポイントを指定します。READMEによれば、トークンの取得と更新は自動で、mcpcはこの拡張とEnterprise-Managed Authorizationの両方を、接続する全サーバーへ宣言します。
mcpc 0.7.0のパッケージはNode.js 22.12.0以上を要求します(npmのenginesフィールド)。
Claude Codeからの使い方
Claude Codeでは、非対話モードの扱いが関係します。claude -pやAgent SDKの実行中には/mcpパネルがなく、OAuthフローを走らせられません。v2.1.196以降、tool searchが有効(既定)なら、認証が必要なサーバーのツールが使えないことがClaudeに伝わります。サインインは対話セッションの/mcpかclaude mcp login <name>で済ませる設計です。
CIのように人間がいない環境では、Authorizationヘッダーを自分で用意する経路があります。claude mcp addの--headerで静的なトークンを渡せるほか、短命なトークンはheadersHelperで接続のたびに生成できます。headers.Authorizationを設定したサーバーが拒否すると、OAuthにフォールバックせず接続失敗になります。
v2.1.287のclaude mcp add --helpに出るOAuth関連のオプションは--client-id・--client-secret・--callback-portです。これらは事前登録済みのOAuthクライアントを使うためのもので、グラントの種類を選ぶオプションはありません。
Claude Tag(v2.1.273)では、小文字のトークンタイプを返すプロバイダーでclient-credentialsとJWT-bearerの接続が失敗する問題が直りました。
「拡張」の位置づけ
OAuth Client Credentialsは、MCPのコア仕様ではなく、識別子io.modelcontextprotocol/oauth-client-credentialsを持つ公式拡張です。既定で無効で、クライアントとサーバーの双方が宣言して初めて有効になります。拡張が公式になるまでの手順はMCPのSEPプロセスにあります。
ext-authのREADMEでは、Enterprise-Managed AuthorizationがStable、Client CredentialsがDraftに分類されています。仕様のパスもspecification/draftです。仕様の記述がOAuth 2.1のIETFドラフト(draft-ietf-oauth-v2-1-13)に基づく点も、docsページの「OAuth 2.0 client credentials flow」という説明とは表記が違います。
対応クライアントの現状
マトリクスはコミュニティが維持しており、載っていない=未対応とは限りません。
そのため、バックグラウンドサービスやCI/CDからMCPサーバーを呼ぶ構成では、SDKのClientCredentialsProvider系のクラスか、mcpcのようなCLIを使うことになります。リモートMCPの標準的なOAuth認可との違いはリモートMCPのOAuth認証にあります。
まとめ
鍵を預けたくないならJWTアサーション、まず動かすならシークレットですが、docsが推奨するのは短命で署名鍵を送らないJWTです。
認可サーバーのメタデータでtoken_endpoint_auth_methods_supportedにprivate_key_jwtがあれば、仕様の形(セクション2.2)で送れます。docsページの図の形(セクション2.1)に対応するかは別の項目で決まります。RFC 8414のgrant_types_supportedにurn:ietf:params:oauth:grant-type:jwt-bearerがあるかで見分けます。client_secret_basicは、この判定には関係しません。