MCPのEnterprise-Managed Authorization・Client Credentials・通常OAuthの使い分け:呼び出し元別の選び方とID-JAG検証【MCPセキュリティ 2026】
MCP公式の認可拡張であるEnterprise-Managed Authorization(ID-JAG/Cross-App Access)とOAuth Client Credentialsの仕組み、社員・CI・サービスごとの方式の選び方、テスト済みのID-JAG検証コード(不正な許可証14件中11件を拒否)、2026年10月時点の対応クライアント、共有APIキーからの移行手順を解説します。
最終更新:2026年10月21日 / 対象:MCP認可拡張(ext-auth)のEnterprise-Managed Authorization(
io.modelcontextprotocol/enterprise-managed-authorization)とOAuth Client Credentials(io.modelcontextprotocol/oauth-client-credentials)、MCP仕様 2026-07-28版、IETFドラフト draft-ietf-oauth-identity-assertion-authz-grant-04
結論(30秒で読める要約)
MCPのEnterprise-Managed Authorization(EMA)とは、OktaなどのID基盤(IdP)が「どの社員が、どのMCPサーバーを使えるか」を一元的に決めるための、MCP公式の認可拡張です。クライアントは社員のSSOログインをIdPでID-JAGという短命の許可証に交換し、MCPサーバー側の認可サーバーがそれをアクセストークンに引き換えます。サーバーごとのOAuth同意画面は出ません。兄弟分のOAuth Client Credentials拡張は、CIジョブや常駐プロセス、サーバー間連携のように人が介在しない呼び出し元を対象にしています。
社内SaaSをMCPで公開しているチームにとって、MCPセキュリティの設計で変わる点は4つです。
- 認証方式はサーバー単位ではなく、呼び出し元の種類ごとに選びます。管理されたクライアントを使う社員はEMA、機械はClient Credentials(できればクライアントシークレットではなく署名付きJWTの
private_key_jwt)、管理外のクライアントを使う個人は通常のMCP OAuthです。固定のAPIキーは標準外の予備手段として残します(3章)。 - 一番重い仕事は自社の認可サーバーに来ます。ID-JAGの
typ、発行者、署名、audience、client_id、resource、jtiを確認し、スコープを絞ったうえで、MCPサーバー向けに限定したアクセストークンを自分で発行します。今回テストした検証コードは、細工した14通りの許可証のうち11通りを拒否しました(7章)。 - IdPにはMCPの通信が見えません。拡張仕様によると、IdPが見るのはトークン発行の段階だけです。失効とポリシーはIdPに移りますが、呼び出しごとの監査ログは引き続き自分たちで取る必要があります。
- 対応クライアントはまだ少数です。コミュニティが管理する拡張対応表では、EMA対応は2クライアント、Client Credentials対応は1クライアントです。一方でAnthropicは、Claude TeamとEnterprise向けにOktaでのEMA対応を別途発表しています(8章)。未対応のクライアント向けの代替手段は残しておきます。
目次
- MCPのEnterprise-Managed Authorizationとは
- リモートMCPサーバーの4つの認証方式を比較
- 呼び出し元ごとの方式の選び方
- Enterprise-Managed Authorizationの流れ
- OAuth Client Credentials拡張の仕組み
- 誰が何を実装するか
- 認可サーバーでID-JAGを検証する(Python、テスト済み)
- 2026年10月時点のクライアント対応状況
- 共有APIキーからEMA・Client Credentialsへの移行手順
- チェックリスト(14項目)
- よくある失敗(症状→原因→対処)
- NodeFlareならこう解決できる
- よくある質問(FAQ)
1. MCPのEnterprise-Managed Authorizationとは
MCPのEnterprise-Managed Authorizationとは、社員一人ひとりではなく組織のIdPが、MCPクライアントからMCPサーバーへのアクセスを認可する仕組みです。公式ドキュメントはIdPを「最終的な判断者(the authoritative decision-maker)」と位置づけ、どの社員がどのMCPサーバーに、どんな条件でアクセスできるかをIdPが管理すると説明しています。
この拡張が生まれたのは、MCP本体の認可フローが「ユーザーがブラウザを開き、MCPサーバー側の認可サーバーにログインして同意する」という1つの型を前提にしているからです。公式ドキュメントは、企業ではその型が次のように行き詰まると整理しています。
| 企業で個人ごとの同意が困る点 | EMAで変わること |
|---|---|
| 社員が各サーバーの認可の仕組みを理解しなければならない | IdPのポリシーが判断し、社員はSSOでログインするだけ |
| セキュリティ部門が統一したポリシーを適用できない | トークン発行前に、グループ・ロール・条件付きアクセスが評価される |
| 入社時に数十のサービスを手作業で認可する | 承認済みのサーバーは初回ログインから使える |
| 退職時にサービスごとに失効させる | IdPで失効させれば、全クライアントで新しい許可証が出なくなる |
本記事で使う用語は次の3つです。
| 用語 | 意味 |
|---|---|
| ID-JAG | Identity Assertion JWT Authorization Grant。企業のIdPが発行する短命のJWTで、「このユーザーが、このクライアントで、この認可サーバーに、このスコープを求めてよい」ことを表す。ヘッダーの typ は oauth-id-jag+jwt |
| Cross-App Access(XAA) | 土台になっているIETFの方式(draft-ietf-oauth-identity-assertion-authz-grant)の通称。Oktaはこの名前で展開している |
| リソース側の認可サーバー | 自社のMCPサーバー向けのアクセストークンを発行する認可サーバー。ID-JAGを受け取って引き換える |
2つの認可拡張は、公式の ext-auth リポジトリ で管理されています。MCPの拡張はすべてオプトイン方式で、クライアント側の明示的な対応が必要であり、既定で有効になることはないと公式ドキュメントに書かれています。
2. リモートMCPサーバーの4つの認証方式を比較
リモートMCPサーバーが受け取る資格情報は、現在4種類あります。3つはMCPの公式文書で定義されていますが、固定のAPIキーは広く使われているもののMCPの仕様には出てきません。
| MCP本体のOAuth(認可コードフロー) | Enterprise-Managed Authorization | OAuth Client Credentials | 固定のAPIキー | |
|---|---|---|---|---|
| 定義元 | MCP仕様 2026-07-28版のAuthorization | ext-auth拡張(仕様は specification/stable 配下) |
ext-auth拡張(ページの表記は「Protocol Revision: draft」) | MCP外。自社の独自設計 |
| 呼び出し元 | ユーザー本人(対話的) | 社員(企業のIdP経由) | 機械(ユーザーなし) | 文字列を持っている人や機械 |
| サーバーごとのブラウザ同意 | あり | なし | なし | なし |
| アクセス可否を決める人 | ユーザー(+サーバー側のポリシー) | IdPのポリシー、その後に自社の認可サーバー | クライアントを登録した人 | キーを発行した人 |
| クライアント登録 | 事前登録、Client ID Metadata Document、または動的クライアント登録 | 多くはIdPへの事前登録。リソース側認可サーバーにはClient ID Metadata Documentも使える | 事前に別経路で登録。動的クライアント登録は使わない | なし |
| トークンの寿命の目安 | 短いアクセストークン+リフレッシュ | ID-JAGは仕様の例で300秒、アクセストークンは例で86,400秒 | 短く、SDKが自動更新 | ローテーションか失効まで |
| 失効のしかた | ユーザー×サーバー単位 | IdPで一元的に | クライアント単位 | キー単位 |
| クライアント対応(2026年10月) | 広い | 少ない(8章) | 少ない(8章) | どこでも(ヘッダーを付けるだけ) |
MCPセキュリティの観点で大事なのは2点です。1つ目は、EMAもClient Credentialsも最後は普通のBearerアクセストークンになることです。audienceに縛られたトークン、下流APIへのトークン転送(token passthrough)の禁止、リクエストごとの検証といった本体仕様のルールは、そのまま適用されます。2つ目は、固定APIキーは対応範囲が最も広い一方で、ライフサイクル管理が最も弱いことです。次の章でAPIキーを予備手段にとどめているのはこのためです。
3. 呼び出し元ごとの方式の選び方
方式は呼び出し元の種類ごとに選びます。社内SaaSが1つでも、3つの方式を同時に使うのが普通です。
| 呼び出し元 | 推奨する方式 | 代替手段 | 理由 |
|---|---|---|---|
| EMAに対応した管理クライアントを使う社員(例:OktaでつないだClaude Team/Enterprise) | Enterprise-Managed Authorization | 自社の認可サーバーでSSOを使う通常のOAuth | アクセス権がIdPのグループに連動し、退職処理が1か所で済む |
| EMA未対応のクライアントを使う社員 | 自社の認可サーバーを社内IdPと連携させた通常のMCP OAuth | スコープを絞った個人別APIキー | SSOでログインできるが、サーバーごとの同意は残る |
| CIパイプライン、定期ジョブ | private_key_jwt を使うClient Credentials |
クライアントシークレット方式、またはジョブごとのAPIキー | ユーザーがいない。署名付きJWTなら長期シークレットを送らずに済む |
| サーバー間連携 | private_key_jwt を使うClient Credentials |
シークレット管理サービスに置いたクライアントシークレット | 同上。クライアントごとに使えるツールを絞る |
| 社外の顧客ユーザー | 通常のMCP OAuth | — | 自社のIdPにいない |
| MCPサーバーから裏側のSaaS APIを呼ぶとき | MCPサーバー自身の資格情報 | — | 本体仕様がクライアントのトークンの転送を禁じている |
公式の概要ページも同じ分け方をしています。バックグラウンドサービス、CI/CD、サーバー間連携はClient Credentials、職場でMCPサーバーを使う社員や組織全体のアクセスポリシー適用はEMA、通常の対話的な認可は拡張不要です。
これからも発行するAPIキーの寿命・保管・ローテーションについては、MCPのAPIキー管理プレイブック にまとめています。
4. Enterprise-Managed Authorizationの流れ
EMAは2回のOAuthのやり取りをつなげた仕組みです。1回目はIdPでID-JAGを受け取り、2回目は自社のリソース側認可サーバーでそれをMCP用のアクセストークンに引き換えます。
- SSOログイン:社員が企業のIdP経由でMCPクライアントにログインします。クライアントは本人確認の結果(OpenID ConnectのIDトークンかSAMLアサーション)を保持します。
- ディスカバリ:クライアントはMCPサーバーのProtected Resource Metadata(RFC 9728)から認可サーバーを見つけ、その認可サーバーのメタデータの
authorization_grant_profiles_supportedにurn:ietf:params:oauth:grant-profile:id-jagがあるか確認します。 - IdPでのトークン交換(RFC 8693):クライアントがIdPにID-JAGを要求します。ポリシーはここで評価されます。アクセス権のないユーザーにはエラーが返り、許可されていないサーバー向けのトークンをクライアントが受け取ることはありません。
- 自社の認可サーバーでのJWT Bearerグラント(RFC 7523):クライアントがID-JAGを提示します。認可サーバーはそれを検証し、ユーザーを自社のアカウントにひも付けて、アクセストークンを発行します。
- MCPの呼び出し:クライアントはそのアクセストークンでMCPサーバーを呼びます。ここは本体仕様のフローと同じです。
拡張仕様に書かれているパラメーターは次のとおりです。
| 手順 | パラメーター・クレーム | 値 |
|---|---|---|
| 3 | grant_type |
urn:ietf:params:oauth:grant-type:token-exchange |
| 3 | requested_token_type |
urn:ietf:params:oauth:token-type:id-jag |
| 3 | audience |
リソース側認可サーバーの発行者識別子でなければならない(MUST) |
| 3 | resource |
任意。指定する場合はMCPサーバーのリソース識別子(RFC 9728) |
| 3 | subject_token_type |
urn:ietf:params:oauth:token-type:id_token(OIDCでログインした場合) |
| 3(応答) | ヘッダー | Cache-Control: no-store、Pragma: no-cache |
| 4 | grant_type |
urn:ietf:params:oauth:grant-type:jwt-bearer |
| 4 | assertion |
ID-JAG |
| ID-JAGのヘッダー | typ |
oauth-id-jag+jwt |
| ID-JAGのクレーム | 仕様の例 | jti、iss、sub、email、aud、resource、client_id、exp、iat、scope |
| ID-JAGのクレーム | resource |
含める場合は、MCPサーバーのリソース識別子でなければならない(MUST) |
| アクセストークン | audience | MCPサーバーに限定しなければならない(MUST) |
この設計から、運用上は次の3点が言えます。
- ポリシーの判定は発行時の1回だけです。拡張仕様によると、IdPが見るのはアクセストークン発行の段階だけで、MCPの通信は見えません。IdPは「誰が、どのクライアントで、どのサーバーを使えるか」とスコープの上限は決められますが、どのツールが呼ばれたかは分かりません。
- 最終的な判断は自社の認可サーバーに残ります。AtlassianはRovo MCPでの実装について、自社の認可サーバーが独自のポリシーも適用し、発行するトークンを決め、スコープを絞り込み、ユーザーが対象リソースに対して有効かを確認すると説明しています。リソースサーバー側は外部のBearerトークンを直接は信用しません。
- アカウントのひも付けには手間がかかります。MCPの公式ドキュメントは、
subクレームを安定した識別子として使い、emailはEMA導入前に作られた既存アカウントとの照合にだけ使うよう勧めています。Atlassianも、信頼ドメインをまたいだID対応付けを難しかった点の1つに挙げています。
5. OAuth Client Credentials拡張の仕組み
OAuth Client Credentials拡張は、機械がトークンエンドポイントで自分自身の身元を証明し、ブラウザもユーザーもなしでMCP用のアクセストークンを受け取る仕組みです。RFC 6749のclient credentialsグラントをそのまま使い、クライアントの認証方法を2つ認めています。
| 方式 | トークン要求 | 仕様上の位置づけ | クライアントが侵害されたときに漏れるもの |
|---|---|---|---|
JWTアサーション(private_key_jwt、RFC 7523) |
クライアントの秘密鍵で署名したJWTを付けて grant_type=client_credentials |
推奨(RECOMMENDED) | 短命のアサーション。秘密鍵そのものはクライアントの外に出ない |
| クライアントシークレット | client_id と client_secret を付けて grant_type=client_credentials |
使用可 | ローテーションするまで有効な長期シークレット |
拡張仕様の規範的な要点は次のとおりです。
- クライアントは上の2方式のどちらかで認証しなければなりません(MUST)。
- 認可サーバーの
token_endpoint_auth_methods_supportedには、private_key_jwtかclient_secret_basicの少なくとも一方を含めなければなりません(MUST)。JWT認証に対応する場合はtoken_endpoint_auth_signing_alg_values_supportedも必要です。 - 実装はOAuth 2.1の7章のセキュリティ実践に従わなければなりません(MUST)。
- 動的クライアント登録は使わず、資格情報は別経路で事前に登録します。
公式ドキュメントは、クライアントシークレットについてはっきり警告しています。シークレットは長期間有効な資格情報であり、漏えいすれば、ローテーションされるまで攻撃者がアプリとして気づかれずに認証できてしまう、というものです。そのため、できる限りJWTアサーションを使うよう勧めています。
公式のTypeScript SDK(@modelcontextprotocol/client)には ClientCredentialsProvider と PrivateKeyJwtProvider が、Python SDKには ClientCredentialsOAuthProvider と PrivateKeyJWTOAuthProvider があり、どちらもトークンの取得と更新を自動で行います。署名付きアサーションを使う最小構成(TypeScript)は次のとおりです。
import { Client, PrivateKeyJwtProvider, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
const provider = new PrivateKeyJwtProvider({
clientId: "ci-release-bot",
privateKey: process.env.CLIENT_PRIVATE_KEY_PEM, // シークレット管理サービスから注入
algorithm: "RS256",
});
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.saas.example/mcp"),
{ authProvider: provider },
);
const client = new Client({ name: "ci-release-bot", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);
MCPサーバー側で新たに必要になることはありません。本体仕様どおり、リクエストごとにトークンの署名とaudienceを検証し、ツールごとにスコープを確認します。server/discover で拡張への対応を示すのは任意ですが、推奨されています。
6. 誰が何を実装するか
EMAでは仕事が4者に分かれます。社内SaaSを運営しているなら、下の2行が自分たちの担当です。
| 担当 | EMAの場合 | Client Credentialsの場合 |
|---|---|---|
| MCPクライアント | _meta["io.modelcontextprotocol/clientCapabilities"].extensions で拡張への対応を示す。SSOに対応する。ID-JAGを取得して引き換える。ユーザーを自社の認可エンドポイントへリダイレクトしない。IdPの設定は組織単位の設定から読む |
拡張への対応を示す。秘密鍵かシークレットを保持する。トークンを取得・更新する |
| 企業のIdP | 承認したクライアントとMCPサーバーを登録する。ポリシーを評価する。ID-JAGを発行する | 関与しない |
| 自社のリソース側認可サーバー | ID-JAGのグラントプロファイルを公開する。ID-JAGを検証する。アカウントをひも付ける。スコープを絞る。audienceを限定したアクセストークンを発行する | クライアントを事前登録する。private_key_jwt や client_secret_basic に対応する。スコープ付きトークンを発行する |
| 自社のMCPサーバー | 任意で拡張への対応を示す。本体仕様どおりアクセストークンを検証し、ツール単位のスコープを適用する | 同じ |
拡張のネゴシエーションは2026-07-28版の方式に沿います。クライアントは各リクエストの _meta で対応する拡張を伝え、サーバーは server/discover の応答で自分の対応を示します。認可拡張を必須にしたサーバーは、未対応のクライアントからの接続を拒否できます。EMAを必須にするか、あくまで推奨にとどめるかは最初に決めておきましょう。Protected Resource Metadata、401・403の返し方、audienceの検証といった本体側の確認事項は、MCP OAuthサーバー実装ガイド で解説しています。
7. 認可サーバーでID-JAGを検証する(Python、テスト済み)
次のコードは、リソース側の認可サーバーがID-JAGを引き換える前に行う確認です。拡張仕様のMUSTと、AtlassianがRovo MCPについて公開した検証項目をまとめています。具体的には、typ、設定済みの発行者か、署名、audienceが自社の認可サーバーか、client_id が引き換えに来たクライアントと一致するか、resource の一致、有効期限と使い回しの防止、最後にスコープの絞り込みです。2026年10月21日に、Python 3.13、PyJWT 2.15、cryptography 50で動作を確認しました。
"""idjag_check.py - checks an MCP authorization server runs before redeeming an ID-JAG
(Enterprise-Managed Authorization extension, jwt-bearer grant). PyJWT >= 2.8."""
import time
import jwt
TRUSTED_IDPS = { # issuer -> JWKS URL, configured by an admin per tenant
"https://acme.okta.example": "https://acme.okta.example/oauth2/v1/keys",
}
MY_ISSUER = "https://auth.saas.example" # this authorization server
MY_RESOURCE = "https://mcp.saas.example/mcp" # the MCP server's canonical URI
ALLOWED_SCOPES = {"tickets:read", "tickets:write"}
_seen_jti: dict[str, int] = {} # use Redis with TTL in production
class Rejected(Exception):
pass
def check_id_jag(assertion: str, authenticated_client_id: str, key_resolver) -> dict:
header = jwt.get_unverified_header(assertion)
if header.get("typ") != "oauth-id-jag+jwt":
raise Rejected("typ is not oauth-id-jag+jwt")
iss = jwt.decode(assertion, options={"verify_signature": False}).get("iss")
if iss not in TRUSTED_IDPS:
raise Rejected("issuer is not a configured enterprise IdP")
claims = jwt.decode(
assertion,
key=key_resolver(TRUSTED_IDPS[iss], header.get("kid")),
algorithms=["RS256", "ES256"], # never accept "none" or HS*
audience=MY_ISSUER, # aud = this AS's issuer identifier
issuer=iss,
options={"require": ["iss", "sub", "aud", "exp", "iat", "jti", "client_id"]},
leeway=30,
)
if claims["client_id"] != authenticated_client_id:
raise Rejected("client_id does not match the client redeeming the grant")
if "resource" in claims and claims["resource"] != MY_RESOURCE:
raise Rejected("resource is not this MCP server")
now = int(time.time())
for k, exp in list(_seen_jti.items()):
if exp < now:
del _seen_jti[k]
if claims["jti"] in _seen_jti:
raise Rejected("jti already redeemed")
_seen_jti[claims["jti"]] = claims["exp"]
granted = set(claims.get("scope", "").split()) & ALLOWED_SCOPES # narrow, never widen
if not granted:
raise Rejected("no grantable scope")
return {"tenant_idp": iss, "sub": claims["sub"], "scope": " ".join(sorted(granted))}
IdPの代わりにローカルで作ったRSA鍵を使い、14通りの許可証を作って試しました。正常な許可証はどれも tickets:read admin:all を要求しています。
| テストした許可証 | 結果 | 理由 |
|---|---|---|
| 正常な許可証 | 受理。スコープは tickets:read に絞られた |
admin:all は付与対象外 |
| 同じ許可証を2回目に引き換え | 拒否 | jti が使用済み |
typ が oauth-id-jag+jwt ではなく JWT |
拒否 | トークン種別が違う(通常のIDトークンを許可証として使わせない) |
| 未設定の発行者 | 拒否 | 信頼していないIdP |
| 別の鍵で署名 | 拒否 | 署名検証に失敗 |
alg が HS256 |
拒否 | 許可していないアルゴリズム |
aud が認可サーバーではなくMCPサーバー |
拒否 | audienceの不一致 |
| 10分前に期限切れ | 拒否 | exp が過去 |
別の client_id のクライアントが引き換え |
拒否 | クライアントの不一致 |
resource が別のMCPサーバー |
拒否 | リソースの不一致 |
jti がない |
拒否 | 必須クレームの欠落 |
admin:all だけを要求 |
拒否 | 付与できるスコープがない |
resource クレームがない |
受理 | 拡張仕様では resource は任意。aud が許可証を縛っている |
合計は受理3件(使い回しテストの1回目を含む)、拒否11件でした。
テストから分かったことは次の4点です。
resourceは任意なので、許可証を縛るのはaudです。resourceがない許可証は設計どおり通りました。その後に発行するアクセストークンは、拡張仕様のとおりMCPサーバーにaudienceを限定する必要があります。typは最初に確認します。同じIdPが出した通常のIDトークンやアクセストークンを許可証として使い回されるのを、最も安く防げます。- スコープの絞り込みは自社のコードで行います。IdPがスコープの上限を決めることはできますが、認可サーバー側でもテナントとユーザーに許される範囲との積集合を取るべきです。
- 使い回しの防止には共有の保存先が必要です。メモリ上の
jtiキャッシュは1プロセスでしか効かないので、複数台で動かすならRedisやTTL付きのデータベースを使います。仕様の例のとおりID-JAGの寿命が300秒なら、保存量は小さく済みます。
本番では、IdPのJWKSから鍵をキャッシュとローテーション込みで取得し、信頼する発行者をテナントごとに分け、拒否した理由をすべてログに残してください。
8. 2026年10月時点のクライアント対応状況
対応状況はクライアントによって違い、公式な情報源どうしでもまだ記載がそろっていません。
| 情報源 | Enterprise-Managed Authorization | OAuth Client Credentials |
|---|---|---|
| MCPの拡張対応表(コミュニティ管理、2026年10月21日に確認) | Archestra、mcpc | mcpc |
| 公式SDK | — | TypeScriptとPythonに、クライアントシークレット用と private_key_jwt 用のプロバイダーがある |
| Anthropic(Claudeブログ、2026年6月18日。8月24日付の追記で一般提供と記載) | Claude TeamとEnterprise向け。開始時のIdPはOkta。EMA拡張の最初の実装と説明。開始時のコネクタはAsana、Atlassian、Canva、Figma、Granola、Linear、Supabaseで、追記でDatadog、Notion、Slackが加わった | — |
| Atlassian(2026年6月29日) | Rovo MCPサーバーがXAAとID-JAGによるEMAに対応。プライベートベータ | — |
この表の読み方は次のとおりです。
- 対応表はベンダーの発表より遅れています。Anthropicは自社ブログで実装を発表していますが、対応表ではClaudeに印が付いていません。対応表は最低ラインと考え、各ベンダーの情報で確認してください。なお、Claudeのブログは冒頭の追記で一般提供としつつ、「始め方」の節にはベータやウェイトリストの記述が残っています。実際に使えるかは管理画面で確認しましょう。
- 自社で作ったサーバーも対象になります。Anthropicは、オープンな標準の上に作られているため、自社チームが作るカスタムコネクタも含めて、どのコネクタでも対応できるとしています。
- IETFのドラフトはまだ確定していません。draft-ietf-oauth-identity-assertion-authz-grantは第4版(2026年5月21日)で、RFCではなくワーキンググループの文書です。細部は変わる可能性があります。
今は各クライアントを手作業で設定しているなら、リモートMCPクライアント設定チートシート で、クライアントごとのURLやヘッダーの設定場所を確認できます。
9. 共有APIキーからEMA・Client Credentialsへの移行手順
一度に全部を切り替える必要はありません。次の順番なら、危険な資格情報から先に減らしつつ、動く代替手段を残せます。
- 呼び出し元を棚卸しします。MCPサーバーに届く資格情報をすべて洗い出し、「人」「機械」「共有」に分類します。最初に手を付けるのはチームで共有しているキーです。
- 共有キーを分割します。呼び出し元ごと・ジョブごとに1つの資格情報にし、使えるツールを名前で絞ります。拡張を導入しない場合でも、これだけはやる価値があります。
- audienceを限定したトークンを発行できる認可サーバーを用意します。EMAとClient Credentialsのどちらにも必要です。既存のIdPをMCP用の認可サーバーとして使うなら、ID-JAGを使うjwt-bearerグラントと、
private_key_jwtによるクライアント認証に対応しているか確認します。 - 機械の呼び出し元を、
private_key_jwtを使うClient Credentialsへ移します。ジョブごとに別のクライアントとして登録し、秘密鍵はシークレット管理サービスに置き、短い並行期間のあとで対応するAPIキーを廃止します。 - まず1つの管理クライアントと1テナントでEMAを有効にします。IdPを設定し、既存アカウントとのひも付け(まず
sub、emailは既存アカウントの照合だけ)を試し、IdPのグループから外したユーザーに新しいトークンが出ないことを確認します。 - それ以外の利用者には通常のOAuthを残します。管理外のクライアントや社外ユーザーには、引き続き対話的なフローが必要です。
- アクセストークンの寿命を短くします。EMAではIdPで失効させますが、すでに発行済みのトークンは期限まで使えてしまいます。仕様の例は86,400秒なので、退職時の反映速度を重視するならもっと短くします。
10. チェックリスト(14項目)
方式の選択
- [ ] すべての呼び出し元の種類に方式(EMA、Client Credentials、通常のOAuth、予備のAPIキー)を割り当てた
- [ ] 人・チーム・ジョブの間で共有している資格情報がない
- [ ] 拡張に未対応のクライアント向けの代替手段がある
リソース側の認可サーバー(EMA)
- [ ] メタデータの
authorization_grant_profiles_supportedにurn:ietf:params:oauth:grant-profile:id-jagを載せた - [ ]
typがoauth-id-jag+jwtでないID-JAGは拒否する - [ ] 発行者をテナントごとに許可リストで管理し、鍵はIdPのJWKSから取得する
- [ ]
audが自社の認可サーバーの発行者識別子と、client_idが引き換えに来たクライアントと一致することを確認する - [ ]
resourceがある場合は、MCPサーバーのリソース識別子と一致することを確認する - [ ]
jtiをexpまで保存し、全レプリカで使い回しを拒否する - [ ] 付与するスコープは、要求されたスコープと自社ポリシーの積集合にする
Client Credentialsとトークン
- [ ] 機械は
private_key_jwtを使い、クライアントシークレットを使う場合はシークレット管理サービスに置いて定期的にローテーションする - [ ] すべてのアクセストークンはMCPサーバーにaudienceが限定され、リクエストごとに検証される
- [ ] アクセストークンの寿命を、退職時に求める反映速度に合わせた
運用
- [ ] MCPへのリクエストごとに、呼び出し元・ツール名・結果をログに残す(IdPはMCPの通信を見ないため)
11. よくある失敗(症状→原因→対処)
| 症状 | 原因 | 対処 |
|---|---|---|
| 退職した社員が何時間もツールを呼べてしまう | 失効前に発行された寿命の長いアクセストークン | アクセストークンの寿命を短くする。自社の認可サーバー側でも失効させる |
| IdPの通常のIDトークンが許可証として通ってしまう | typ を確認していない |
デコードを進める前に oauth-id-jag+jwt を必須にする |
| あるクライアント向けの許可証を別のクライアントが引き換えられる | client_id クレームと認証済みクライアントを比べていない |
比較して、一致しなければ拒否する |
| テナントAのユーザーがテナントBのアカウントに入ってしまう | email だけでひも付けている、または設定済みの発行者をどのテナントにも信頼している |
iss と sub の組でひも付け、信頼する発行者をテナントごとに分ける |
| IdPのポリシーより広いスコープのトークンが出てしまう | 認可サーバーが要求されたスコープをそのままコピーしている | 自社ポリシーとの積集合を取り、広げない |
| ロードバランサーの後ろで使い回しが成功する | jti のキャッシュがプロセスのメモリにある |
TTL付きの共有ストアを使う |
| CI用のトークンがビルドログから漏れる | クライアントシークレットをコマンド引数で渡している、または出力している | private_key_jwt を使い、CIのシークレット機能から鍵を注入し、ログを伏せ字にする |
| クライアントが分かりにくいエラーで失敗する | サーバーがEMAを必須にしているのに、クライアントが拡張への対応を示していない | 必須であることを文書化し、明確なエラーを返すか、通常のOAuthを代替手段として用意する |
| 「どのツールを誰が使ったかはIdPのログで分かる」と思っていた | IdPが見るのはトークン発行だけ | MCP側でリクエストごとの監査ログを取る |
12. NodeFlareならこう解決できる
NodeFlareは、リモートMCPサーバーの前段にプロキシを置き、リクエストがコードに届く前に認証・認可・記録を行うホスティングサービスです。ただし、ドキュメントにはEnterprise-Managed AuthorizationやOAuth Client Credentials拡張への対応は書かれておらず、NodeFlareは企業のIdPでもありません。ID-JAGの発行と引き換えは、引き続きIdPと自社の認可サーバーの担当です。NodeFlareが受け持てるのは、これらのフローの周りの実行環境です。本記事で挙げたリスクと、ドキュメントに記載された機能の対応は次のとおりです。
| 本記事のリスク | NodeFlareの機能 | 内容 |
|---|---|---|
| 機械用の資格情報の共有や長期化(3章・9章) | 呼び出し元ごとの mcp_* APIキーとツール単位のスコープ |
キーはハッシュ化して保存されます。tools:call:<ツール名>(例:tools:call:get_weather)、tools:list、resources:read:<uri>、prompts:get:<name> といったスコープをプロキシが転送前に照合し、拒否された呼び出しはJSON-RPCエラーを返してサーバーに届きません |
| 資格情報の総当たり | 誤ったキーによるロックアウト | 同じIPから誤ったキーが繰り返し使われると、そのIPを10分間ロックします |
| 想定外のネットワークからの機械の接続 | IP許可リスト | ポリシーを restricted にしてIPv4/IPv6のCIDRを指定します。プロキシが認証前、かつscale-to-zeroで止まっているサーバーの起動前に確認し、リストが空なら全拒否になります。ネットワークアクセス制限はProプランの機能です |
| EMA未対応のクライアントを使う対話ユーザー | ディスカバリ付きのOAuth | プロキシはOAuthアクセストークンを受け付け、RFC 8414とRFC 9728のディスカバリメタデータを配信するので、標準的なMCPクライアントは認可フローを自動で見つけられます |
| 自社でEMA用の認可サーバーとトークン検証を運用したい | パススルー | auth_enabled = false にすると、呼び出し元の資格情報がそのままサーバーへ転送されます。その場合、検証はサーバー側で行う必要があります |
| MCPサーバーが持つ裏側のSaaS用資格情報(3章の最終行) | 暗号化されたシークレット | 環境変数は保存時に暗号化され、VM起動時にだけ注入されます。ビルド引数、イメージレイヤー、ログには残らず、ビルドログでは既知のトークン形式が伏せ字になります |
| IdPにはMCPの通信が見えない(4章) | リクエストの監査ログ | すべてのJSON-RPCリクエストを、mcp_method、tool_name、arg_keys、http_status、outcome、caller_kind、client_ip などの項目とともに記録します。保持期間はFreeで7日、ProとTeamで30日です |
| 侵害されたサーバーから他のシステムへの到達 | サーバーごとのFirecracker microVMとEgress制御 | 各サーバーは専用のmicroVMで動きます。Egress制御のモードは off(既定)、observe、enforce の3つで、ホスト名、先頭のワイルドカード1つ(例:*.example.com)、IPを最大100件まで指定できます |
NodeFlareがやらないこと:ID-JAGの発行や検証はしません。Client Credentialsのトークンエンドポイントにもなりません。モデル内部でのプロンプトインジェクションも防げません。また、パススルーにした呼び出し元にはNodeFlareのツール単位のスコープ確認が効かないので、サーバー側の確認で必ずカバーしてください。
最小の始め方:
- GitHubリポジトリからMCPサーバーをデプロイし、裏側のSaaS用の資格情報は暗号化された環境変数として登録します。
- 機械の呼び出し元ごとに、必要な
tools:call:<ツール名>だけを付けたmcp_*キーを発行し、CIからの呼び出しはCIDRで制限します。 - 自社の認可サーバーでEMAとClient Credentialsを展開するあいだ、これらのキーを代替手段として残しておきます。
1サーバーまでは無料で試せます。NodeFlareを始める
13. よくある質問(FAQ)
MCPのEnterprise-Managed Authorizationとは何ですか?
組織のIdPが、どの社員がどのMCPサーバーを使えるかを管理するための、MCP公式の認可拡張(io.modelcontextprotocol/enterprise-managed-authorization)です。クライアントは社員のSSOの本人確認をIdPでID-JAGに交換し、それをMCP用のアクセストークンに引き換えます。サーバーごとの同意画面は出ません。
ID-JAGとは何ですか?
ID-JAG(Identity Assertion JWT Authorization Grant)は、企業のIdPがポリシーを確認したうえで発行する短命のJWTです。ユーザー、クライアント、引き換え先の認可サーバー(aud)、要求スコープが書かれており、ヘッダーは typ: oauth-id-jag+jwt です。IETFのドラフト draft-ietf-oauth-identity-assertion-authz-grant で定義されています。
Enterprise-Managed AuthorizationとCross-App Access(XAA)は同じものですか?
同じ仕組みを別の側から呼んだ名前です。Cross-App AccessはIETFのIDアサーショングラント方式の通称で、Oktaが推進しています。MCPのEnterprise-Managed Authorization拡張は、その方式をMCPのクライアント・サーバー・認可サーバー向けに具体化したものです。
MCPの仕様はEnterprise-Managed Authorizationを必須にしていますか?
いいえ。任意の拡張で、MCPの拡張は既定で有効になることはありません。クライアントとサーバーの両方が対応を示す必要があります。そもそもMCP本体の仕様でも、HTTPサーバーの認可自体が任意です。
MCPでOAuth Client Credentials拡張を使うべきなのはどんなときですか?
人が介在しない場合です。CI/CDパイプライン、定期ジョブ、常駐プロセス、サーバー間連携が該当します。人がアクセスを承認すべき場面では、通常のMCP OAuthかEnterprise-Managed Authorizationを使います。
MCPの機械クライアントはクライアントシークレットとprivate_key_jwtのどちらを使うべきですか?
private_key_jwt を優先してください。拡張仕様はJWTアサーションを推奨(RECOMMENDED)としており、公式ドキュメントはクライアントシークレットが漏れるとローテーションまで気づかれずに認証されると警告しています。署名付きアサーションは短命で、秘密鍵はクライアントの外に出ません。
Enterprise-Managed Authorizationに対応しているMCPクライアントはどれですか?
2026年10月時点で、コミュニティが管理するMCPの拡張対応表にはArchestraとmcpcが載っています。これとは別に、AnthropicはClaude TeamとEnterprise向けにOktaでのEMA対応を発表し、拡張の最初の実装と説明しています。状況は早く変わるので、各ベンダーのドキュメントで確認してください。
自社の社内MCPサーバーでEnterprise-Managed Authorizationに対応できますか?
リソース側の認可サーバーがID-JAGを引き換えられれば対応できます。ID-JAGのグラントプロファイルを公開し、発行者・署名・typ・audience・client_id・resource・jti を検証し、ユーザーをひも付けて、MCPサーバーに限定したアクセストークンを発行します。Anthropicは、自社チームが作るカスタムコネクタも対応できるとしています。
Enterprise-Managed AuthorizationでAPIキーは不要になりますか?
対応クライアントを使う社員については、個人ごとのキーは不要になります。機械はClient Credentialsへ移すのが筋です。どちらの拡張にも対応していないクライアント向けには、キーを呼び出し元ごとに分け、使えるツールを名前で絞ったうえで、APIキーを現実的な代替手段として残せます。
IdPから、社員がどのMCPツールを呼んだか分かりますか?
分かりません。拡張仕様によると、IdPが見るのはアクセストークン発行の段階だけで、MCPの通信は見えません。誰にどのスコープのトークンを出すかは決められますが、呼び出しごとの記録はMCPサーバーかゲートウェイの監査ログで取る必要があります。
Enterprise-Managed Authorizationでは、退職時のアクセス停止はどのくらいで効きますか?
IdPで失効させた時点から、新しいID-JAGは発行されなくなります。ただし発行済みのアクセストークンは、認可サーバー側でも失効させない限り期限まで有効です。退職時の反映速度に合わせて、アクセストークンの寿命を決めてください。
まとめ
MCPには、本体のOAuthフローが苦手な場面に対する公式の答えが2つ用意されました。Enterprise-Managed Authorizationは、ID-JAGを通じて企業のIdPが「どの社員がどのMCPサーバーに届くか」を決める仕組みで、OAuth Client Credentials拡張は、機械がスコープ付きトークンを標準的な方法で受け取る仕組みです(できれば private_key_jwt で)。どちらも自社側の仕事を減らすものではありません。認可サーバーはすべての許可証を検証してスコープを絞り、audienceを限定したトークンを発行します。IdPはMCPの通信を見ないので、MCPサーバー側の呼び出しログも引き続き必要です。対応クライアントはまだ少ないものの増えています。呼び出し元ごとに方式を割り当て、スコープを絞った代替手段を残し、まず共有キーから移行しましょう。これらのフローの土台になる本体側のOAuthの確認事項は、MCP OAuthサーバー実装ガイド で解説しています。
参考資料(一次情報)
- MCP: Authorization Extensions(概要)
- MCP: Enterprise-Managed Authorization
- ext-auth: Enterprise-Managed Authorization 仕様
- MCP: OAuth Client Credentials
- ext-auth: OAuth Client Credentials 仕様(ドラフト)
- MCP: Extensions(概要とネゴシエーション)
- MCP: 拡張の対応表
- IETF: draft-ietf-oauth-identity-assertion-authz-grant
- RFC 8693: OAuth 2.0 Token Exchange
- RFC 7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants
- Claudeブログ: Centrally manage authorization for MCP connectors
- Atlassian: Enterprise-Managed Authorization for Rovo MCP with XAA and ID-JAG
- NodeFlareドキュメント
