結論:本記事では「MCP認可の実装ガイド」を具体的なステップとコピペ可能な実例つきで解説します。
対象読者:本テーマに興味がある実務担当者・意思決定者。
読了後にできること:本記事の要点を踏まえて、自社や自分の状況に合わせた次のアクションを判断できます。
「MCPサーバーを社内ツールにつないだ瞬間、認可まわりが急に難しくなった」——最近のAIエージェント実装で、ここに詰まるケースが増えています。
ローカルで動くMCPなら簡単です。ところが、SaaS API、社内CRM、ナレッジベース、請求システムなどに接続し始めると、「誰の権限で」「どのサーバー向けに」「どの範囲だけ」ツールを呼べるのかを明確にしないと危険です。
この記事では、Model Context Protocol(MCP)の最新仕様である 2025-11-25版 の認可仕様をもとに、OAuth 2.1、PKCE、Resource Indicators、Protected Resource Metadataをどう組み合わせるかを、実装担当者向けに整理します。
MCP認可で最初に決めるべき3つの境界
MCP認可の設計で最初に見るべきなのは、ログイン画面ではありません。境界です。どのMCPサーバーが保護リソースで、どの認可サーバーがトークンを発行し、どのクライアントがそのトークンを使うのかを先に決めます。
3つの境界の役割と決定事項
MCPの認可設計では、サーバー・認可サーバー・クライアントの3者がそれぞれ異なる責務を持ちます。以下の表で各境界の役割を整理します。
| 境界 | 決めること | 実装上の注意 |
|---|---|---|
| MCPサーバー | どのツール・リソースを公開するか | canonical URIを固定し、トークンの対象にする |
| 認可サーバー | 誰に、どのscopeを許可するか | OAuth 2.1相当のフロー、PKCE、redirect URI検証を使う |
| MCPクライアント | どのサーバー向けのトークンを要求するか | authorization request と token request の両方に resource を入れる |
MCPサーバーはOAuth 2.1のResource Serverとして振る舞う
公式仕様では、保護されたMCPサーバーはOAuth 2.1のresource serverとして振る舞います。つまり、MCPサーバー自身がユーザー認証を全部抱え込むのではなく、アクセストークンを受け取り、そのトークンが自分宛てかどうかを検証する立ち位置です。
resourceパラメータを必ず入れる
最新仕様で特に重要なのが、RFC 8707の Resource Indicators です。MCP仕様では、authorization request と token request の両方に resource パラメータを含めることが求められています。これにより、トークンの宛先が「どのMCPサーバーなのか」を明示できます。
認可URLにresourceを含める実装例
まずは、認可URLを作る関数を小さく切り出すのが安全です。
# 動作環境: Python 3.11+
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。
from urllib.parse import urlencode
AUTHORIZATION_ENDPOINT = "https://auth.example.com/oauth2/authorize"
CLIENT_ID = "https://client.example.com/oauth/client-metadata.json"
REDIRECT_URI = "https://client.example.com/oauth/callback"
MCP_RESOURCE = "https://mcp.example.com"
def build_authorization_url(state: str, code_challenge: str) -> str:
params = {
"response_type": "code",
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": "mcp:tools.read mcp:tools.call",
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": "S256",
"resource": MCP_RESOURCE,
}
return f"{AUTHORIZATION_ENDPOINT}?{urlencode(params)}"
scopeとresourceの違いを理解する
ポイントは、scopeだけで安心しないことです。scopeは「何ができるか」、resourceは「どのリソース向けか」を表します。複数のMCPサーバーを使うエージェントでは、この区別がないとトークンの使い回しやconfused deputy問題につながります。
トークン交換時もresourceを落とさない
authorization request にresourceを入れても、token requestで落とすと意味が薄れます。MCP仕様では両方に含める前提なので、トークン交換の関数にも必ずresourceを渡します。
トークン交換のコード例とresource指定
以下のコードは、認可コードをトークンに交換する際にresourceパラメータを含める実装例です。
# 動作環境: Python 3.11+, httpx>=0.27
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。
import httpx
TOKEN_ENDPOINT = "https://auth.example.com/oauth2/token"
MCP_RESOURCE = "https://mcp.example.com"
async def exchange_code_for_token(code: str, code_verifier: str) -> dict:
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": "https://client.example.com/oauth/callback",
"client_id": "https://client.example.com/oauth/client-metadata.json",
"code_verifier": code_verifier,
"resource": MCP_RESOURCE,
}
async with httpx.AsyncClient(timeout=10.0) as client:
res = await client.post(TOKEN_ENDPOINT, data=data)
res.raise_for_status()
return res.json()
client secretの安全な管理
ここでclient secretをコードに直書きしないことも大事です。confidential clientでsecretを使う場合は、環境変数やシークレットマネージャーに置き、ログにも出さないようにします。
MCPサーバー側ではaudienceとissuerを検証する
アクセストークンを受け取ったMCPサーバー側では、「署名が正しい」だけでは不十分です。少なくとも issuer、audience、scope、期限を確認します。
JWTトークン検証の最小構成
JWTを使う場合、issuer・audience・scope・有効期限の4項目を必ずチェックする実装を用意します。
# 動作環境: Python 3.11+, fastapi>=0.110, pyjwt[crypto]>=2.8
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。
from fastapi import Depends, FastAPI, HTTPException, Request
import jwt
app = FastAPI()
ISSUER = "https://auth.example.com"
AUDIENCE = "https://mcp.example.com"
JWKS_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----n...n-----END PUBLIC KEY-----"
async def require_mcp_token(request: Request) -> dict:
auth = request.headers.get("authorization", "")
if not auth.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="missing bearer token")
token = auth.split(" ", 1)[1]
try:
claims = jwt.decode(token, JWKS_PUBLIC_KEY, algorithms=["RS256"], issuer=ISSUER, audience=AUDIENCE)
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="invalid token")
scopes = set(claims.get("scope", "").split())
if "mcp:tools.call" not in scopes:
raise HTTPException(status_code=403, detail="insufficient scope")
return claims
@app.post("/mcp/tools/call")
async def call_tool(claims: dict = Depends(require_mcp_token)):
return {"ok": True, "subject": claims.get("sub")}
audience固定が防ぐトークン流用リスク
この例では audience="https://mcp.example.com" を固定しています。別のMCPサーバー向けに発行されたトークンを受け付けないための、かなり重要な行です。
Protected Resource Metadataを返す
クライアントがどの認可サーバーに行けばよいか分からない場合、Protected Resource Metadataが役立ちます。RFC 9728では、保護リソースがメタデータURLを示す仕組みが定義されています。MCPでもこの考え方が採用されています。
メタデータエンドポイントの実装例
MCPサーバーに /.well-known/oauth-protected-resource エンドポイントを追加し、認可サーバーの情報を返せるようにします。
# 動作環境: Python 3.11+, fastapi>=0.110
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。
from fastapi import FastAPI, Response
app = FastAPI()
RESOURCE = "https://mcp.example.com"
AUTH_SERVER = "https://auth.example.com"
@app.get("/.well-known/oauth-protected-resource")
async def protected_resource_metadata():
return {"resource": RESOURCE, "authorization_servers": [AUTH_SERVER], "scopes_supported": ["mcp:tools.read", "mcp:tools.call"], "bearer_methods_supported": ["header"]}
@app.get("/mcp")
async def mcp_entrypoint():
return Response(status_code=401, headers={"WWW-Authenticate": 'Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"'})
リバースプロキシ利用時のURL不一致に注意
本番では、メタデータのURL、resource値、実際のMCPサーバーURLが食い違わないようにします。リバースプロキシやカスタムドメインを使っている場合、ここがズレやすいです。
よくある失敗パターンと回避策
scopeだけの認可とPKCE省略の危険性
失敗1:scopeだけで認可を完結させる
scopeは便利ですが、トークンの宛先までは表しません。MCPサーバーが複数あるならresource/audience検証まで入れてください。
失敗2:PKCEを省略する
ブラウザやデスクトップアプリを含むpublic clientでは、認可コードの横取り対策が重要です。MCP仕様でもPKCEの利用が前提になっています。
トークン流用と開発設定の残存リスク
失敗3:トークンをそのまま下流SaaSへ渡す
token passthroughは、権限の境界を曖昧にします。MCPサーバー側で必要最小限の権限に変換するか、ツールごとにscopeを分ける設計にしてください。
失敗4:ローカル開発用の例外が本番に残る
localhost、自己署名証明書、広すぎるCORS、private IP許可などは開発では便利ですが、本番では攻撃面になります。設定を環境ごとに分けるのが安全です。
実装チェックリスト
認可フロー設計の確認項目
トークン発行までのフローが正しく構成されているか、以下の項目で確認します。
- MCPサーバーのcanonical URIを1つに決めたか
- authorization request と token request の両方にresourceを入れたか
- PKCEのS256方式を使っているか
- ツールごとに必要scopeを分けたか
サーバー側検証とセキュリティの確認項目
トークン受信後の検証と、本番環境のセキュリティ設定を確認します。
- MCPサーバー側でissuer、audience、scope、expを検証しているか
- Protected Resource Metadataを返せるか
- ログにaccess token、authorization code、client secretを出していないか
- 開発環境の例外設定が本番に残っていないか
すでにMCPサーバーの作り方を確認したい方は、MCPの基礎解説と、PythonでMCPサーバーを作るガイドもあわせて読むと全体像をつかみやすいです。エージェント全体のガードレール設計は、AIエージェントのガードレール解説も参考になります。
まとめ:今日から始める3つのアクション
優先度別のアクションプラン
認可実装は一度にすべてを完成させる必要はありません。以下の3段階で進めると確実です。
- 今日やること:MCPサーバーごとのcanonical URIを一覧化し、resource値として固定する
- 今週中:認可URLとトークン交換処理にresourceとPKCEを入れ、ステージングで検証する
- 今月中:audience検証、scope分割、Protected Resource Metadataを本番構成に組み込む
参考・出典
- Authorization – Model Context Protocol — MCP公式仕様 2
MCP認可の実装ガイドのFAQ
導入判断で迷いやすい点を、実務運用の観点から整理します。
よくある質問
MCPで認可が重要な理由は?
AIエージェントが外部ツールを呼ぶため、誰がどのツールをどの範囲で使えるかを明確にしないと、情報漏洩や誤操作につながります。
OAuth 2.1では何を確認しますか?
認可コードフロー、PKCE、スコープ、トークン失効、リフレッシュトークンの扱いを確認します。
スコープはどう設計すべきですか?
読み取り、作成、更新、削除を分け、初期状態は読み取り中心にします。危険操作は追加承認を必要にします。
企業導入で必要なログは?
ユーザー、クライアント、スコープ、実行ツール、対象リソース、成功/失敗、承認者を残します。
最初に実装するべき制御は?
最小権限、短いトークン期限、明示的なユーザー承認、監査ログ、緊急停止の5つです。
最終確認日: 2026年5月19日
