自前のMCP ServerをClaude.ai / ChatGPTのカスタムコネクタとして公開する
Claude.aiとChatGPTがMCPのカスタムコネクタに対応した。自分で運用しているMCP Serverを登録すると、チャット画面からコーディング不要で接続できる。
EDINET DB(edinetdb.jp)でこの対応をやったので、実装の全体像と判断ポイントを書く。
前提: Claude.ai / ChatGPT の MCP Connector は何をしているか
ユーザーがSettings → ConnectorsにURLを入力すると、クライアント(Claude.ai / ChatGPT)は以下の流れを踏む。
1. GET /.well-known/oauth-protected-resource
→ MCP Serverが保護リソースであることを確認、AS(Authorization Server)のURLを取得
2. GET /.well-known/oauth-authorization-server
→ authorize / token / registration エンドポイントを取得
3. POST /oauth/register (DCR)
→ client_id と client_secret を動的に取得
4. GET /oauth/authorize?response_type=code&client_id=...&code_challenge=...&code_challenge_method=S256
→ ユーザーを認証画面にリダイレクト
5. POST /oauth/token
→ 認可コード + PKCE verifier → access_token取得
6. POST /mcp (Authorization: Bearer <access_token>)
→ MCP Streamable HTTPでツール呼び出し
OAuth 2.1 + PKCE (S256) が必須。plain は許容されない。
実装すべきエンドポイント
1. Protected Resource Metadata
RFC 9728。MCP Serverが「認証が必要」であることをクライアントに伝える最初の接点。
# /.well-known/oauth-protected-resource
{
"resource": "https://edinetdb.jp/mcp",
"authorization_servers": ["https://edinetdb.jp"],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://edinetdb.jp/docs/mcp-guide"
}
MCP Server自体が401を返すとき、WWW-AuthenticateヘッダにこのメタデータのURLを含める。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://edinetdb.jp/.well-known/oauth-protected-resource"
Claude.aiはこの401をトリガーにOAuthフローを開始する。
2. Authorization Server Metadata
RFC 8414。クライアントがOAuthの各エンドポイントを発見するための標準メタデータ。
# /.well-known/oauth-authorization-server
{
"issuer": "https://edinetdb.jp",
"authorization_endpoint": "https://edinetdb.jp/oauth/authorize",
"token_endpoint": "https://edinetdb.jp/oauth/token",
"registration_endpoint": "https://edinetdb.jp/oauth/register",
"scopes_supported": ["mcp"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["client_secret_post", "none"],
"code_challenge_methods_supported": ["S256"],
"service_documentation": "https://edinetdb.jp/docs/mcp-guide"
}
token_endpoint_auth_methods_supportedに"none"を含める。Claude.aiはpublic client(client_secretなし)として接続してくるケースがある。
3. Dynamic Client Registration (DCR)
RFC 7591。クライアントが自分自身を登録するエンドポイント。
# POST /oauth/register
# Request:
{
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"client_name": "Claude"
}
# Response:
{
"client_id": "60154f73-d646-47e0-b170-e698e7ef18d2",
"client_secret": "a1b2c3d4...",
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_post",
"client_id_issued_at": 1740328897,
"client_secret_expires_at": 0
}
client_idはUUID v4、client_secretは64文字のhex。secretはSHA-256でハッシュ化してからDBに保存する。
注意点として、Claude.aiはユーザーごとに(あるいはセッションごとに)DCRを叩く場合がある。同じredirect_uriで複数のclient_idが登録されるのは正常な挙動。
4. Authorize エンドポイント
ここが自前のMCP Serverの設計判断が一番分かれるところ。
EDINET DBの場合、Google OAuth(OpenID Connect)をIdPとして使っている。/oauth/authorizeに来たリクエストを、そのままGoogle OAuth認証画面にリダイレクトする。
Claude.ai → /oauth/authorize?client_id=...&code_challenge=...&redirect_uri=...
→ Google OAuth認証画面
→ /oauth/callback (Googleからのリダイレクト)
→ redirect_uri?code=<signed_blob> (Claude.aiに認可コードを返す)
認可コードはHMACで署名したJSONブロブにした。DBに認可コードを保存しない。
payload = {
"sub": google_sub, # Googleユーザーの安定ID
"email": email,
"cid": client_id,
"ru": redirect_uri,
"cc": code_challenge, # PKCE
"ccm": "S256",
"ts": int(time.time()), # タイムスタンプ
}
blob = json.dumps(payload)
signature = hmac.new(SECRET_KEY, blob.encode(), hashlib.sha256).hexdigest()[:32]
auth_code = base64url_encode(json.dumps({"p": blob, "s": signature}))
Cloud Runのような水平スケーリング環境では、インスタンス間で状態を共有する仕組み(Redis等)を持たないことが多い。HMACブロブなら任意のインスタンスで検証できる。
TTLは300秒。タイムスタンプを検証して期限切れの認可コードを拒否する。
5. Token エンドポイント
認可コードをaccess_tokenに交換する。
# POST /oauth/token
# Request:
{
"grant_type": "authorization_code",
"code": "<signed_blob>",
"client_id": "<uuid>",
"code_verifier": "<pkce_verifier>",
"redirect_uri": "https://claude.ai/api/mcp/auth_callback"
}
# Response:
{
"access_token": "edb_...",
"token_type": "Bearer",
"scope": "mcp",
"expires_in": 315360000
}
EDINET DBの設計判断として、access_tokenそのものがAPIキーになっている。別途token管理テーブルを持たない。
理由は3つ。
- APIキー認証は既に運用中で、認証・認可・レート制限のインフラが整っている
- refresh_tokenを発行する必要がない(APIキーは長期有効)
- OAuthフロー以外の接続方法(Claude Code、Cursor等)と同じAPIキーを使える
PKCE検証はここで行う。
verifier_hash = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip("=")
if not hmac.compare_digest(verifier_hash, stored_code_challenge):
return error("invalid_grant")
hmac.compare_digestで定時間比較する。==で比較するとタイミング攻撃の余地が生まれる。
Claude.ai 固有の注意点
redirect_uri
https://claude.ai/api/mcp/auth_callback
https://claude.com/api/mcp/auth_callback
両方のドメインがある。事前登録する場合は両方をカバーする。DCR経由なら都度登録されるので問題ない。
public client
Claude.aiはclient_secretなしで/oauth/tokenを叩いてくることがある。token_endpoint_auth_methodsに"none"を入れておく必要がある。
client_secretがない場合でもclient_idの存在確認は行い、PKCEの検証は必須のまま。セキュリティはPKCEで担保する設計。
セッション
Claude.aiはMCPのセッションをConnectorごとに保持している。一度接続が確立すると、access_tokenがConnector設定に保存される。ユーザーがConnectorを削除するまで有効。
ChatGPT 固有の注意点
redirect_uri
https://chatgpt.com/connector_platform_oauth_redirect
https://platform.openai.com/apps-manage/oauth
Claude.aiと異なるパターン。
DCR
ChatGPTはDCRを使ってclient_idを自動登録する。事前にoauth_clientsテーブルに登録しておくことも可能だが、DCR対応している方が確実。
Streamable HTTP Transport
MCP Serverのtransport。/mcpエンドポイントにPOSTでJSON-RPCリクエストを送る。
POST /mcp HTTP/1.1
Authorization: Bearer edb_...
Content-Type: application/json
{"jsonrpc":"2.0","method":"tools/list","id":1}
レスポンスはJSON-RPCのレスポンスをそのまま返す。SSE(Server-Sent Events)でストリーミングすることも可能だが、EDINET DBはJSONレスポンスのみ(財務データは一発で返せるサイズなので、ストリーミングの利点が薄い)。
MCP Server を公開するときのチェックリスト
実装を終えてから振り返ったチェックリスト。
-
/.well-known/oauth-protected-resourceが正しいJSONを返す -
/.well-known/oauth-authorization-serverが全エンドポイントURLを返す -
code_challenge_methods_supportedに"S256"が含まれる -
token_endpoint_auth_methods_supportedに"none"が含まれる(public client対応) -
DCR(
/oauth/register)が任意のredirect_uriを受け入れて、client_id/secretを返す - client_secretはハッシュ化して保存
- 認可コードのTTLが5分以内
- PKCE S256の検証に定時間比較を使っている
-
/oauth/tokenがform-encodedとJSON両方を受け入れる - access_tokenがMCPエンドポイントで使える
-
/mcpの401レスポンスにWWW-Authenticateヘッダがある - HTTPSが有効(OAuth 2.1の必須要件)
まとめ
MCP Serverのカスタムコネクタ対応は、要素技術としてはOAuth 2.1の標準的な実装。ただし、Claude.ai / ChatGPTそれぞれのクライアント挙動の違い(redirect_uriのパターン、public client対応の要否、DCRの使い方)を押さえる必要がある。
HMACブロブによるstateless設計はCloud Run環境で効果的に機能した。認可コード・セッション・stateパラメータの全てをDBなしで管理できるので、水平スケーリングとの相性がいい。
EDINET DB: https://edinetdb.jp
接続手順(Claude.ai): https://edinetdb.jp/docs/connect-claude
接続手順(ChatGPT): https://edinetdb.jp/docs/connect-chatgpt
MCP接続ガイド: https://edinetdb.jp/docs/mcp-guide
Discussion