どうも、AWS大好き芸人の @ken11 です。
1026回目の夏、いかがお過ごしでしょうか。そろそろ梅雨明けも進んで、熱中症が怖い時期に突入しそうです。去年は3回なった。
さて、最近はMCPサーバーを自社サービスとして提供する場合の認証について考えていました。
MCP使ってますか?仕様もだいぶ落ち着いてきて、MCPサーバを提供するサービスも増えてきたと感じます。今日はそんなMCPサーバをいざ提供しようと思ったときにあたる、OAuthの話をしたいと思います。
MCPのOAuth仕様で起きること
HTTPベースのMCPサーバーを保護する場合、MCPではOAuth 2.x系の仕組みが使われます。
ざっくり言うと、MCPサーバーはProtected Resourceとして振る舞い、MCPクライアントはOAuth clientとしてAuthorization Serverからaccess tokenを取得します。そのtokenを Authorization: Bearer でMCPサーバーへ送り、MCPサーバー側で検証します。 MCPサーバーは未認証リクエストに対して 401 Unauthorized を返し、WWW-Authenticate ヘッダーでProtected Resource Metadataの場所を伝えます。MCPクライアントはそのmetadataを見て、どのAuthorization Serverを使えばよいかを発見します。
ここまでは自然です。ユーザーがMCPサーバーのURLを入力すれば、クライアントが認可情報を発見し、OAuthフローを開始できる。MCPらしい世界観です。
問題は、OAuthフローを始めるには client_id が必要なことです。
MCPの仕様では、MCPクライアントが client_id を取得する方法として、Client ID Metadata Document、事前登録、Dynamic Client Registrationなどが想定されています。Client ID Metadata Documentは、クライアントとサーバーの間に事前関係がない場面を扱うための方式です。DCRは現在のMCP仕様では必須ではなく、Authorization ServerとMCPクライアントがサポートしてもよい、という位置づけです。(modelcontextprotocol.io)
MCPの標準が目指しているのは、いろいろなMCPクライアントが、いろいろなMCPサーバーへ接続できる世界です。それ自体は分かります。
ただ、自社サービスとしてユーザーのデータを扱う場合、「誰でも接続しやすい」ことは、そのまま「メリット」とは限りません。
Cognitoで普通にやると、DCR/CIMDはそのまま使えない
ここでCognitoの話をします。
Cognito User PoolのApp clientは、OAuth/OIDCでいうOAuth clientの登録に相当します。App clientごとに、client ID、client secretの有無、許可するgrant type、callback URL、許可scope、token有効期限などを設定します。CognitoのOAuth 2.0認可では、User Pool内のApp clientに対して利用可能なgrant typeを設定します。Authorization Code GrantではPKCEもサポートされています。(docs.aws.amazon.com)
つまり、Cognitoを素直に使うなら流れはこうです。
- Cognito側でApp clientを作る。
- callback URLやscopeを設定する。
- 発行されたclient IDをMCPクライアント側に設定する。
- MCPクライアントはそのclient IDでAuthorization Code + PKCEを開始する。
- 認可成功後、Cognitoは登録済みのcallback URLへ認可コードを返す。
一方、MCPの仕様をそのまま追うと、MCPクライアントがclient IDを得る方法としてDCRやCIMDが出てきます。
Cognitoは、DCRやCIMDをそのまま受けるAuthorization Serverではありません。DCRのregistration endpointがあるわけではありませんし、CIMDのmetadata URLをclient IDとして渡しても、Cognito上のApp clientとして存在しなければ通常のOAuthフローは成立しません。
そのため、CognitoでMCPのOAuth仕様に広く対応しようとすると、自然と「Cognitoの前段にOAuth façadeやbrokerを置いて、DCR/CIMD相当を自前で吸収するか?」という話になります。
たとえば、DCRを受けて裏側でCognito App clientを作る。 あるいは、CIMDを解釈して、Cognitoの既存App clientや独自の認可処理へ対応づける。 技術的には、できなくはありません。
ただし、その瞬間に、Cognitoの「事前に作成したApp clientだけを扱う」という分かりやすい前提から外れます。
そして、 未知のクライアントが提示するredirect URIやmetadataをどこまで信用するのか 、という問題に向き合うことになります。
未知のOAuthクライアントが接続できるとはどういうことか
未知のOAuthクライアントというと、「知らないMCPクライアントが接続してくる」くらいに聞こえるかもしれません。
でも、問題はそう単純ではありません。
悪意あるアプリケーションは、別に「MCPクライアントです」と名乗る必要がありません。便利なPDF要約ツール、ブラウザ拡張、業務効率化ツール、社内検索ツールのように見せることもできます。
ユーザーはそのアプリケーションの用途を信じて使い始めます。しかし裏側では、MCPやCIMDの仕組みを使って、サービスのMCPサーバーに対するOAuthフローを開始する。
すると、ユーザーはサービスの正規ログイン画面へ誘導されます。ログイン画面は偽物ではありません。Cognitoなどの正規のAuthorization Serverです。
ここがややこしいところです。
ユーザーから見ると、正規のログイン画面が出ているので安心しやすい。しかし実際には、tokenの受け取り手は、別用途を装った悪意ある未知のOAuthクライアントかもしれません。 ユーザーがログインし、権限を許可すると、そのクライアントはaccess tokenを受け取ります。そのtokenでMCPサーバーを呼べるなら、ユーザーに許可された範囲のデータを取得できます。
これは、偽ログイン画面にパスワードを入力させるタイプの単純なフィッシングとは違います。 ログイン自体は正規のAuthorization Serverで行われています。問題は、 権限を渡している相手が、ユーザーもサービス提供者も意図していないアプリケーションになり得る ことです。
DCRはなぜ厳しいのか
Dynamic Client Registration、いわゆるDCRを有効にすると、クライアントはAuthorization Serverに対して動的にclient registrationを行えます。 ユーザーが事前にclient IDを用意しなくても接続できるので、体験としては便利です。 ただ、サービス提供者から見ると扱いはかなり重いです。
DCRでは、クライアントが登録したいmetadataをAuthorization Serverに送ります。そこにはredirect URIなど、OAuthの安全性に直結する情報が含まれます。
つまり、あるアプリケーションが「自分はこういうOAuthクライアントです」「認可コードの返却先はここです」と提示し、それが通れば、そのアプリケーションはclient IDを得てOAuthフローを開始できます。
もちろん、DCRにも制限はかけられます。登録エンドポイントを保護する、software statementを使う、redirect URIを検証する、許可するmetadataを絞る、といった対策はあります。 ただ、そこまでやるなら、結局「どのクライアントを信用するのか」を別の仕組みで判定する必要があります。それはもう、何でも動的に受け入れるDCRではありません。
DCRをそのまま採用すると、未知のクライアントが提示するredirect URIやmetadataをどう信用するのか、という問題を引き受けることになります。
CIMDならよいのか
では、Client ID Metadata Document、いわゆるCIMDならよいのでしょうか。
CIMDは、DCRのようにAuthorization Serverへ動的登録レコードを作らせる方式ではありません。クライアントが自分のmetadata documentを公開し、そのURLをclient IDとして使う方式です。MCP仕様でも、クライアントとサーバーの間に事前関係がない場合を扱いやすくする方式として説明されています。(modelcontextprotocol.io)
DCRより軽く見えるかもしれませんが、「未知のクライアントを受け入れる」という点では本質的な問題は変わりません。
CIMDでも、クライアントのmetadataにはredirect URIなどが含まれます。Authorization Serverは、それを見てOAuthフローを成立させるかどうかを判断します。 つまり、サービス側が事前に把握していないOAuthクライアントが、自分のmetadataを提示して、ユーザーを認可画面へ誘導する構図は残ります。 しかも、それがMCPクライアントを名乗るとは限りません。別用途のアプリケーションを装いながら、裏側でCIMDを使ってOAuthフローを開始することも考えられます。
もちろん、CIMDにも制限はかけられます。redirect URIをclient IDのoriginと同一に制限する、特定domainのclient IDだけを許可する、metadataを検証する、といった対策はあり得ます。 しかし、これも結局は「そのクライアントを信用してよいのか」を判断する仕組みが必要になります。
現実解: 対応するMCPクライアントを限定する
MCPは、いろいろなクライアントがいろいろなサーバーへ接続できることを重視しているので、DCRやCIMDは仕様として至って普通のことです。ただ、顧客データや社内データに触れるMCPサーバーでは、MCPの接続性をそのまま最大化するのは危ういと感じています。ここにあるのはAIを取り巻く環境の進歩とそれに対する仕様の矛盾でしかないような気もしてきます。
なにはともかく、現実的には結局のところはなっからクライアントを限定する≒MCPらしさを一部捨てるというのが、自分のいま考えている答えです。
任意のMCPクライアントに対応するのではなく、 接続を許可するMCPクライアントを限定することで、DCR/CIMDを受け入れない 。 Claude向け、ChatGPT向け、社内エージェント向け、といった単位でOAuth clientを事前に作成する。MCPサーバー側では、そのclient IDだけを許可する。
重要なのは、client IDそのものではありません。許可済みclient IDに紐づくredirect URI、scope、client typeを、サービス提供者側で固定できることです。
プロダクトで扱うデータが重いなら、接続性よりも先に境界を決めた方がよいかなと思います。
Claudeは対応しやすい
この方針を取るには、MCPクライアント側が、サービス提供者の用意したOAuth clientを使える必要があります。
メジャーどころで対応しやすいのはClaudeです。 Claudeは、MCPサーバーへ接続する手段としてカスタムコネクタを提供しており、OAuth Client Secretの入力欄は必要な場合のみ使うものとして説明されています。また、DCR以外の認証方式も含めて、Claude側で認証設定を行える構成になっています。Claudeのドキュメントでは、サービス提供者が自分のAuthorization Serverで作成したOAuth client_id / client_secretをAnthropicに渡し、Anthropic側で保持する方式も説明されています。(claude.com)
これにより、サービス側ではClaude用のOAuth clientをCognitoに事前作成し、そのclient IDをClaude側に設定する、という構成が取れます。 DCRやCIMDを受け入れなくても、事前登録したclient IDでAuthorization Code + PKCEのフローを成立させられます。
現時点でメジャーどころから始めるなら、 Claudeのカスタムコネクタはかなり現実的な選択肢 だと思います。
ChatGPTは現時点では少し扱いづらい
一方で、ChatGPTは現時点では少し扱いづらいと感じています。
OpenAIのApps SDKドキュメントでは、ChatGPTのOAuth redirect URLは https://chatgpt.com/connector/oauth/{callback_id} の形式で、アプリ管理画面に表示されるproduction redirect URIをAuthorization Server側のallowlistへ追加する、と説明されています。(developers.openai.com)
CognitoはApp clientごとにcallback URLを事前登録する必要があります。 そのため、ChatGPT側のcallback URLが接続単位などで変わる運用になる場合、Cognito側のcallback URL設定を都度更新する必要が出ます。
もちろん、ChatGPT連携が不可能という意味ではありません。OpenAI側に表示されるredirect URIをCognito側に登録できるのであれば、技術的にはOAuthフローを組めます。 ただ、Claudeのように固定的なOAuth client設定として扱いやすいかというと、現時点では慎重に見た方がよいです。
複数環境、複数顧客、複数組織に展開するSaaSでは、callback URLの管理が運用負荷になります。Cognitoの設定を動的に更新する運用は、認証基盤としてはあまりやりたくありません。
まとめ
MCPサーバーをサービスとして提供するとき、OAuthの難しさはtoken検証の実装だけではありません。
考えるべきなのは、どのMCPクライアントをOAuth clientとして信用するのか。そしてユーザーが、どのアプリケーションにアクセスを許可していると理解できるのか、です。
DCRやCIMDを受けず、許可するMCPクライアントを限定する。Cognitoを使う場合は、MCPクライアントごとにApp clientを事前作成し、MCPサーバー側でclient IDをallowlistする。 これはMCPの接続性を一部制限します。 ただ、新しいプロトコルに広く対応することと、ユーザーのデータを安全に扱うことは、必ずしも同じではありません。
「未知のクライアントを受け入れないこと」 は プロダクトとしてMCPサーバーを提供するなら重要なポイントです。