チュートリアル
MCP クライアントは OAuth の issuer をどう固定するか:Python SDK 1.30 と 2.2 に上げたあとも残る作業
サーバー側の OAuth は、誰がツールを呼べるかを決めます。9月28日の案内が問うのはクライアントです。発見結果が指したログイン先を、そのまま信じてよいか。
2026年9月28日、公式 MCP Python SDK のメンテナは、欠陥のある HTTP クライアントが、本来は本物のログイン先へ渡すべき client secret、authorization code、PKCE の証明を、相手の制御する token エンドポイントへ送ってしまう可能性を認めました。修正版は 1.x の 1.30.0 と 2.x の 2.2.0 です。9月7日に出ていて、当時のリリースノートは挙動変更と書いていました。案内そのものは9月28日で、Cycode も同日に調査を公開し、翌日に複数のセキュリティ媒体が伝えました。経緯は The Hacker News の記事、いま正しいクライアントの形は OAuth clients です。サーバーがトークンを要求する手順は、サイト内の MCP OAuth 2.1 のままです。自分で完全には支配しないリモートへ繋ぐときの配置は Remote MCP を本番へ を見てください。
案内が直すのはクライアントであり、すでに守ったサーバーではない
影響を受けるのは、公式 Python SDK を HTTP クライアントとして使い、OAuth を有効にしたプロセスです。自分では完全に信頼していない MCP サーバーへ接続し、本物のログインサービスの資格情報を持っています。メンテナの説明では、一部の発見経路が期待するログイン先を決める前に、サーバーが指した相手を採用していました。修正版は期待する issuer を先に決め、そのあと認可サーバーのメタデータを取ります。文書内の issuer がその相手でなければ拒否します。保存した資格情報にもログイン先が付き、A 用の記録を B とのトークン交換には使いません。
これは「MCP サーバーに OAuth 2.1 を足す」層とは別です。サーバー側は、誰がツールを呼べるか、トークンがそのリソース向けかを決めます。クライアント側は、発見文書が指す認可サーバーが、client secret を発行した相手かを決めます。両方要ります。サーバーだけ守っても、クライアントは発見の途中で別の相手についていけます。クライアントだけ直しても、オーディエンスを見ないサーバーの別問題は覆いません。
本線に入った修正の説明は PR 3398 です。認可サーバーのメタデータは、取得したサーバー自身を issuer と名乗る必要があります。根拠は RFC 8414 の 3.3 節 です。一致しなければ止めます。クライアントに「issuer を無視する」スイッチは足しません。
| 動かしているもの | この案内 | 上げたあとも残る作業 |
|---|---|---|
| HTTP クライアント、OAuthClientProvider | 1.9.1–1.29.1、または 2.0.0–2.1.1 | 1.30.0 か 2.2.0 へ上げ、古い client_info を消す |
| クライアント資格情報、または秘密鍵 JWT | 同じバージョン範囲 | 上げることに加え issuer を渡す |
| SDK で書いたサーバー、stdio、自前トークン | この案内の影響外 | 依存は修正版へ上げる |
影響範囲に入る相手を先に分ける
1.x は 1.9.1 から 1.29.1、2.x は 2.0.0 から 2.1.1 です。クラスは OAuthClientProvider、ClientCredentialsOAuthProvider、PrivateKeyJWTOAuthProvider、そして issuer 引数を持たない廃止済みの RFC7523OAuthClientProvider です。Cycode は高深刻度と評価しています。ローカル stdio、自分でトークンを付けるクライアント、SDK でサーバーだけを書く配置は、この案内の影響外です。
範囲が狭いからといって、古い pin を残してよいわけではありません。CLI、夜間ジョブ、デスクトップクライアントが、同じロックファイルの mcp を共有していることがよくあります。図ではサーバーだけが SDK を使うことになっていても、HTTP クライアントが 1.29 のまま入っています。上げる基準はロックファイルの実バージョンです。
旧版での一時対応は一つだけです。OAuth を有効にしたクライアントは、信頼するサーバーにだけ接続します。すでに信頼しないサーバーへ繋いだなら、ログイン先で client secret をローテーションし、発行済みトークンを失効させます。これは環境の外に出たかもしれない資格情報の話であり、発見手順を再現する話ではありません。
1.30 または 2.2 のあと、マシン間はまだ issuer が要る
ブラウザフローの OAuthClientProvider は、修正版では期待するログイン先を決めてからメタデータを取ります。ブラウザのない二本は違います。ClientCredentialsOAuthProvider と PrivateKeyJWTOAuthProvider には issuer を渡します。値は、その認可サーバーの /.well-known/oauth-authorization-server 文書が宣言する issuer です。発見は走りますが、トークン交換はその相手のメタデータだけを使います。サーバーが別を指せば、OAuthFlowError で止まります。
issuer を渡さないと、この二本は修正版でも発見結果についていきます。案内は明記しています。この引数がなければ、アップグレードはマシン間に届きません。1.30.0 の注意は通常の DeprecationWarning で、Python は既定で表示しないため、CI ログに出ないことが多いです。3.0 では必須になります。今書いてください。メジャーでジョブが赤くなるのを待たないでください。
RFC7523OAuthClientProvider には issuer 引数がありません。使い続けると、上げてもログイン先を固定できません。ClientCredentialsOAuthProvider か PrivateKeyJWTOAuthProvider へ移し、issuer を渡します。秘密は環境変数かシークレット管理から読み、リポジトリには書きません。
信頼するメタデータでは、issuer は取得 URL と一致する
すでに信頼している認可サーバーから文書を取ります。例は https://auth.example.com/.well-known/oauth-authorization-server です。中の issuer はそのサーバー自身でなければなりません。比較は文字列の一致で、正規化しません。末尾スラッシュ、ホストの大文字小文字、www の有無は別の値です。クライアントへ渡す issuer は文書の原文です。「同じに見える」アドレスを手で打たないでください。
次の JSON は、信頼している認可サーバーのメタデータです。issuer は取得したオリジンと一致し、token エンドポイントも同じホストにあります。
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/token",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
"code_challenge_methods_supported": ["S256"],
"authorization_response_iss_parameter_supported": true
}
Protected Resource Metadata は、どの認可サーバーがこの MCP を守るかをクライアントに伝えるだけです。ログイン先の身元の代わりにはなりません。クライアントはそのサーバーのメタデータを取り、issuer を照合します。リソース側が A を指し、認可サーバー文書が自分を B と呼ぶなら止めます。統合を先に通すために B を pin しないでください。
2026-07-28 の改訂は動的登録を下げ、Client ID Metadata Document を推しています。安定した HTTPS にクライアント JSON を置き、その URL が client_id です。SDK は client_metadata_url で受けます。認可サーバーが対応を宣言すると、/register で新しい id と secret を取りません。URL は非ルートパス付きの HTTPS で、構築時に検査します。保存済みの client_info はこの文書より優先されるので、古い記録を残すと新しい issuer は効きません。
古い client_info を消し、秘密のローテーションは別作業にする
修正版は、保存した登録にログイン先を付けます。アップグレード前の client_info にはその印がありません。案内はこれらの記録を消し、次の登録を新しい規則で作り直し、正しいログイン先へ縛るよう求めています。パッケージだけ上げて古いファイルを残すと、プロセスは結び付いていない登録を出し続けます。
記録を消すことと秘密を回すことは別です。client_info を消すのは、新しいコードが登録し直すためです。client secret のローテーションとトークン失効は、信頼しないサーバーへ資格情報を渡したかもしれないからです。その履歴がなければ、内部クライアントの秘密を全部替える必要はありません。履歴があるならログイン先で行い、チャットに secret を貼らないでください。
コールバックの state と iss は、届いたまま Provider へ返します。Provider は state を自分の生成値と、iss を発見した issuer と照合します。この二つは取り違え防止であり、デバッグ項目ではありません。自分でコールバックを解析して iss を落とすと、修正版が足した照合を外すことになります。
固定した issuer をリポジトリに置き、契約として見る
接続する MCP エンドポイントごとに一レコードです。サーバー URL と、許可する issuer。値は認可サーバーメタデータの原文から写します。レビューするのは整形した JSON であり、誰かのメモのドメインではありません。リリースのあいだで差分を取り、誰が issuer を別ホストへ動かしたかを見ます。
リポジトリに残すのはこの対照です。client_secret は残しません。秘密はシークレット管理にあり、ここが固定するのはログイン先だけです。
{
"clients": [
{
"mcp_server": "https://orders.example.com/mcp",
"issuer": "https://auth.example.com"
}
]
}
マシン間のコンストラクタとこの一覧は、同じ文字列を使います。一覧が https://auth.example.com で、コードにスラッシュが一つ多いと、修正版は別の相手とみなして止まります。欲しい失敗はこれです。ジョブは OAuthFlowError で赤くなり、token エンドポイントを静かに乗り換えません。
コミット前にJSON フォーマッタでメタデータと issuer 一覧を広げ、JSON Schema 検証で issuer と token_endpoint が文字列であることを確認し、JSON 差分で、誰がログイン先を変えたかを見ます。
よくある質問
ローカルの stdio サーバーだけなら、この案内は関係ありますか?
stdio クライアントは影響外です。同じ環境にこのパッケージの HTTP クライアントもあるなら、そのバージョンで上げます。ロックファイルに 1.9.1 から 1.29.1、または 2.0.0 から 2.1.1 があれば、対応する修正版へ上げます。「主に stdio」では飛ばしません。
1.30.0 に上げてログに警告が無いなら、もう安全ですか?
そうは判断できません。ClientCredentialsOAuthProvider と PrivateKeyJWTOAuthProvider に issuer が無いときの注意は、既定で隠れる DeprecationWarning です。非推奨警告を出すか、コンストラクタに issuer を書いてください。警告が見えないことは、引数が入ったことにはなりません。
動的登録と Client ID Metadata Document のどちらが向きますか?
認可サーバーが client_id_metadata_document_supported を宣言しているなら、HTTPS 上のクライアント JSON を使い、その URL を client_id にします。/register で毎回 secret を取りません。未対応なら SDK は動的登録へ戻ります。どちらの道でも保存済み client_info が優先されるので、上げたあとは古い記録を消します。
サーバー側の OAuth 2.1 の記事とはどう組み合わせますか?
サーバーの記事は、リソースサーバーが access token をどう要求するかです。この記事は、クライアントがログイン先を取り違えないことです。リモートが自分のゲートウェイの外にあるときは両方読みます。サーバーはトークンを検証し、クライアントは issuer をコンストラクタとリポジトリの一覧に固定します。
まとめ
9月28日の案内は、「発見結果が名乗るログイン先をそのまま信じる」を既定から外しました。HTTP クライアントは 1.30.0 か 2.2.0 へ上げます。マシン間には、メタデータ原文と一致する issuer を渡します。廃止された RFC7523 Provider は置き換えます。
そのあと古い client_info を消します。信頼しないサーバーへ繋いだことがあるなら、ログイン先で secret を回し、トークンを失効させます。リポジトリにはサーバー URL と issuer の対照だけを残し、整形、検証、差分でその文字列を見ます。