ChatGPTのMCPサーバーをOAuth認証で作成する方法
概要
この記事ではOAuth認証付きのMCPサーバーの作成方法について簡単に説明します。
ChatGPTを参考に解説していきますが多分Claudeでも動くでしょう。
必要な手順は以下になります。
・oauth-protected-resource.json
・oauth-authorization-server.jsonとopenid-configuration.json
・/registerエンドポイント(クライアントの自動登録)
・/authorizeページ(ログインページ)と/api/authorizeエンドポイント
・/tokenとrefresh_token
それでは解説していきます。
oauth-protected-resource.json
ChatGPTはMCPサーバーにどのようなOAuth認証のAPIのエンドポイントがあるのかわかりません。それを知らせるためのファイルがこのoauth-protected-resource.jsonファイルになります。自分のMCPサーバーのドメインがhttps://xxx.your-service.com だとして、このファイルをhttps://xxx.your-service.com/.well-known/oauth-protected-resource というエンドポイントに置いておくとMCPクライアント(ChatGPTやClaude)がこのファイルにアクセスし必要な情報を読み取ってくれます。MCPのリソースサーバーのドメインがxxx.your-service.com、認証サーバーのドメインがauth.your-domain.comだとするとファイルの中身はこんな感じです。
{
"resource": "https://xxx.your-domain.com",
"authorization_servers": [ "https://auth.your-domain.com" ],
"bearer_methods_supported": [ "header" ],
"scopes_supported": [
"openid",
"profile",
"offline_access",
"project"
],
"resource_documentation": "https://xxx.your-domain.com/oauth_document"
}
oauth-authorization-server.jsonとopenid-configuration.json
次はOAuth認証を行う認証サーバーにファイルを置きます。
(※もしかしたらどちらか片方だけでもいけるかも?未確認)
認証サーバーのドメインがauth.your-domain.com だとするとそれぞれのファイルは以下のようになります。
https://auth.your-domain.com/.well-known/oauth-authorization-server
{
"issuer": "https://auth.your-domain.com/",
"authorization_endpoint": "https://auth.your-domain.com/authorize",
"token_endpoint": "https://auth.your-domain.com/token",
"registration_endpoint": "https://auth.your-domain.com/register",
"jwks_uri": "https://auth.your-domain.com/jwks.json",
"token_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" ],
"token_endpoint_auth_signing_alg_values_supported": [ "HS256", "RS256" ],
"userinfo_endpoint": "https://auth.your-domain.com/user/info",
"scopes_supported": [
"openid",
"profile",
"offline_access",
"project"
],
"response_types_supported": [
"code"
],
"service_documentation": "http://xxx.your-domain.com/oauth_document",
"ui_locales_supported": [ "en-US", "ja-JP" ]
}
https://auth.your-domain.com/.well-known/openid-configuration
{
"issuer": "https://auth.your-domain.com/",
"authorization_endpoint": "https://auth.your-domain.com/authorize",
"token_endpoint": "https://auth.your-domain.com/token",
"registration_endpoint": "https://auth.your-domain.com/register",
"jwks_uri": "https://auth.your-domain.com/jwks.json",
"scopes_supported": [
"openid",
"profile",
"offline_access",
"project"
],
"response_types_supported": [
"code"
],
"code_challenge_methods_supported": [ "S256" ],
"response_modes_supported": [ "query", "fragment", "form_post" ],
"subject_types_supported": [ "public" ],
"id_token_signing_alg_values_supported": [ "HS256", "RS256" ],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"claims_supported": [
"aud",
"auth_time",
"created_at",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"identities",
"iss",
"name",
"nickname",
"phone_number",
"picture",
"sub"
],
"request_uri_parameter_supported": false
}
/registerエンドポイント
今までのOAuth認証のクライアントの作成ではOAuthサーバー(Googleカレンダー、Microsoft365など)のWEBページでアプリの登録が事前に必要でした。OAuthサーバーはプラットフォーム企業(GoogleやMicrosoftなど)でアプリ開発者は小さい企業や個人という関係でした。この関係であればアプリ開発者がGoogleのページでアプリ登録をしてそこで得たclient_idとclient_secretをアプリに設定して作業するという流れは自然です。
しかし今回のMCPの仕組みではプラットフォームとアプリ開発者の関係が逆になっています。MCPサーバーが多数作られていったとして、OpenAIの社員がFigmaなどの大きな企業のみならず、小さな企業のMCPサーバーのWEBページでChatGPTのアプリを登録しそのclient_idなどをChatGPTで使うようにする形は非現実的です。
そこでDynamic Client Registrationという仕組みが新たにOAuth認証に導入されました。
RFC7591
https://datatracker.ietf.org/doc/html/rfc7591
これによってChatGPTは/registerエンドポイントにPOSTリクエストを送るだけでclient_idなどの情報を取得できるようになります。
ChatGPTの場合、以下のようなリクエストのBodyが/registerエンドポイントに送信されてきます。
{
"client_name":"ChatGPT",
"redirect_uris":[
"https://chatgpt.com/connector_platform_oauth_redirect"
],
"grant_types":[
"authorization_code",
"refresh_token"
],
"response_types":[
"code"
],
"token_endpoint_auth_method":"client_secret_post"
}
これに対してMCPサーバーからは以下のようなレスポンスを返します。
{
"client_id": "xxx",
"client_secret": "xxx",
"client_id_issued_at": 12345, --UnixTime
"client_secret_expires_at": 123456, --UnixTime
"redirect_uris": [
"https://chatgpt.com/connector_platform_oauth_redirect"
],
"grant_types": ["authorization_code", "refresh_token"],
"client_name": "ChatGPT",
"token_endpoint_auth_method": "client_secret_basic",
"logo_uri": "https://xxx.your-domain.com/logo.png",
"jwks_uri": "https://xxx.your-domain.com/my_public_keys.jwks",
"example_extension_parameter": "value1"
}
MCPサーバー側は動的に生成したclient_idなどの情報をDBに保存する処理を作り、上記のレスポンスを返す感じのコードを作ることになります。
コードのイメージ
private static async ValueTask OAuthRegister(HigLaboAppHttpContext context)
{
var r = await context.CreateFromBody<DynamicClientRegistration>();
var now = DateTimeOffset.Now;
var sp = new OAuthClient_Add();
sp.ClientId = Guid.NewGuid();
sp.DisplayName = r.client_name;
sp.CreateTime = now;
sp.RequestBody = await context.Request.GetRequestBodyTextAsync();
sp.ClientSecret = HigLaboAppSecurity.CreateRandomString(128);
await sp.ExecuteNonQueryAsync();
r.client_id = sp.ClientId.ToString()!;
r.client_secret = sp.ClientSecret;
r.client_id_issued_at = now.ToUnixTimeSeconds();
r.client_secret_expires_at = now.AddDays(100).ToUnixTimeSeconds();
r.grant_types.Clear();
r.grant_types.Add("authorization_code");
r.grant_types.Add("refresh_token");
await context.Response.WriteAsJsonAsync(r);
}
/authorizeページ(ログインページ)と/api/authorizeエンドポイント
次はログインのページを作ります。上記の各種設定ファイルの構成と/registerエンドポイントまで正しく作れている場合、ChatGPT上でMCPサーバーの設定をするとこの/authorizeページに遷移します。
まずはChatGPTでMCPサーバーの設定を行います。まだベータ版扱いなので開発者モードにしてから設定する必要があります。(※2025/10/18現在)

作成するボタンを押すとブラウザで/authorizeページが表示されます。以下のようなURLになります。
/authorize?response_type=code&client_id=xxx&redirect_uri=https%3A%2F%2Fchatgpt.com%2Fconnector_platform_oauth_redirect&state=oauth_s_68f014b79c6881919164d1a86bbca75a&resource=https%3A%2F%2Fxxx.your-domain.com
このページは基本的にはログインID(メールアドレス)とパスワードとログインボタンがある認証画面になると思います。ログインボタンを押したときに/api/authorizeエンドポイントが呼び出され、メールアドレスとパスワードの認証が合っていればChatGPTから指定されたURLへリダイレクトをする形になります。
リダイレクトのURLは以下のようになります。
Redirect URL: https://chatgpt.com/connector_platform_oauth_redirect?code=xxx&state=yyy
認証が成功したときにcodeの値はこちらで生成してUserIdと紐づけてDBなどに保存しておく必要があります。リダイレクトしてすぐに、ChatGPTは/tokenエンドポイントにこのcodeの値付きでPOSTリクエストを送ってきます。この時のリクエストはJSON形式ではなく、application/x-www-form-urlencoded形式で送られてきます。
/tokenエンドポイントに送られてくるBodyは以下のような形になります。
grant_type=authorization_code&code=xxx&redirect_uri=https://chatgpt.com/connector_platform_oauth_redirect&client_id=some_client_id&client_secret=some_client_secret_value&resource=https://xxx.your-domain.com
Bodyからgrant_typeの値がauthorization_codeであることを確認します。その後にclient_id、client_secret、codeの値を取り出し、DBに保存しておいたclient_id、client_secret、codeの値からUserIdを特定しアクセストークンをレスポンスとして返します。
{
access_token = "some_access_token",
token_type ="Bearer",
expires_in = 3600,
refresh_token = "some_refresh_token",
scope = "offline_access profile"
}
これで認証が完了しMCPサーバーとして無事に登録が完了するはずです。
OAuth認証の仕様上、しばらく経つとaccess_tokenの期限が切れます。その時は/tokenエンドポイントにgrant_type=refresh_tokenのリクエストが飛んできます。フォーマットはapplication/x-www-form-urlencodedです。
grant_type=refresh_token&refresh_token=xxx&client_id=some_client_id&client_secret=some_client_secret_value&resource=https://xxx.your-domain.com
パラメーターにrefresh_tokenが含まれているのでそれを使って新しいaccess_tokenを発行しレスポンスとして返します。
ASP.NET Coreの場合、サーバーのコードはこんな感じになるでしょう。
private static async ValueTask OAuthToken(HigLaboAppHttpContext context)
{
var ff = context.Request.CreateDictionaryFromRequestFormAsync();
var grantType = ff.GetValueOrDefault("grant_type");
var code = ff.GetValueOrDefault("code");
var client_id = ff.GetValueOrDefault("client_id");
var client_secret = ff.GetValueOrDefault("client_secret");
if (grantType == "authorization_code")
{
var rToken = await DB.OAuthToken_Get_By_Code_OrNull(code);
if (rToken == null)
{
throw new HigLaboAppException("Invalid code");
}
if (client_secret != null)
{
var rClient = await DB.OAuthClient_Get_By_ClientId_ClientSecret_OrNull(rToken.ClientId, client_secret);
if (rClient == null)
{
throw new HigLaboAppException("Invalid client_secret");
}
}
else
{
throw new HigLaboAppException("client_secret required.");
}
await context.Response.WriteAsJsonAsync(new
{
access_token = rToken.AccessToken,
token_type = "Bearer",
expires_in = 3600,
refresh_token = rToken.RefreshToken,
scope = string.Join(' ', rToken.ScopeList),
});
}
else if (grantType == "refresh_token")
{
var refresh_token = ff.GetValueOrDefault("refresh_token");
var rToken = await DB.OAuthToken_Get_By_RefreshToken_OrNull(refresh_token);
if (rToken == null)
{
throw new HigLaboAppException("Invalid refresh_token");
}
await context.Response.WriteAsJsonAsync(new
{
access_token = rToken.AccessToken,
token_type = "Bearer",
expires_in = 3600,
refresh_token = rToken.RefreshToken,
scope = string.Join(' ', rToken.ScopeList),
});
}
else
{
throw new HigLaboAppException("Invalid grant_type." + grantType);
}
}
これで認証は完了です。ChatGPTで会話していて自分の作ったツールが使われた場合、以下のようなリクエストが送られてきます。
BodyはJSON形式で以下のようになります。
{
"method":"tools/call",
"params":{
"_meta":{
"openai/userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"openai/locale":"ja-JP",
"openai/userLocation":{
"city":"Kodaira",
"region":"Tokyo",
"country":"JP",
"timezone":"Asia/Tokyo",
"latitude":"35.72603",
"longitude":"139.48508"
},
"openai/subject":"v1/oHBmOflqrwxZrURnT9JVlM1mNecpeWJXMEbXoUUtJmHs"
},
"name":"get_todays_weather",
"arguments":{
"city":"福岡"
}
},
"jsonrpc":"2.0",
"id":1
}
リクエストのヘッダーにはアクセストークンがセットされています。
Authorization: Bearer access_token_value_xxxxxxxxxx
この値を使ってユーザーを特定できます。
MCPサーバーの処理を実行して結果をChatGPTにレスポンスとして返すという感じで実装すれば様々な機能をChatGPTから呼べるようになります。
下記の記事でASP.NET CoreでMCPサーバーの各処理の作り方を解説しています。
https://www.higlabo.ai/blog/higty-tech/aspnet-mcp-server-from-openai
参考記事
https://platform.openai.com/docs/guides/tools-connectors-mcp