MCP
MCPサーバー認証を「動く仕様」にする:OAuth 2.1時代の実装ランブック(Cloudflare Workers実践)
MCPサーバーをOAuth 2.1前提で安全運用するための実装ガイドです。RFC 9728/9700の要点、PKCE、スコープ設計、Cloudflare Workersでの実装パターンを個人開発向けに整理します。
MCPサーバー認証を「動く仕様」にする:OAuth 2.1時代の実装ランブック(Cloudflare Workers実践)
AIエージェントを作っていると、だいたい同じ壁にぶつかります。
「動く。便利。……でも、このMCPサーバー誰でも叩けない?」という、心臓にやや悪い壁です。
最近のMCP(Model Context Protocol)仕様では、認証・認可まわりがかなり整理されてきました。特に、MCPサーバーを OAuth 2.1 のリソースサーバーとして扱う前提が明確になり、WWW-Authenticate ヘッダーや /.well-known のメタデータ公開を前提にした発見フローが実務レベルに落ちてきています。
本記事では、私が個人開発で使っている「最小だけど事故りにくい」設計を、Cloudflare Workersを例にまとめます。
トーンは軽めですが、中身はちゃんと堅めです。冗談は入れますが、トークンは漏らしません。
なぜ今、MCP認証を見直すのか
理由はシンプルで、MCPサーバーが「ローカルのお試し」から「外部接続される運用対象」へ一気に進んだからです。
MCPの認可仕様(draft)では、以下が実質の前提になっています。
- MCPサーバーは OAuth 2.1 の保護リソースとして振る舞う
- 保護リソースメタデータ(RFC 9728)を提供する
- クライアントは
WWW-Authenticateと/.well-known/oauth-protected-resourceを使って認可サーバーを発見する
※OAuth 2.1: OAuth 2.0の実運用で推奨される手法を統合・整理したフレームワーク案です。Implicit Grantのような危険運用を避け、PKCE前提を強める方向です。
※PKCE(Proof Key for Code Exchange): 認可コードの横取り対策。公開クライアントでは必須級です。
「とりあえずAPIキー1本で」は立ち上げ時には速いのですが、利用者が増えた瞬間に権限管理と監査ログが崩れます。
エージェントが社内Wikiを読み、Issueを作り、DBを引く世界では、誰の代理で何をしたかが追えない設計は長生きしません。
仕様の要点を3分で押さえる(MCP × OAuth 2.1)
実装で重要なのは、仕様書の全暗記ではなく「どこが必須か」を間違えないことです。
- MCPサーバーはリソースサーバー
- トークン発行主体(認可サーバー)と責務を分離できる
- 認可サーバー発見が標準化
- 401応答時の
WWW-Authenticateからresource_metadataを示す - もしくは
/.well-known/oauth-protected-resourceを提供
- 401応答時の
- クライアント側は複数AS対応を前提にする
authorization_serversが複数返る可能性がある- ASごとにクライアント登録・トークン状態を分離する
ここでハマりやすいのが、「1つのclient_idを全ASで共通利用できると思い込む」問題です。
これはかなり危険です。ASごとに識別子の意味が違うため、資格情報の使い回しは事故の入口になります。
「同じ見た目のエンドポイントだから同じだろう」は、開発者の楽観としては満点ですが、セキュリティとしては赤点です。
OAuth 2.0 BCP(RFC 9700)から逆算する設計原則
RFC 9700(OAuth 2.0 Security Best Current Practice)は、平たく言うと「昔のやり方、そのままだと危ないです」を体系化した文書です。MCP文脈でも効くポイントは次の3つです。
1) 認可コード + PKCEを標準路線にする
公開クライアント(CLI、デスクトップアプリ、ローカルエージェント)では、PKCEなし運用を許す理由はほぼありません。
「開発中だから平文challenge許可で」は短期的には楽ですが、本番に残りがちです。
2) トークンの権限を絞る
- スコープを雑に
*へ寄せない - ツール単位で最低権限に分割する
- トークンTTLを長くしすぎない
※TTL(Time To Live): 有効期限です。長いほど運用は楽ですが、漏えい時の被害半径が広がります。
3) 監査可能性を設計に含める
「成功ログだけある」は監査では不合格です。以下を最低限残します。
- 誰が(主体)
- どのクライアントで(client_id / app)
- どのスコープで(権限)
- 何を実行したか(tool/action)
- いつ失敗したか(拒否・期限切れ・不足権限)
監査ログは、問題が起きた後に読むものです。
つまり、平時に「未来の自分が読める日本語」で書いておくのが最適解です。英語でももちろん良いですが、深夜2時に自分が読める言語を選ぶと生存率が上がります。
Cloudflare Workersで作る最小実装(サンプル)
Cloudflareの workers-oauth-provider は、MCPサーバーをOAuthで保護する土台として使いやすいです。以下は概念を掴むための最小例です(実環境での検証・調整は必須)。
// 動作未検証サンプル(設計説明用)
import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
import { WorkerEntrypoint } from "cloudflare:workers";
class ApiHandler extends WorkerEntrypoint {
async fetch(req: Request): Promise<Response> {
// 実際には user / scope 情報を取り、ツール権限を判定
return new Response(JSON.stringify({ ok: true }), {
headers: { "content-type": "application/json" },
});
}
}
export default new OAuthProvider({
apiRoute: "/mcp",
apiHandler: ApiHandler,
defaultHandler: {
async fetch() {
return new Response("MCP OAuth gateway", { status: 200 });
},
},
authorizeEndpoint: "/authorize",
tokenEndpoint: "/oauth/token",
clientRegistrationEndpoint: "/oauth/register",
// スコープはツール単位に寄せる
scopesSupported: ["task.read", "task.write", "admin.audit.read"],
// 互換性より安全性を優先
allowImplicitFlow: false,
allowPlainPKCE: false,
// トークン寿命は短めから開始
accessTokenTTL: 3600,
refreshTokenTTL: 1209600,
});
ポイントは、便利なデフォルトをそのまま本番に持ち込まないことです。
特に allowPlainPKCE は段階的にでも false へ寄せる価値があります。
実運用で効く「権限設計」の切り方
MCPサーバーの認可設計は、技術よりモデリングが9割です。私は次の順で決めると失敗が減りました。
手順A: ツールを「読み取り」と「変更」に分離
doc.readdoc.writerepo.issue.createrepo.issue.close
この分離だけで、誤操作時の被害がだいぶ小さくなります。
手順B: 危険操作に追加ガード
- 本番反映、削除、課金操作は別スコープ
- 可能なら二段階承認(人間確認)を入れる
手順C: スコープ不足を“親切に”返す
401/403時のエラーメッセージが雑だと、クライアント実装者が迷子になります。
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "insufficient_scope",
"required": ["repo.issue.create"],
"granted": ["repo.read"]
}
この「必要権限の見える化」は、UX改善だけでなく運用問い合わせの削減にも効きます。
サポート担当の睡眠時間は、仕様準拠の副産物として守られます。
よくある地雷と回避策
ここはかなり実務的な話です。私が実際に何度か踏んだ(そして足跡がまだある)地雷を列挙します。
地雷1: Redirect URIの管理が甘い
- 回避策: 完全一致で検証する
- ワイルドカードや曖昧一致を避ける
地雷2: スコープ設計が雑で肥大化
- 回避策: ツール境界ごとに分割し、利用実績で棚卸しする
- 使われないスコープは削除候補にする
地雷3: 静的トークンを長期運用
- 回避策: 短命アクセストークン + リフレッシュトークン + 失効導線
- 漏えいを前提に「被害最小化」で設計
地雷4: 監査ログが“読めない”
- 回避策: JSON構造を統一し、
actor,client_id,scope,tool,resultを固定 - 検索しやすいキー名に統一
地雷5: 仕様ドラフト依存のハードコード
- 回避策: 仕様差分を吸収できる設定レイヤーを作る
.well-knownやヘッダー解析を関数化して差し替え可能にする
ドラフト仕様を使う時は、「未来の変更は来る」という前提で実装しておくのが平和です。
「仕様が変わるなんて聞いてない」は、だいたい自分が昨日の自分に言っています。
導入チェックリスト(個人開発向け)
最後に、今日から使える最低限チェックです。
- MCPエンドポイントが401時に
WWW-Authenticateを返す -
/.well-known/oauth-protected-resourceを公開している - 認可コードフロー + PKCE(S256)を採用している
-
allowImplicitFlowを無効化している - スコープがツール単位で分離されている
- 期限切れ・拒否・権限不足のログが残る
- 認可サーバーごとにclient登録状態を分離している
- トークンTTLと失効導線を定義している
全部いきなり完璧でなくても大丈夫です。
まずは「発見フロー準拠」と「PKCE前提化」から始めるだけで、事故率はかなり下がります。
まとめ
MCPの認証・認可は、面倒に見えて実は「後から効く保険」です。
エージェントが賢くなるほど、権限事故の破壊力も上がります。だからこそ、OAuth 2.1系の実装原則とMCP仕様の発見フローを、最初に押さえておく価値があります。
私の結論は次の一文です。
「エージェントを速くする前に、代理実行の責任境界を速く定義する」
速度はキャッシュで買えますが、信頼は設計でしか買えません。
そして設計は、だいたい締切の前日に突然は生えてきません。残念ながら。
参考リンク
- Model Context Protocol: Authorization https://modelcontextprotocol.io/specification/draft/basic/authorization
- IETF RFC 9728: OAuth 2.0 Protected Resource Metadata https://datatracker.ietf.org/doc/html/rfc9728
- IETF RFC 9700: Best Current Practice for OAuth 2.0 Security https://datatracker.ietf.org/doc/rfc9700/
- IETF Draft: The OAuth 2.1 Authorization Framework https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/
- Cloudflare Docs: Authorization for MCP on Workers https://developers.cloudflare.com/agents/model-context-protocol/authorization/
- Cloudflare workers-oauth-provider https://github.com/cloudflare/workers-oauth-provider
- Cloudflare Blog: Managed OAuth for Access https://blog.cloudflare.com/managed-oauth-for-access/