リモートMCPサーバーをインターネットや社内ネットワークへ公開する場合、誰でもtools/callを実行できる状態にはしたくありません。そこで利用できるのがOAuthです。
MCPでは、Streamable HTTPで公開するMCPサーバーをOAuthのResource Serverとして保護できます。ここで重要なのは、MCPサーバー自身がユーザーのパスワードを受け取り、ログイン画面を表示し、アクセストークンを発行する必要はないという点です。
現在のMCP Python SDKでも、Streamable HTTPのMCPサーバーはOAuth 2.1 Resource Serverとして動作し、クライアントから送られてきたBearer Tokenを検証する役割として説明されています。認可サーバーがユーザーをログインさせてTokenを発行し、MCPクライアントが取得したTokenをAuthorization: Bearer ...としてMCPサーバーへ送信します。概念的には次の構成です。
ユーザー ↓ MCP Client ↓ Authorization Server ↓ Access Tokenを取得 ↓ Authorization: Bearer <access_token> ↓ MCP Server ↓ Tokenを検証 ↓ tools/call
ただし、単に「JWTの署名が正しい」ことを確認するだけでは十分ではありません。そのTokenが本当にこのMCPサーバー向けに発行されたものなのか、必要なScopeを持っているのか、有効期限が切れていないのかを確認する必要があります。
この記事では、MCPでOAuth認証を実装する基本構成と、アクセストークンを安全に扱うためのAudience、Resource、Scope、Token保存、ログ、Refresh Token、外部API連携の考え方を解説します。OAuthの認可フロー全体やClient ID Metadata Documentの形式、Token Passthrough禁止といった基礎はClaude Code + MCP セキュリティ設計完全ガイドで解説しているため、この記事ではアクセストークンをどう安全に実装・運用するかにしぼります。HTTPSやReverse Proxyなどリモート公開のインフラ面はMCPサーバーをリモート公開する方法を参照してください。
- MCPサーバーはOAuthのResource Serverになる
- OAuth認証はstdioではなくStreamable HTTPで利用する
- MCPクライアントはBearer Tokenを送信する
- まずTokenVerifierを実装する
- TokenVerifierとAuthSettingsはセットで設定する
- Protected Resource MetadataはSDKが公開できる
- JWTなら署名だけでなくissを確認する
- 最も重要なのはAudienceを確認すること
- Auth0やEntra独自のAudienceを使う場合
- Scopeで「何ができるか」を制限する
- ToolごとにScopeを確認する
- 認証と認可は別にする
- Tokenがない場合はTool Errorではなく401を返す
- Access Tokenをデータベースへ無条件に保存しない
- Access Tokenをログへ出さない
- Access TokenをTool Resultへ返さない
- Refresh TokenをMCP Serverへ送らない
- Client側でTokenを保存するなら平文ファイルを避ける
- Access Tokenは短命にする
- MCPのTokenを外部APIへそのまま転送しない
- 外部サービス用OAuth Tokenは別に管理する
- 外部OAuthにはURL Mode Elicitationも利用できる
- Protected Resource MetadataとAuthorization Server Metadataを分ける
- 401と403を使い分ける
- 最初からAdmin Scopeを全部付与しない
- Token検証結果をRequest単位で扱う
- 2026-07-28ではIssuer検証がより重要になった
- Dynamic Client Registrationは非推奨になった
- OAuth SecretをMCP Toolの引数にしない
- Tokenを.envに固定するだけでは本番OAuthにならない
- Tokenの失効も考える
- OAuthを導入してもHTTPSは必須と考える
- Access Tokenの安全な扱いは「LLMから遠ざける」が基本
- MCPでOAuth認証を実装する際によくある質問
- MCPのOAuthはToken発行よりToken検証から考える
MCPサーバーはOAuthのResource Serverになる
OAuthでは、認証に関係する役割が複数あります。MCP Server側から見ると重要なのは、
Authorization Server MCP Client Resource Server
の関係です。Authorization Serverは、ユーザーを認証してAccess Tokenを発行します。MCP ClientはTokenを取得します。MCP ServerはTokenを受け取り、検証します。
MCP Python SDKの現行ドキュメントでも、MCPサーバーはResource Serverであり、ユーザーをログインさせたりTokenを発行したりせず、各HTTP RequestのAuthorization Headerを検証する役割とされています。つまりMCPサーバーへ/login・/password・ユーザーDB・Token発行処理をすべて実装する必要はありません。既存のAuth0、Microsoft Entra ID、Keycloak、社内IdP、独自OAuth Authorization Serverなどを利用できます。
OAuth認証はstdioではなくStreamable HTTPで利用する
MCPのOAuth認証はHTTPレイヤーで行われます。ClientからはAuthorization: Bearer eyJ...のようなHTTP Headerが送られます。そのためOAuth認証を利用するのは基本的にStreamable HTTPです。stdioとStreamable HTTPの違い自体はMCPのstdioとStreamable HTTPの違いで解説しています。
MCP Python SDKでも、AuthorizationはHTTP Transportに存在する仕組みであり、stdioではAuthorization Headerが存在しないためtoken_verifierは呼び出されないと説明されています。ローカルstdio MCP Serverなら、OSユーザー・Process・Filesystem Permission・Claude CodeのPermission・SandboxなどがSecurity Boundaryになります。一方、Remote MCPではOAuthなどのHTTP認証が必要になります。
MCPクライアントはBearer Tokenを送信する
認証済みMCP ClientはRequestごとにAccess Tokenを送信します。概念的には次の形です。
POST /mcp HTTP/1.1 Host: mcp.example.com Authorization: Bearer eyJhbGciOiJSUzI1NiIs... Content-Type: application/json
MCP ServerはこのTokenを検証してからJSON-RPC Requestを処理します。Tokenが存在しない、無効、期限切れなどの場合は、HTTP/1.1 401 Unauthorizedを返します。MCP Python SDKでは、認証なしまたはToken Verifierが無効と判断したRequestはMCP Requestを解析する前に拒否され、WWW-Authenticate HeaderからProtected Resource Metadataへ誘導されます。つまりtools/callの内部で「ログインしてください」と返すより前に、HTTPレイヤーで認証を処理します。
まずTokenVerifierを実装する
MCP Python SDKでは、受け取ったAccess Tokenが有効かどうかをTokenVerifierで判断できます。基本構造は次のようになります。
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
RESOURCE = "https://mcp.example.com/mcp"
class MyTokenVerifier(TokenVerifier):
async def verify_token(
self,
token: str
) -> AccessToken | None:
result = await verify_access_token(token)
if not result.valid:
return None
return AccessToken(
token=token,
client_id=result.client_id,
scopes=result.scopes,
resource=result.resource,
)
mcp = MCPServer(
"Example MCP",
token_verifier=MyTokenVerifier(),
auth=AuthSettings(
issuer_url=AnyHttpUrl(
"https://auth.example.com"
),
resource_server_url=AnyHttpUrl(
RESOURCE
),
required_scopes=[
"mcp:read"
],
validate_token_resource=True,
),
)
TokenVerifierはAuthorization Headerから取り出された生のTokenを受け取り、有効ならAccessToken、無効ならNoneを返します。本番実装では、JWTの署名を検証するか、Authorization ServerのToken Introspection Endpointへ問い合わせる形が一般的です。MCP Python SDKも、実際のVerifierではJWT Signatureを検証するかRFC 7662のToken Introspectionを利用する構成を案内しています。
TokenVerifierとAuthSettingsはセットで設定する
Python SDKでは、token_verifier=MyTokenVerifier()だけ設定すればよいわけではありません。AuthSettingsも設定します。現在のMCP Python SDKではtoken_verifierとauthはセットで利用する必要があり、一方だけを渡すとServerがRequestを処理する前にエラーになります。
issuer_urlにはTokenを発行するAuthorization Serverを指定します。resource_server_urlにはClientが実際に接続するMCP Serverの公開URLを指定します。ここをhttp://127.0.0.1:8000/mcpのような内部URLにしたまま本番へDeployしないよう注意してください。公開URLがhttps://mcp.example.com/mcpなら、resource_server_urlも同じPublic URLにします。
Protected Resource MetadataはSDKが公開できる
MCP Clientは最初から「どのAuthorization ServerからTokenを取ればよいか」を知っているとは限りません。そこでMCP ServerはProtected Resource Metadataを公開します。MCP Python SDKでAuthSettingsを設定すると、たとえば次のようなEndpointが自動的に用意されます。
/.well-known/oauth-protected-resource/mcp
ここから概念的には次の情報を取得できます。
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": [
"https://auth.example.com/"
],
"scopes_supported": [
"mcp:read"
],
"bearer_methods_supported": [
"header"
]
}
現在のPython SDKではRFC 9728 Protected Resource MetadataをAuthSettingsから生成でき、認証されていないRequestにはWWW-Authenticate HeaderからMetadata URLが通知されます。その結果、MCP Clientでは概念的に次のFlowを構築できます。
/mcpへRequest ↓ 401 Unauthorized ↓ WWW-Authenticateを確認 ↓ Protected Resource Metadataを取得 ↓ Authorization Serverを発見 ↓ OAuth認証 ↓ Access Token取得 ↓ /mcpへ再Request
JWTなら署名だけでなくissを確認する
JWT Access Tokenを使っている場合、「Signatureが正しい」だけで認証を完了させてはいけません。少なくともiss・aud・exp・scopeなどを確認する必要があります。
issはTokenを発行したIssuerを示します。自分が信頼しているAuthorization Server以外が発行したTokenを受け付けないようにします。2026-07-28 MCP仕様ではAuthorization周辺がさらに強化され、Authorization ServerがRFC 9207のissを返し、Client側でもIssuerを検証することが求められるようになりました。Authorization Server間でCredentialを使い回さないための変更も導入されています。
OAuth実装では「Tokenが暗号学的に正しい」だけではなく、「信頼しているIssuerが発行したToken」であることを確認します。
最も重要なのはAudienceを確認すること
OAuth実装で特に重要なのがAudienceです。たとえば同じAuthorization Serverが、
向けのAccess Tokenを発行しているとします。署名はすべて同じIssuerによって行われる可能性があります。そのため「署名が正しい」だけでは「MCP Server向けのToken」とは限りません。別API向けTokenをMCP Serverでも利用できてしまう状態は避ける必要があります。
Python SDKでは、validate_token_resource=Trueを設定すると、AccessToken.resourceとresource_server_urlを比較し、このMCP Server向けではないTokenを拒否できます。MCP Clientが接続しているPublic URLとresource_server_urlを一致させることも重要です。
Auth0やEntra独自のAudienceを使う場合
Authorization Serverによっては、MCP URLそのものではなく独自のAudience Identifierを利用します。たとえばAuth0ならhttps://example-apiのようなAPI Identifierを使うことがあります。Microsoft Entra IDならApplication IDなどがAudienceとして使われる構成があります。
この場合、validate_token_resource=Trueによる単純比較ではなく、自分のTokenVerifierでJWTのaudを確認します。現在のPython SDKでも、Authorization Server独自のAudience Identifierを使う場合はvalidate_token_resourceに任せるのではなく、Verifier側でaudを検証し、このServer向けではないTokenならNoneを返す方式が案内されています。重要なのは、Audience Validationそのものを省略しないことです。
Scopeで「何ができるか」を制限する
Audienceが「どのServer向けのTokenか」を表すのに対し、Scopeは「そのServerで何ができるか」を表します。たとえばnotes:read・notes:write・notes:deleteのように分離できます。読み取りだけ許可されたTokenならnotes:readだけを持たせます。
MCP Python SDKのAuthSettingsではrequired_scopes=["notes:read"]を指定できます。この場合、必要なScopeを持っていないTokenを拒否できます。ただしServer全体へ同じScopeを要求するだけでは、細かなTool Authorizationには足りない場合があります。
ToolごとにScopeを確認する
たとえば次のToolがあるとします。
list_notes create_note delete_note
list_notesはnotes:readで利用できるようにします。create_noteはnotes:writeを要求します。delete_noteはnotes:deleteを要求します。MCP Python SDKではHandler内部から現在のRequestの認証情報を取得できます。
from mcp.server.auth.middleware.auth_context import (
get_access_token,
)
@mcp.tool()
def list_notes() -> list[str]:
token = get_access_token()
if token is None:
raise PermissionError(
"authentication required"
)
if "notes:read" not in token.scopes:
raise PermissionError(
"notes:read scope required"
)
return [
"note-a",
"note-b",
]
get_access_token()は、そのRequestでToken Verifierが返したAccessTokenを取得します。Client ID、Scope、Subjectなどを利用してTool単位のAuthorizationを実装できます。
認証と認可は別にする
OAuthを導入すると、「Tokenが有効」だけで安心してしまうことがあります。しかしToken Validationは主に認証の入口です。実際のTool実行では、「このユーザーがこのResourceにこの操作をしてよいか」も確認する必要があります。
たとえばuser_id=100、tenant_id=Aのユーザーが、tenant_id=Bの顧客情報を取得してはいけません。これはOAuth Tokenの署名検証だけでは防げません。Tool内部でもSubject・Tenant・Role・Scope・Resource Ownerなどを確認します。特にMulti-tenant MCPでは、「AIにTenant IDを間違えないよう指示する」だけではなく、Server側で強制してください。
Tokenがない場合はTool Errorではなく401を返す
Protected MCP Serverで認証されていない場合、Tool ResultのErrorとして「Please login」のような内容を返したくなるかもしれません。しかしOAuth認証はTool LayerではなくHTTP Layerで処理するのが基本です。
Protected Resourceへ有効なBearer Tokenなしでアクセスされた場合は、HTTP/1.1 401 Unauthorizedを返します。MCP Appsの公式Authorizationドキュメントでも、Protected Toolの認証はHTTP Boundaryで行い、未認証RequestはTool Level ErrorではなくHTTP 401として返す必要があると説明されています。これによりMCP HostがOAuth Flowを開始できます。
Access Tokenをデータベースへ無条件に保存しない
MCP Serverへ送られてくるBearer Tokenを「念のため保存しておこう」とDatabaseへ書き込む必要は通常ありません。Resource Serverの役割はRequestごとにTokenを検証することです。Token Verifierからclient_id・subject・scope・resource・expires_atなど必要な認証情報を取り出して処理すれば十分です。
生のAccess Tokenそのものを長期保存すると、Database Leak時にTokenを悪用される範囲が広がります。特別な理由がない限り、Request受信→Token検証→Claimsを取得→Request処理終了→生Tokenは保持しない、という設計が分かりやすくなります。
Access Tokenをログへ出さない
特に危険なのがLoggingです。Authorization Headerをそのままログへ出す実装は避けます。Logは、Cloud Logging・APM・Error Tracking・Backup・開発者PCなど多くの場所へ複製される可能性があります。
Tokenを丸ごと保存するのではなく、client_id・subject・scope・request_id・tool_name・statusなど、調査に必要な情報だけ記録します。どうしてもTokenを識別する必要があるなら、生Tokenではなく不可逆Hashなどを利用して相関を取る設計を検討します。
Access TokenをTool Resultへ返さない
さらに避けたいのが、認証確認用のTool内で生のAccess Tokenをそのまま返す実装です。これではAccess TokenがLLM Contextへ入り込む可能性があります。TokenがConversation HistoryやTraceへ残れば、Security上の範囲が大きくなります。
認証確認用Toolを作るなら、次のようにします。
@mcp.tool()
def whoami() -> dict:
token = get_access_token()
if token is None:
return {
"authenticated": False
}
return {
"authenticated": True,
"client_id": token.client_id,
"scopes": token.scopes,
}
Python SDK公式例でもget_access_token()からClient IDとScopeを取得する例はありますが、生Token自体をモデルへ返す必要はありません。
Refresh TokenをMCP Serverへ送らない
OAuthのAccess Tokenは比較的短い有効期限にすることがあります。期限切れになったらRefresh Tokenを使って新しいAccess Tokenを取得できます。ここで重要なのは、通常のMCP Resource ServerがRefresh Tokenを必要としないことです。概念的には、
MCP Client ↓ Refresh Token ↓ Authorization Server ↓ 新しいAccess Token ↓ MCP Server
となります。MCP Serverへ送るのはAccess Tokenです。Refresh TokenはAuthorization ServerとのToken更新に利用する秘密情報なので、MCP Tool Argument・MCP Serverへの通常Request・Conversationへ載せる必要はありません。現在のMCP OAuth Client実装でも、Refresh Tokenが保存されている場合はClient側がToken Endpointで更新し、その後MCP Requestを再試行する設計になっています。
Client側でTokenを保存するなら平文ファイルを避ける
自作MCP Clientを作る場合は、Access TokenやRefresh Tokenを永続化したくなることがあります。開発中ならJSON Fileへ保存する実装でも動きます。しかし本番では、oauth.json・tokens.json・.envへRefresh Tokenをそのまま保存する設計は避けたほうが安全です。
Desktop ApplicationならOSのCredential StoreやKeychainを利用できます。Server ApplicationならSecret Manager、暗号化Database、KMSなどを利用します。特にRefresh TokenはAccess Tokenより長期間利用できることがあるため、より強い保護が必要です。Repository内へToken Fileを置くことは避けてください。.gitignoreへ追加していても、File System上では読み取り可能だからです。
Access Tokenは短命にする
Token Leakの影響を小さくするにはAccess Tokenを短命にする考え方が有効です。Access Tokenが永続的に使える設計では、一度漏えいすると長期間悪用される可能性があります。短いExpirationを設定し、必要に応じてClientがRefresh Tokenを利用して新しいAccess Tokenを取得する構成にします。
Resource Server側でもexpを必ず確認します。Python SDKのBearer Auth Middlewareでも、AccessToken.expires_atが現在時刻より過去ならTokenを無効として扱います。
MCPのTokenを外部APIへそのまま転送しない
これは非常に重要です。たとえばMCP Serverに「GitHubを検索するTool」があるとします。ClientからMCP Serverへ「MCP用Access Token」が送られてきます。そのTokenをそのままAuthorization: Bearer <MCPのToken>としてGitHub APIへ送ってはいけません。
MCP Server向けに発行されたTokenとGitHub向けTokenは別物です。このようなToken PassthroughはAudience Boundaryを壊します。この問題自体はMCP仕様で明示的な禁止事項として扱われており、詳細はClaude Code + MCP セキュリティ設計完全ガイドで解説しています。外部APIへ接続する場合は、「MCP Client → MCP Server用Token」と「MCP Server → GitHub用Token」を分離してください。
外部サービス用OAuth Tokenは別に管理する
たとえばMCP ServerからGoogle Driveへ接続する場合、「MCP Server自身へのOAuth」と「Google DriveへのOAuth」は別の認証です。概念的には、
MCP Client ↓ MCP Access Token ↓ MCP Server ↓ Google Access Token ↓ Google API
となります。MCP Clientから受け取ったTokenをGoogleへ転送しません。Google用TokenはMCP Server側のCredential Storeでユーザーごとに管理します。これにより「MCP ResourceのAudience」と「Google APIのAudience」を明確に分離できます。
外部OAuthにはURL Mode Elicitationも利用できる
MCP Serverが外部サービスへのCredentialを必要とする場合、「GitHub Tokenを入力してください」とTool Argumentで秘密情報をLLMへ渡す設計は避けたいところです。
MCPでは、ユーザーを安全なBrowser上のOAuth Flowへ誘導する方法も用意されています。2025-11-25仕様で導入されたURL Mode Elicitationでは、MCP Clientを経由してAPI KeyやOAuth Credentialを入力させるのではなく、外部Browser上でCredential Acquisitionを行い、Server側が直接Tokenを管理できます。MCP公式Blogでも、これによって外部OAuthをToken Passthroughなしで実装できると説明されています。
つまり「LLMにCredentialを渡す」のではなく、「ユーザー→Browser→外部OAuth→MCP ServerがCredentialを保存」という構成にできます。
Protected Resource MetadataとAuthorization Server Metadataを分ける
OAuth Discoveryでは、「MCP ServerがどのAuthorization Serverに守られているか」を表すMetadataと、「Authorization ServerがどのEndpointを提供しているか」を表すMetadataがあります。MCP Server側ではProtected Resource Metadataを公開します。Authorization Server側ではAuthorization Server Metadataを公開します。
MCP ClientはまずMCP Server側のMetadataを見てAuthorization Serverを特定し、その後Authorization ServerのMetadataからauthorization_endpoint・token_endpoint・scopesなどを取得します。現在のMCP SDKでも、401のWWW-AuthenticateからProtected Resource Metadataを発見し、その情報からAuthorization Serverを探索するFlowが実装されています。
401と403を使い分ける
認証関連ではHTTP Status Codeも重要です。Tokenがない、Tokenが無効、期限切れなら401 Unauthorizedです。Token自体は有効だが、必要なScopeがない場合は403 Forbiddenとinsufficient_scopeを使えるケースがあります。
現在のMCP OAuth Client実装では、403のWWW-Authenticateにerror="insufficient_scope"が含まれる場合、必要なScopeを追加して再認可するStep-up Authorizationにも対応しています。たとえばnotes:readしか持っていないClientがdelete_noteを実行しようとした場合です。いきなり全ユーザーへ強いScopeを要求するのではなく、必要になった時点で追加権限を要求する設計も可能です。
最初からAdmin Scopeを全部付与しない
OAuthを導入しても、「全部のToolが必要だからadminを付ける」という設計ではLeast Privilegeになりません。たとえばsearchだけ利用するClientへ、delete・deploy・billing・adminまで許可する必要はありません。
Scopeを細分化し、read・write・delete・adminなど、必要な権限だけ要求します。AI Agentは自動的にToolを選択するため、意図しないTool Callの影響を抑える意味でも権限の最小化は重要です。
Token検証結果をRequest単位で扱う
2026-07-28のMCP CoreはStatelessになっています。従来のProtocol SessionへUser Identityを固定しておくのではなく、Requestごとに必要な情報を扱う方向になっています。各Requestが自己完結し、通常のRound-robin Load Balancerへ配送できる設計です。
認証も同様に、「このSessionはAlice」と隠れた状態へ依存するより、Request→Bearer Token検証→Identity取得→Tool実行とするほうが分かりやすくなります。Python SDKのAuth ContextもRequestごとのContextとしてToken情報を保持します。
2026-07-28ではIssuer検証がより重要になった
MCP 2026-07-28ではOAuth周辺に複数のSecurity Hardeningが入りました。Authorization ServerはRFC 9207に沿ってissを返し、ClientはAuthorization CodeをTokenへ交換する前にIssuerを検証します。さらにClient Credentialは、それを発行したIssuerへ結び付けられ、別のAuthorization Serverへ使い回さない設計が導入されています。これはAuthorization Server Mix-upなどを防ぐために重要です。古いMCP OAuth実装をそのままコピーしている場合は、2026-07-28対応SDKへ更新したうえでIssuer Validationを確認してください。
Dynamic Client Registrationは非推奨になった
古いMCP OAuthの記事では、ClientがAuthorization ServerへDynamic Client Registrationを行う例が多くあります。しかし2026-07-28仕様ではDynamic Client Registration、DCRは非推奨となり、Client ID Metadata Documents、CIMDへ移行する方針になりました。
CIMDではClient自身がHTTPS URLでMetadata Documentを公開し、そのURL自体をclient_idとして利用できます。これによりAuthorization ServerごとにClient Registration Requestを繰り返す必要を減らせます。CIMDの具体的なJSON形式はClaude Code + MCP セキュリティ設計完全ガイドにサンプルがあります。現在はBackward CompatibilityのためDCRも残っていますが、新規のOAuth Clientを設計するならCIMD対応を確認する価値があります。
OAuth SecretをMCP Toolの引数にしない
Tool ArgumentへAccess Tokenを直接渡す設計は避けたい実装です。このTokenはLLM Context・Tool Call Log・Trace・Conversation履歴などに残る可能性があります。TokenやPasswordをAIモデルへ入力させる必要はありません。認証はHTTP Authorization LayerまたはBrowser OAuth Flowで行い、Toolには業務上必要な値だけを渡します。
Tokenを.envに固定するだけでは本番OAuthにならない
開発中はMCP_TOKEN=abc123のような固定Tokenで動作確認する方法があります。これはToken Verificationの仕組みを試す目的では便利です。Python SDK公式ドキュメントにもStatic Tokenを利用した簡単なExampleがあります。
しかし本番環境で全ユーザー共通のTokenを長期間使う設計では、誰が利用したか分からない・Scopeを分けられない・個別に失効できない・漏えい時に全利用者へ影響するという問題があります。複数ユーザーへ提供するRemote MCPでは、Authorization Serverからユーザー・Client単位でTokenを発行するほうが管理しやすくなります。
Tokenの失効も考える
JWTをLocal Validationする構成では、署名とExpirationが正しければTokenを有効として扱うことが一般的です。しかし「ユーザーを無効化した」「権限を取り消した」「Tokenを緊急失効した」という変更を即時反映したい場合があります。
この場合は短いAccess Token Lifetimeを使うか、Authorization ServerのToken Introspection Endpointで状態を確認する設計を検討できます。MCP Python SDKの公式Authorizationガイドでも、ProductionのToken VerifierとしてJWT Signature ValidationまたはRFC 7662 Token Introspectionを利用する形が想定されています。どちらを選ぶかは、Latency・失効反映速度・可用性・Token形式・IdPの仕様によって変わります。
OAuthを導入してもHTTPSは必須と考える
Bearer Tokenは、その値を持っている主体が利用できるCredentialです。そのためHTTP平文通信で送信するのは避けます。OAuth = 誰がアクセスできるか、HTTPS = 通信内容を保護するという別の役割です。HTTPS・Reverse Proxy・Host Allowlistなどリモート公開のインフラ構成はMCPサーバーをリモート公開する方法で詳しく解説しています。
Access Tokenの安全な扱いは「LLMから遠ざける」が基本
MCPでOAuthを安全に実装するとき、最も分かりやすい考え方は「TokenをできるだけLLMのContextへ入れない」ことです。ClientはOAuth LayerでAccess Tokenを取得し、HTTP HeaderとしてMCP Serverへ送信します。MCP ServerはMiddlewareでTokenを検証します。Tool Handlerには必要なIdentity、Scope、Subjectなどだけを渡します。外部サービスのCredentialが必要なら、それもMCP Server側で別に管理します。
この構成なら、User Prompt・Model Context・Tool Arguments・Tool Resultへ秘密情報が流れ込む範囲を減らせます。
MCPでOAuth認証を実装する際によくある質問
QMCPサーバー自身にログイン画面やユーザーDBを実装する必要がありますか
A必要ありません。MCPサーバーはOAuthのResource Serverとして実装するのが基本です。ユーザーのログインとToken発行はAuth0やEntra ID、社内Identity ProviderなどのAuthorization Serverに任せ、MCPサーバー側はTokenVerifierでAuthorization HeaderのTokenを検証するだけです。
QJWTの署名が正しければ認証は完了ですか
A完了しません。署名が正しいだけでは、そのTokenが本当にこのMCPサーバー向けに発行されたものかは分かりません。iss(発行者)、aud/resource(対象)、exp(期限)、scope(権限)を確認する必要があります。特にAudienceの確認を省略すると、別API向けのTokenをそのまま受け付けてしまう可能性があります。
QAccess Tokenをデータベースやログに保存してもよいですか
A基本的には避けてください。MCPサーバーの役割はRequestごとにTokenを検証することで、生のTokenを長期保存する必要は通常ありません。ログにはclient_idやscope、request_idなど調査に必要な情報だけを残し、Authorizationヘッダーの値そのものは出力しないようにします。
QMCPサーバーが受け取ったTokenを外部API(GitHubやGoogleなど)へそのまま転送してもよいですか
Aしてはいけません。これはToken Passthroughと呼ばれ、MCP仕様で明示的に禁止されているパターンです。MCPサーバー向けのTokenと外部API向けのTokenはAudienceが異なるため、外部APIへ接続する場合はそのAPI用の認証情報を別に取得・管理する必要があります。
QRefresh TokenはMCPサーバーへ送る必要がありますか
A送る必要はありません。MCPサーバーへ送るのはAccess Tokenだけです。Refresh TokenはMCP ClientとAuthorization Serverの間でAccess Tokenを更新するために使う秘密情報であり、MCP Tool ArgumentやMCP Serverへの通常のRequestに載せるものではありません。
Q固定のAPIキー1本でOAuthの代わりにしてもよいですか
A少人数・限定ネットワークでの検証目的なら技術的には可能です。ただし全ユーザー共通で有効期限もScopeもない固定Tokenは、利用者識別や個別失効ができず、漏えい時の影響が全利用者に及びます。複数ユーザーや外部提供を想定するなら、OAuthベースのAccess Tokenを使うほうが安全に運用できます。
MCPのOAuthはToken発行よりToken検証から考える
MCPでOAuth認証を実装するときは、「MCP Serverにログイン機能を作る」ところから始める必要はありません。Streamable HTTP上のMCP ServerはOAuth Resource Serverとして動作し、Clientから届くBearer Tokenを検証します。現在のPython SDKでも、Resource ServerはTokenを発行せず、TokenVerifierを通してRequestごとのTokenを検証する構造です。
実装ではtoken_verifier=MyTokenVerifier()とauth=AuthSettings(...)を設定します。JWTを使う場合は署名、Issuer、ExpirationだけでなくAudienceを必ず確認します。MCP Server向けではないTokenを受け付けないことが、Token Passthroughを防ぐうえでも重要です。
必要な操作はScopeで制限し、Tool Handler内ではget_access_token()からCaller Identityを取得してResource単位のAuthorizationを行います。Access TokenはDatabaseやLogへ無条件に保存せず、Tool ArgumentやTool Resultにも載せないようにします。Refresh TokenはMCP ServerではなくOAuth ClientとAuthorization Serverの間で扱い、MCP Serverへ通常Requestとして送信する必要はありません。
またMCP ServerからGitHubやGoogleなど別のAPIへ接続する場合、MCP Server用のAccess Tokenを外部APIへそのまま転送してはいけません。外部サービス向けOAuthは別に実施し、それぞれのAudienceに対応したCredentialを利用します。2026-07-28 MCPではIssuer ValidationやCredential Isolationが強化され、Dynamic Client RegistrationからClient ID Metadata Documentsへ移行する方針も示されています。
MCPのOAuth認証を安全に設計するポイントは、「Tokenを持っているから通す」のではなく、「誰が発行し、どのResource向けで、どのScopeを持ち、現在も有効なのか」をRequestごとに確認することです。さらにTokenそのものをLLMから切り離し、HTTP Authentication LayerとServer-side Credential Storeの中だけで扱う構成にすると、Remote MCPを安全に運用しやすくなります。

