For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
メインナビゲーション
Theme
概要 ChatGPT と Codex のユーザー向けドキュメント ユースケース

認証

プラグインの MCP サーバーで利用できる認証方式を説明します。

ユーザー認証

多くのプラグインの MCP サーバーは、匿名でも読み取り専用で利用できます。ただし、顧客ごとのデータを公開したり、書き込み操作を提供したりする場合は、ユーザーを認証してください。

公開したプラグインは ChatGPT と Codex の両方で利用できます。MCP の認可要件は両製品に共通です。このガイドでは、コールバック、メタデータドキュメント、アカウント連携画面が製品によって異なる場合に、ChatGPT クライアント固有の動作を説明します。

既存のサーバー側アプリケーションに接続したり、ユーザー間でデータを共有したりする必要がある場合は、独自の認可サーバーと統合できます。

OAuth 2.1 を使った独自認証

認証を必要とする MCP サーバーには、MCP 認可仕様に準拠した OAuth 2.1 フローを実装してください。

構成要素

  • リソースサーバー: ツールを公開し、リクエストごとにアクセストークンを検証する MCP サーバーです。
  • 認可サーバー: トークンを発行し、ディスカバリメタデータを公開する ID プロバイダーまたは独自実装のサーバーです。
  • クライアント: ChatGPT や Codex など、ユーザーに代わって動作する OpenAI のホストです。対応クライアントは、クライアント ID メタデータドキュメント(CIMD)、動的クライアント登録(DCR)、事前定義済みの OAuth クライアント、および PKCE を利用します。

MCP 認可仕様の要件

  • MCP サーバーで保護対象リソースのメタデータを公開
  • 認可サーバーで OAuth メタデータを公開
  • OAuth フロー全体で resource パラメーターの値を引き継ぐこと
  • OpenAI ホストが自身の OAuth クライアントを識別または登録する方法を CIMD、DCR、事前定義済みの OAuth クライアントから選択
  • 認可サーバーのトークンエンドポイントが受け付ける認証方式を公開

以下では、各要件を具体的に説明します。

MCP サーバーでの保護対象リソースのメタデータ公開

  • ChatGPT がメタデータの取得先を特定できるよう、GET https://your-mcp.example.com/.well-known/oauth-protected-resource などの HTTPS エンドポイントを用意してください。同じ URL を WWW-Authenticate ヘッダーで通知する方法もあります。このヘッダーは 401 Unauthorized レスポンスに含めます。
  • このエンドポイントは、リソースサーバーと利用可能な認可サーバーを記述した JSON ドキュメントを返します。
{
  "resource": "https://your-mcp.example.com",
  "authorization_servers": ["https://auth.yourcompany.com"],
  "scopes_supported": ["files:read", "files:write"],
  "resource_documentation": "https://yourcompany.com/docs/mcp"
}
  • 設定する主なフィールドは次のとおりです。
    • resource:MCP サーバーの正規の HTTPS 識別子です。ChatGPT は OAuth フローで、この値をそのまま resource クエリパラメーターとして送信します。
    • authorization_servers:ID プロバイダーを指す発行者のベース URL を 1 つ以上指定します。ChatGPT は各 URL で OAuth メタデータを探します。
    • scopes_supported:任意のリストです。ChatGPT がユーザーに求める権限を説明するために使用します。
    • RFC 9728 の任意フィールド(resource_documentation、resource_policy_uri、resource_tos_uri など)を含めると、クライアントや管理者が構成を把握しやすくなります。

未認証のリクエストを拒否する場合は、次のようなチャレンジを返してください。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://your-mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

このヘッダーだけで、ChatGPT は初めて接続する場合でもメタデータの URL を特定できます。

認可サーバーでの OAuth メタデータ公開

  • ChatGPT が構成を取得できるよう、ID プロバイダーは次のいずれかの既知の場所でディスカバリドキュメントを公開する必要があります。
    • OAuth 2.0 メタデータの公開先 https://auth.yourcompany.com/.well-known/oauth-authorization-server
    • OpenID Connect メタデータの公開先 https://auth.yourcompany.com/.well-known/openid-configuration
  • どちらのドキュメントからも、OpenAI ホストはユーザーのリダイレクト先、認可コードの交換方法、自身をクライアントとして識別する方法を確認できます。以下は典型的なレスポンスです。
{
  "issuer": "https://auth.yourcompany.com",
  "authorization_response_iss_parameter_supported": true,
  "authorization_endpoint": "https://auth.yourcompany.com/oauth2/v1/authorize",
  "token_endpoint": "https://auth.yourcompany.com/oauth2/v1/token",
  "client_id_metadata_document_supported": true,
  "token_endpoint_auth_methods_supported": ["none", "private_key_jwt"],
  "registration_endpoint": "https://auth.yourcompany.com/oauth2/v1/register",
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["files:read", "files:write"]
}
  • 各フィールドを次のように設定してください。
    • issuer:認可サーバーの正規の識別子です。保護対象リソースのメタデータの authorization_servers リストにも、まったく同じ値を使用してください。
    • authorization_response_iss_parameter_supported:true に設定できるのは、エラーを含むすべての認可レスポンスで認可サーバーが iss パラメーターを返す場合だけです。
    • authorization_endpoint、token_endpoint:ChatGPT が PKCE を使った OAuth 認可コードフローを開始し、トークン交換まで完了するために必要な URL です。
    • client_id_metadata_document_supported:ChatGPT のクライアント登録に CIMD を使用する場合は true に設定します。ChatGPT は利用可能であれば CIMD を優先しますが、CIMD と DCR の両方が利用可能な場合は、プラグインの作成者が DCR を選択できます。
    • token_endpoint_auth_methods_supported:認可サーバーのトークンエンドポイントが受け付ける認証方式を含めてください。これは CIMD、DCR、事前定義済みの OAuth クライアントのすべてに適用されます。CIMD の場合、ChatGPT はパブリッククライアントとしてのトークン交換に none、署名付きクライアントアサーションによるトークン交換に private_key_jwt を使用できます。他の OAuth クライアントでは、一般に none、client_secret_post、client_secret_basic のいずれかを使用します。
    • registration_endpoint:動的クライアント登録(DCR)に対応する場合に含めます。DCR を使用すると、ChatGPT は MCP サーバー接続専用の client_id を作成し、再利用できます。
    • code_challenge_methods_supported:S256 を含める必要があります。認可サーバーのメタデータにこのフィールドがない場合や、S256 への対応が明示されていない場合、その MCP サーバーはサポートされません。これは MCP 認可仕様の要件です。
    • 任意フィールドは RFC 8414 または OpenID Discovery に従います。管理者のポリシー設定に役立つものを含めてください。

OIDC スコープ

  • プロバイダーが OIDC スコープ(openid、email、profile など)を scopes_supported に記載している場合、ChatGPT は OAuth フローでデフォルトでそれらのスコープを要求します。対象となるのは .well-known/oauth-authorization-server または .well-known/openid-configuration のドキュメントです。
  • ID プロバイダーによっては、記載した OIDC スコープがデフォルトでは有効になっていない場合があります。CIMD を使う場合も、手動または DCR でクライアントを作成した場合も、プロバイダーの設定を確認し、記載したすべてのスコープがその OAuth クライアントで有効になっていることを確認してください。

ワークスペースのドメイン制限への対応

ChatGPT Enterprise ワークスペースでは、メールドメインの所有権を確認できます。OAuth で連携したプラグインがユーザーの確認済みメールアドレスを提供すると、ChatGPT はそのドメインに基づき、ユーザーがその企業アカウントを使って個人用ワークスペースや組織外のワークスペースでプラグインを連携するのを防げます。

この保護に対応するには、認可サーバーを次のように設定してください。

  • OpenID Connect のディスカバリメタデータを公開します。
  • openid と email のスコープをメタデータに記載し、有効にします。
  • ユーザーの email クレームと email_verified: true を返す UserInfo エンドポイントをメタデータに記載します。

OAuth フローでこれらのクレームを ID トークンに含めて返すこともできますが、ワークスペースのドメイン制限には UserInfo エンドポイントが必須です。

Enterprise ワークスペース側でもドメインの所有権を確認する必要があります。認可サーバーはユーザーの ID 情報を提供し、ChatGPT がそれをワークスペースに設定された確認済みドメインと照合します。認可サーバーがワークスペースのドメイン所有権を確認するわけではありません。

再認可時のログイン状態の引き継ぎ

追加の OAuth スコープを要求する場合など、ChatGPT が既存の連携を再認可するときは、以前の OIDC ID トークンを標準の id_token_hint パラメーターとして認可リクエストに含めることがあります。ユーザーが最初からログインし直さずに追加スコープを許可できるよう、初回の OAuth フローで ID トークンを発行し、認可時に id_token_hint を利用するよう認可サーバーを設定してください。

この最適化は任意です。ID トークンがない場合や、認可サーバーがヒントを使用しない場合も、再認可は可能です。

発行者識別によるコールバックの保護

OpenAI ホストは RFC 9207 の発行者識別を使用し、認可サーバーの取り違えを狙う攻撃から OAuth コールバックを保護します。対象となる OAuth クライアントの作成時に ChatGPT と Codex が固定のリダイレクト URI を使用できるよう、次の要件を満たしてください。

  • authorization_response_iss_parameter_supported: true を認可サーバーのメタデータに設定します。
  • メタデータの issuer フィールドと、保護対象リソースのメタデータの authorization_servers リストには、まったく同じ発行者識別子を使用します。
  • 成功時もエラー時も、すべての認可レスポンスで iss を返します。その値はメタデータの issuer と完全に一致する必要があります。クライアントは文字列をそのまま比較し、末尾のスラッシュ、パス、ポート、大文字と小文字の違いを正規化しません。

ChatGPT と Codex はユーザーをリダイレクトする前に、選択したメタデータの issuer を記録し、認可コードを交換する前に、返された iss を確認します。発行者識別への対応を明示したサーバーが iss を返さない場合や、値が一致しない場合、ChatGPT と Codex はレスポンスを拒否します。これらの要件は MCP の認可レスポンス検証規則に従っています。

リダイレクト URL

MCP サーバーの管理ページに表示される本番用のリダイレクト URI を、そのまま認可サーバーの許可リストにコピーしてください。

  • 認可サーバーが上記の発行者識別要件を満たさない場合、ChatGPT はコールバック ID ごとのリダイレクト URI https://chatgpt.com/connector/oauth/{callback_id} を使用します。
  • 認可サーバーが要件を満たす場合、ChatGPT は固定のリダイレクト URI https://chatgpt.com/connector_platform_oauth_redirect を使用します。

ChatGPT がコールバック ID ごとのリダイレクトを導入する前に公開された MCP サーバーも、引き続き固定のリダイレクト URI を使用します。

OAuth フロー全体での resource パラメーターの引き継ぎ

  • ChatGPT は認可リクエストとトークンリクエストの両方に resource=https%3A%2F%2Fyour-mcp.example.com を追加します。この値によって、トークンが前述の保護対象リソースのメタデータに紐づきます。
  • 認可サーバーがこの値をアクセストークンにコピーするように設定してください。通常は aud クレームに格納します。これにより、MCP サーバーはトークンが自分だけを対象に発行されたことを確認できます。
  • 想定した対象者またはスコープを持たないトークンを受け取った場合は拒否し、WWW-Authenticate チャレンジを返して、正しいパラメーターで再認可するよう ChatGPT に促してください。

認可コードフローへの対応

  • MCP クライアントとして動作する ChatGPT は、S256 コードチャレンジを使う PKCE 付き認可コードフローを実行します。これにより、攻撃者が傍受した認可コードを再利用できなくなります。
  • クライアントがメタデータから PKCE への対応を確認できるよう、認可サーバーは code_challenge_methods_supported に S256 を含めて公開する必要があります。

OAuth フロー

上記の MCP 認可仕様を実装すると、OAuth フローは次のように進みます。

  1. ChatGPT が MCP サーバーに問い合わせ、保護対象リソースのメタデータを取得します。

  1. ChatGPT が自身を OAuth クライアントとして識別します。MCP サーバーが CIMD を使用する場合、ChatGPT は動的クライアント登録を行わず、CIMD ドキュメントの URL を client_id として送信します。上記の発行者識別要件を満たす認可サーバーには固定の https://chatgpt.com/oauth/client.json を使用し、それ以外にはコールバック ID ごとの https://chatgpt.com/oauth/{callback_id}/client.json を使用します。MCP サーバーの管理ページには、その接続のコールバック方式に対応するクライアントメタデータドキュメントとリダイレクト URI が表示されます。MCP サーバーが DCR を使用する場合、ChatGPT は MCP サーバー接続ごとに一度だけ認可サーバーの registration_endpoint を呼び出し、生成された client_id を受け取ります。以降、その接続では登録済みのクライアントを再利用します。

CIMD を使う場合、クライアント登録のステップはありません。次の画面は DCR を使用した場合のものです。

  1. ユーザーが初めてツールを呼び出すと、ChatGPT クライアントが PKCE 付きの OAuth 認可コードフローを開始します。ユーザーは認証を行い、要求されたスコープに同意します。

  1. ChatGPT が認可コードをアクセストークンと交換し、以降の MCP リクエストにそのトークンを付与します(Authorization: Bearer <token>)。

  1. サーバーはリクエストごとにトークンの発行者、対象者、有効期限、スコープを検証してからツールを実行します。

クライアント登録

認可サーバーが対応しており、プラグインの作成者が選択した場合は、クライアント登録にクライアント ID メタデータドキュメント(CIMD)を優先して使用してください。CIMD では、ChatGPT は HTTPS で公開されたメタデータドキュメントの URL を client_id として使用します。認可サーバーはそのドキュメントを取得し、公開されたクライアントメタデータとリダイレクト先のリソース識別子を検証し、その URL を ChatGPT の固定のクライアント識別子として扱います。

CIMD に対応する場合は、認可サーバーのメタデータに client_id_metadata_document_supported: true を設定してください。これにより、CIMD を選択した MCP サーバーに対して、ChatGPT が共通の固定クライアント識別子を使用できます。認可サーバーはこの識別子をリダイレクト URI の許可リスト、レート制限、その他のポリシーに利用できます。

ChatGPT は MCP SEP-3149 で提案された CIMD の仕様変更への移行を進めています。本番環境の CIMD ドキュメントは、ChatGPT が使用できる方式を token_endpoint_auth_methods_supported に配列で公開しています。配列の順序は優先順位を表しません。移行期間中は、優先する方式を従来の単数形のフィールド token_endpoint_auth_method でも公開します。

{
  "token_endpoint_auth_methods_supported": ["none", "private_key_jwt"],
  "token_endpoint_auth_method": "private_key_jwt"
}

複数形のフィールドが表す内容は、ドキュメントによって異なります。認可サーバーのメタデータにはトークンエンドポイントが受け付ける方式を、ChatGPT の CIMD ドキュメントには ChatGPT が使用できる方式を記載します。ChatGPT は両方に含まれる方式から 1 つを選択します。従来の単数形のフィールドで指定された優先方式が両方に含まれていれば、ChatGPT はその方式を使用します。これは、今も単数形の指定を必須条件として扱う認可サーバーとの互換性を保つためです。そうでなければ、ChatGPT は両方に含まれる別の方式を使用できます。

CIMD の複数形のフィールドを読み取る認可サーバーは、ローカルのセキュリティポリシーがそのクライアントでの使用を禁止している場合を除き、両方に含まれる方式をどれでも受け付けるようにしてください。共通の方式以外は必ず拒否してください。client_id の URL は固定で、クエリパラメーターによって方式ごとのドキュメントを選択することはありません。

対応する方式は次のとおりです。

  • none:トークンエンドポイントがクライアント認証なしで PKCE を使った認可コード交換を受け付ける場合に、このパブリッククライアント用フローを使用します。ChatGPT はクライアントごとのシークレットを保存しません。
  • private_key_jwt:トークンエンドポイントがクライアント認証を要求する場合に、この署名付きクライアントアサーションを使うフローを使用します。ChatGPT は CIMD メタデータで公開 JWKS の URL を提供します。JWKS はメタデータと同じオリジンの /oauth/jwks.json から配信されます。ChatGPT は管理された秘密鍵と kid を使ってサーバー側でトークンリクエストに署名し、認可サーバーは公開 JWKS を使ってアサーションを検証します。

DCR も引き続き利用できます。registration_endpoint を含めると、プラグインの作成者が DCR を選んだ場合や CIMD を利用できない場合に、ChatGPT が動的に登録できます。ChatGPT は MCP サーバー接続ごとに一度だけ DCR を実行し、その接続用に登録した OAuth クライアントを保持して再利用します。DCR では接続が増えると登録済みクライアントも多数作成されるため、大規模な運用では通常、CIMD の方が管理しやすくなります。

MCP サーバー接続を使用している間は、登録済みの OAuth クライアントと、クライアントシークレットがある場合はそのシークレットを有効に保ってください。認可サーバーがいずれかの認証情報を失効、削除、または置換すると、ユーザーやレビュアーの接続時に invalid_client エラーが発生することがあります。アクセストークンとリフレッシュトークンは、通常どおり有効期限を設定したりローテーションしたりできます。

クライアントの識別

MCP サーバーがリクエストの送信元を ChatGPT と確認する方法については、よく質問が寄せられます。ChatGPT は MCP サーバーへの接続時に OpenAI が管理するクライアント証明書を提示するため、mTLS を使ってトランスポート層でクライアントを検証できます。ChatGPT が公開している送信元 IP アドレス範囲を許可リストに追加することもできます。ChatGPT は、クライアントクレデンシャル、サービスアカウント、JWT ベアラーアサーションなど、マシン間の OAuth グラントには対応していません。カスタム API キーや顧客が用意した mTLS 証明書も提示できません。

CIMD では ChatGPT の識別情報を記述したドキュメントを固定の HTTPS URL から認可サーバーに提供するため、クライアントの識別をさらに強化できます。private_key_jwt を使用する場合は、CIMD メタデータで公開された JWKS を使い、ChatGPT がトークンエンドポイントに送信したクライアントアサーションを検証してください。

相互 TLS(mTLS)

現在、ChatGPT は MCP サーバーとの TLS 接続を確立する際に、OpenAI が管理するクライアント証明書を提示します。アプリケーションでクライアント証明書を検証する場合は、以下の OpenAI 証明書チェーンを信頼するよう設定してください。

MCP サーバーとの TLS 接続を確立する際は、次の手順でクライアント証明書を検証してください。

  • リーフ証明書が提示され、その証明書チェーンが OpenAI Connectors mTLS 中間 CA につながることを確認します。
  • リーフ証明書がクライアント認証に有効であることを確認します。
  • リーフ証明書の SAN の dnsName が mtls.prod.connectors.openai.com であることを確認します。
  • リーフ証明書のフィンガープリントを固定しないでください。OpenAI は公開済みの CA チェーンを維持したまま、リーフ証明書をローテーションすることがあります。

MCP クライアントである ChatGPT の認証には mTLS を使用してください。エンドユーザーの認証とツールへのアクセスの認可には、引き続き OAuth 2.1 を使用します。

ID プロバイダーの選択

ほとんどの OAuth 2.1 ID プロバイダーは、ディスカバリドキュメントの公開、none または private_key_jwt を使った CIMD への対応、必要に応じた DCR への対応、および発行するトークンへの resource パラメーターの値の引き継ぎによって、MCP の認可要件を満たせます。クライアント登録に CIMD を利用できるプロバイダーを優先してください。

認証を自分で一から実装するよりも、実績のある既存の ID プロバイダーを利用することを強く推奨します。

代表的な ID プロバイダーの設定手順を以下に示します。

Auth0

Auth0 は、メタデータの検出、CIMD による登録、API の保護、自社およびサードパーティのツール呼び出しに必要なトークン交換を提供します。これにより、MCP クライアントは MCP サーバーに安全に接続できます。

ホスト型プロバイダーの例(Stytch)

トークン検証の実装

OAuth フローが完了すると、ChatGPT は取得したアクセストークンを以降の MCP リクエストに直接付与します(Authorization: Bearer …)。MCP サーバーでリクエストを受け取った時点では、トークンを信頼しないでください。署名の検証、発行者と対象者の照合、有効期限の確認、リプレイ攻撃への対策、スコープによるアクセス制御など、リソースサーバーに必要な検証をすべて実施する責任は、MCP サーバーの実装者にあります。

具体的には、次の処理を実装してください。

  • 認可サーバーが公開する署名検証鍵を取得し(通常は JWKS を使用)、トークンの署名と iss を検証します。
  • 有効期限が切れたトークンや、有効期間がまだ始まっていないトークンを拒否します(exp/nbf)。
  • トークンが自分のサーバー向けに発行されていること(aud または resource クレーム)と、必須に指定したスコープが含まれていることを確認します。
  • サーバー固有のポリシーを確認し、特定したユーザーの ID 情報をリクエストコンテキストに追加するか、401 と WWW-Authenticate チャレンジを返します。

検証に失敗した場合は、401 Unauthorized と、保護対象リソースのメタデータを指す WWW-Authenticate ヘッダーを返します。これにより、クライアントに OAuth フローの再実行を促します。

SDK のトークン検証機能

Python と TypeScript の MCP SDK にはヘルパーが用意されているため、トークン検証を一から組み立てる必要はありません。

複数アカウントへの対応

ユーザーは、個人用と仕事用など、複数のアカウントを同じプラグインに接続できます。OpenAI は選択された接続の認証情報を使って、各ツール呼び出しを該当するアカウントに振り分けます。複数アカウントの接続にプロフィールツールは必須ではありません。ユーザーが接続を見分けやすくし、再接続後も同じプロフィールとして識別できるようにするには、固定の ID と識別に役立つ表示用メタデータを返す、認証が必要なプロフィールツールを用意してください。

複数アカウントの利用方法

ユーザーはプラグインの設定ページからアカウントを追加で接続できます。モデルは接続済みのすべてのアカウントを利用でき、ツールを呼び出す際にユーザーのリクエストに応じて 1 つ以上の適切なアカウントを選択します。各ツール呼び出しには、選択したアカウントの認証情報と権限が適用されます。

アカウント識別の改善

OpenAI が接続済みのプロフィールを識別し、わかりやすいラベルを表示できるよう、次の設定を行ってください。

  • 認証が必要なプロフィールツールを用意し、リクエストの認証情報に対応するプロフィールを一意かつ継続的に識別できる不透明な ID を返します。これにより、OpenAI は再接続後も同じプロフィールを識別し、他のプロフィールと区別できます。id という名前のフィールドがこの目的に使えるのは、その値がこれらの条件を満たす場合だけです。
  • MCP メタデータでプロフィールツールを指定し、認証済みプロフィールの情報を取得するために呼び出すツールを OpenAI が特定できるようにします。

プロフィール情報が必要になると、OpenAI は実行時に指定されたツールを特定し、その接続の認証情報で呼び出します。レスポンスを検証してからプロフィールデータを使用します。プロフィールツールがなくてもユーザーはアカウントを接続できますが、アカウントのラベル表示、識別、重複検出の精度が低下する場合があります。プロフィールツールを宣言する場合は、有効な識別情報を返してください。無効なレスポンスが返ると、アカウントを接続できないことがあります。

固定のプロフィール識別子の定義

プロフィールは、リクエストの認証情報によって認証された主体を識別します。個別に接続できるプロフィールはサービス側で定義します。この仕様は、サービスの組織構造や認可モデルを規定するものではありません。

アプリ内で一意の不透明なプロフィール ID を返してください。トークンの更新や再接続後も、同じプロフィールには同じ ID を使い、異なるプロフィールには異なる ID を使う必要があります。OpenAI は ID の内容を解釈せず、値だけを比較します。

プロバイダーに既存の不変かつ不透明な ID があり、それだけでプロフィールを完全に識別できる場合は、その ID を使用してください。それ以外の場合は、不透明な ID を一度だけ割り当て、プロフィールとの対応関係を永続化し、以降のリクエストでも同じ ID を取得してください。内部の対応関係はサービス内で管理し、返す ID には氏名、メールアドレス、組織内の関係を埋め込まないでください。

id は次の要件を満たす必要があります。

  • 空でなく、空白以外の文字を含む文字列であること。プロバイダーの ID が数値の場合は文字列にシリアライズします。
  • トークン更新、再接続、スコープ拡張後も、同じプロフィールには同じ値を使用すること
  • アプリ経由で接続できる異なるプロフィールには、異なる値を使用すること
  • プロフィールのメールアドレス、氏名、表示ラベルが変わっても値を変更しないこと
  • プロフィールの削除後も、その値を別のプロフィールに再割り当てしないこと

ログイン、トークン、セッション、ツール呼び出しのたびに新しい ID を生成しないでください。メールアドレスや変更可能な名前は表示用メタデータに格納します。変更や再割り当てが可能なメールアドレスは、固定のプロフィール ID として使用できません。Google OIDC では、メールアドレスのクレームではなく、不変の sub を使用してください。Google のドキュメントによると、メールアドレスは変わることがありますが、sub は変わらず、再利用もされません。詳しくは Google の ID に関するドキュメントを参照してください。

統合を更新する際は、既存のプロフィール ID を維持してください。表示名の変更、トークンの新規発行、接続の新規作成を理由に、新しいプロフィール識別子を作成してはいけません。

プロフィールツールの実装と宣言

空の引数オブジェクトを受け取り、現在のプロフィールを返す、認証が必要な読み取り専用ツールを公開してください。ツール名は get_profile、whoami、または任意の名前にできます。実行時にはメタデータを使ってプロフィールツールを特定します。OpenAI がレスポンスを正しく利用できるよう、以下の識別要件を満たしてください。

  • リクエストの検証済み認証情報からプロフィールを特定します。
  • 操作は読み取り専用とし、通常の接続権限で利用できるようにします。
  • 現在のリクエストの認証情報に対応するプロフィールを 1 つだけ返します。
  • 呼び出し元にユーザー ID、メールアドレス、アカウントの指定を求めないでください。
  • 認証に失敗した場合は、仮の ID や別のアカウントのプロフィールを返さず、適切な認証エラーを返してください。

プロフィールのレスポンスは、次の JSON スキーマに準拠する必要があります。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S",
      "description": "Opaque profile identifier, unique within this app and unchanged across token refresh, reconnection, and display-metadata changes. Never reassigned to another profile."
    },
    "name": {
      "type": "string",
      "description": "Display name for the authenticated profile."
    },
    "email": {
      "type": "string",
      "description": "Email address for display; not used as the profile identity."
    },
    "nickname": {
      "type": "string",
      "description": "A useful label that helps users distinguish connected profiles."
    }
  },
  "required": ["id"],
  "additionalProperties": false
}

レスポンスには、空でなく、空白以外の文字を含む文字列の id が必須です。表示用フィールドは任意です。ツールのメタデータは、OpenAI にプロフィール情報の取得先を伝えます。レスポンスは、現在の認証情報に対応するプロフィールを識別します。

スキーマ検証で確認できるのは、レスポンスの構造とフィールドの型がプロフィールの処理に適しているかどうかです。ID の一意性と不変性、および認証情報に対応するプロフィールだけが返ることは、サービス側で保証してください。メタデータの宣言やスキーマ検証の成功だけでは、これらの動作は保証されません。

ユーザーがプロフィールを見分けられるよう、name、email、nickname のうち、利用できるものを含めてください。取得できない任意の値は省略し、架空の値や無関係な個人データを追加しないでください。プロフィールを見分ける手がかりになる説明は ID に埋め込まず、nickname に入れてください。

ツールに _meta["openai/profile"]: true を設定し、プロフィールのレスポンススキーマをそのツールの outputSchema として公開してください。このマーカーは、どのツールがプロフィール情報を提供するかを OpenAI に伝えます。マーカー自体が機能を有効にしたり、利用資格を付与したりすることはありません。マーカーが存在しない場合や値が false の場合、そのツールはこの仕組みではプロフィール情報の取得元に指定されません。文字列、数値、null はマーカーの値として無効です。

{
  "name": "get_profile",
  "description": "Return the profile represented by this request's authenticated credentials. The opaque id is unique within this app and remains unchanged across token refresh, reconnection, and display-metadata changes.",
  "inputSchema": {
    "type": "object",
    "properties": {},
    "additionalProperties": false
  },
  "outputSchema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "id": {
        "type": "string",
        "minLength": 1,
        "pattern": "\\S",
        "description": "Opaque profile identifier, unique within this app and unchanged across token refresh, reconnection, and display-metadata changes. Never reassigned to another profile."
      },
      "name": {
        "type": "string",
        "description": "Display name for the authenticated profile."
      },
      "email": {
        "type": "string",
        "description": "Email address for display; not used as the profile identity."
      },
      "nickname": {
        "type": "string",
        "description": "A useful label that helps users distinguish connected profiles."
      }
    },
    "required": ["id"],
    "additionalProperties": false
  },
  "annotations": {
    "readOnlyHint": true,
    "destructiveHint": false,
    "openWorldHint": false
  },
  "securitySchemes": [
    {
      "type": "oauth2",
      "scopes": []
    }
  ],
  "_meta": {
    "openai/profile": true
  }
}

プロフィールへのアクセスに OAuth スコープが必要な場合は、実際に使用するスコープを指定してください。宣言だけでは認証は行われません。サーバーで認証情報を検証し、権限に基づいてアクセスを制御する必要があります。トークン検証の実装とツールリファレンスを参照してください。

structuredContent にプロフィールを格納して返し、outputSchema に照らして検証できるようにします。互換性を保つため、同じプロフィールを JSON にシリアライズしたものもテキストコンテンツ項目に含めてください。

{
  "content": [
    {
      "type": "text",
      "text": "{\"id\":\"prf_8d7e4b19\",\"name\":\"Alex Chen\",\"email\":\"alex@example.com\",\"nickname\":\"Alex — Moonwaffle work\"}"
    }
  ],
  "structuredContent": {
    "id": "prf_8d7e4b19",
    "name": "Alex Chen",
    "email": "alex@example.com",
    "nickname": "Alex — Moonwaffle work"
  },
  "isError": false
}

プロフィールの各フィールドをトップレベルに配置した単一の JSON オブジェクトを使用してください。

既存のプロフィールツールがある場合は、ツール名を維持したままプロフィールのメタデータ宣言を追加し、標準形式のプロフィールレスポンスを返してください。既存のレスポンスの構造が異なる場合は、サーバー側で形式を変換するか、スキーマに準拠した小さなラッパーツールを公開してください。標準の統合方法では、どのアプリにも共通の宣言とレスポンス形式を使用します。

具体例:Moonwaffle のプロフィールの永続化

架空のサービス Moonwaffle で、Alex が 2 つのプロフィールを個別に接続できるとします。Moonwaffle はプロフィールごとに異なる不透明な ID を保存します。リクエストの認証情報から保存済みのプロフィールを 1 つ特定し、プロフィールツールがその既存の ID を返します。

保存済みプロフィールの例。 ラベルは変更できますが、識別子は変わりません。

Alex — Moonwaffle personal: prf_42a9c6e0
Alex — Moonwaffle work:     prf_8d7e4b19

この例の ID には、プロフィールのラベルや内部の対応関係は埋め込まれていません。プロフィールごとに一度だけ保存され、再接続、トークンの更新、メールアドレスや表示名の変更後も同じ ID が使用されます。

認証済みプロフィールからのレスポンスの生成。 次の JavaScript の例は、MCP SDK に組み込めるハンドラーの処理を示します。loadAuthenticatedProfile はアプリケーション側で実装する統合コードです。リクエストの認証情報を検証し、権限を適用して、対応するプロフィールの保存済み ID と表示用メタデータを取得します。requestContext はサーバー側のリクエスト処理から取得します。モデルが指定するツール引数ではありません。

async function getProfile(requestContext) {
  // Your auth/provider integration validates credentials and loads
  // the existing profile. Auth failures use normal MCP auth handling.
  const account = await loadAuthenticatedProfile(requestContext);
  const id = account.profileId;

  if (typeof id !== "string" || id.trim().length === 0) {
    return {
      isError: true,
      content: [{ type: "text", text: "Profile identity unavailable." }],
    };
  }

  // Return the persisted ID unchanged; do not generate an ID per call.
  const profile = {
    id,
    ...(typeof account.name === "string" ? { name: account.name } : {}),
    ...(typeof account.email === "string" ? { email: account.email } : {}),
    ...(typeof account.nickname === "string"
      ? { nickname: account.nickname }
      : {}),
  };

  return {
    isError: false,
    structuredContent: profile,
    content: [{ type: "text", text: JSON.stringify(profile) }],
  };
}

上記のメタデータ宣言と入出力スキーマを使って、このハンドラーを登録してください。loadAuthenticatedProfile は、同じプロフィールに対応する認証情報が渡された場合も再接続後も、同じ保存済みプロフィールを取得する必要があります。OAuth グラントやセッションごとに新しいプロフィール ID を作成してはいけません。他のすべてのツールでも、リクエストの認証情報に基づき、同じプロフィールの権限を適用する必要があります。

プロフィール識別の動作確認

テスト 期待される結果
Moonwaffle の仕事用プロフィールで繰り返し呼び出す prf_8d7e4b19 が毎回返る
トークン更新、再接続、またはスコープ拡張後に同じプロフィールを使用 prf_8d7e4b19
メールアドレスまたは表示ラベルの変更後に同じプロフィールを使用 prf_8d7e4b19 が返る。ラベルは変更可能
Moonwaffle の個人用プロフィールを使用 prf_42a9c6e0 が返り、仕事用プロフィールと区別できる
保存済みのプロフィール ID が存在しないか空白 エラーが返る。架空の識別子や別のプロフィールで代替しない

この識別の仕組みは、すべてのプロフィールで、将来の統合変更後も維持する必要があります。表示用メタデータやトークンの内容が変わった場合も、接続の作成や再接続などが発生した場合も、同じ保証を維持してください。

テストと展開

  • ローカルテスト: すばやく検証を繰り返せるよう、有効期間の短いトークンを発行する開発用テナントから始めます。
  • 内部テスト: 認証が機能したら、広く展開する前に、アクセスを信頼できるテスターに限定します。特定のツールだけ、または MCP サーバー全体でアカウント連携を必須にできます。
  • ローテーション: トークンの失効、更新、スコープ変更を考慮して設計してください。トークンが存在しない場合や、古くなって無効な場合は、未認証として扱い、対処方法がわかるエラーメッセージを返してください。
  • OAuth のデバッグ: MCP Inspector の認証設定を使い、リリース前に OAuth の各ステップを順に確認して、フローが失敗する箇所を特定してください。

認証を実装すると、ChatGPT と Codex のユーザーに、ユーザー固有のデータと書き込み操作を提供できます。

認証 UI の表示

ChatGPT が OAuth のアカウント連携 UI を表示するのは、MCP サーバーが OAuth を利用できる、または利用する必要があることを通知した場合だけです。

ツール単位の OAuth フローを開始するには、メタデータ(securitySchemes とリソースメタデータドキュメント)および _meta["mcp/www_authenticate"] を含む実行時エラーが必要です。両方がそろわないと、そのツールの連携 UI は表示されません。

  1. リソースメタデータを公開します。 MCP サーバーは、https://your-mcp.example.com/.well-known/oauth-protected-resource などの既知の URL で OAuth の構成を公開する必要があります。

  2. 各ツールの認証ポリシーを securitySchemes で記述します。 ツールごとに securitySchemes を宣言すると、どのツールに OAuth が必要で、どのツールを匿名で実行できるかを ChatGPT に伝えられます。サーバー全体で同じポリシーを使う場合でも、ツールごとに宣言してください。サーバー単位のデフォルトに依存すると、後から個々のツールを変更しにくくなります。

    現在利用できるスキームは 2 種類です。認証を任意にする場合は、複数のスキームを指定できます。

    • noauth:匿名で呼び出せるため、ChatGPT はすぐにツールを実行できます。
    • oauth2:ツールに OAuth 2.0 アクセストークンが必要です。同意画面に正確な情報が表示されるよう、要求するスコープを含めてください。

    配列自体を省略すると、ツールはサーバーが公開するデフォルトの設定を継承します。noauth と oauth2 の両方を宣言すると、匿名で呼び出しを開始でき、連携後は権限が必要な機能も利用できることを ChatGPT に伝えられます。クライアントへの通知内容にかかわらず、サーバーは呼び出しのたびにトークン、スコープ、対象者を検証する必要があります。

    TypeScript SDK の例(公開ツール、認証は任意)

    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { z } from "zod";
    
    const server = new McpServer({ name: "auth-example", version: "1.0.0" });
    
    server.registerTool(
      "search",
      {
        title: "Public Search",
        description: "Search public documents.",
        inputSchema: {
          q: z.string(),
        },
        outputSchema: {},
        _meta: {
          securitySchemes: [
            { type: "noauth" },
            { type: "oauth2", scopes: ["search.read"] },
          ],
        },
      },
      async ({ q }) => {
        return {
          content: [{ type: "text", text: `Results for ${q}` }],
          structuredContent: {},
        };
      }
    );

    TypeScript SDK の例(認証必須)

    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { z } from "zod";
    
    const server = new McpServer({ name: "auth-example", version: "1.0.0" });
    
    server.registerTool(
      "create_doc",
      {
        title: "Create Document",
        description: "Make a new doc in your account.",
        inputSchema: {
          title: z.string(),
        },
        outputSchema: {},
        _meta: {
          securitySchemes: [{ type: "oauth2", scopes: ["docs.write"] }],
        },
      },
      async ({ title }) => {
        return {
          content: [{ type: "text", text: `Created doc: ${title}` }],
          structuredContent: {},
        };
      }
    );
  3. ツールハンドラー内でトークンを確認し、_meta["mcp/www_authenticate"] を返します。 これにより、必要なときに ChatGPT に認証 UI を表示させることができます。トークンを調べ、発行者、対象者、有効期限、スコープを検証してください。有効なトークンがない場合は、_meta["mcp/www_authenticate"] を含むエラー結果を返し、その値に error と error_description の両方のパラメーターを含めてください。ステップ 1 と 2 の設定が済んでいれば、この WWW-Authenticate ペイロードを受け取った時点で、ツール単位の OAuth UI が表示されます。チャレンジによって再認可を求める場合、プロバイダーはそのフローでユーザーの既存のログイン状態を引き継ぐことができます。

    例

    {
      "jsonrpc": "2.0",
      "id": 4,
      "result": {
        "content": [
          {
            "type": "text",
            "text": "Authentication required: no access token provided."
          }
        ],
        "_meta": {
          "mcp/www_authenticate": [
            "'Bearer resource_metadata=\"https://your-mcp.example.com/.well-known/oauth-protected-resource\", error=\"insufficient_scope\", error_description=\"You need to login to continue\"'"
          ]
        },
        "isError": true
      }
    }