Claude Code などの MCP クライアントから Cloudflare Workers 上の MCP サーバーへOAuth接続したいとき、[@cloudflare/workers-oauth-provider](https://github.com/cloudflare/workers-oauth-provider) を使いますが、
Cloudflare Access も有効でサイト全体をそのまま保護すると接続できないことがあります。
この記事では、実際に詰まったポイントと、最終的に動作した構成をまとめます。なお、ここで紹介する構成のうち一部は Cloudflare Access や MCP の一般ルールではなく、今回のアプリ実装に依存する判断を含みます。
先に結論
Cloudflare Access でホスト全体を保護したまま MCP を同居させる こと自体は可能でした。 ただし、MCP クライアントが参照する endpoint の一部を path 単位で Bypass しないと接続できませんでした。
今回の example-app では、結果的に次のような切り分けで動作しました。
UI 全体は Cloudflare Access 保護のまま
MCP 用 metadata / token / register / api endpoint は
Bypass/oauth/authorizeはBypassしない
この最後の点は、今回の実装で /oauth/authorize 側が Cloudflare Access の JWT を前提にユーザー特定していることに依存しています。一般化して断定できるものではありません。
症状
最初は Cloudflare Access で example.shwld.app/* を保護していました。
その状態で MCP endpoint (example.shwld.app/api/mcp) にアクセスすると、未認証時に返ってほしい 401 Unauthorized ではなく、Cloudflare Access のログイン URL への 302 が返っていました。
例えば、次のような状態です。
/api/mcp->302/.well-known/oauth-authorization-server->302/.well-known/oauth-protected-resource/api/mcp->302
この状態では、MCP クライアントは OAuth フローを開始できません。
期待される正しい挙動
MCP クライアントが接続できる状態では、未認証時に少なくとも次のようなレスポンスになる必要があります。
/.well-known/oauth-authorization-server->200/.well-known/oauth-protected-resource/api/mcp->200/api/mcp->401/api/mcpのWWW-Authenticateヘッダーにresource_metadata=...が含まれる
この状態になって初めて、Claude Code などのクライアントが OAuth metadata を読み取り、ブラウザ認証へ進めます。
今回動いた Cloudflare Access 構成
最終的に動作したのは次の構成です。
1. UI 全体を保護する application
name:
example-apphost:
example.shwld.apppath: 空欄
policy:
Allow
これは通常の Web UI 用です。
2. MCP endpoint を Bypass する application
name:
example-mcp-apihost:
example.shwld.apppath:
api/mcppolicy:
Bypass
3. OAuth authorization server metadata を Bypass する application
name:
example-mcp-authz-serverhost:
example.shwld.apppath:
.well-known/oauth-authorization-serverpolicy:
Bypass
4. OAuth protected resource metadata を Bypass する application
name:
example-mcp-protected-resourcehost:
example.shwld.apppath:
.well-known/oauth-protected-resource*policy:
Bypass
5. OAuth client registration endpoint を Bypass する application
name:
example-mcp-registerhost:
example.shwld.apppath:
oauth/registerpolicy:
Bypass
6. OAuth token endpoint を Bypass する application
name:
example-mcp-tokenhost:
example.shwld.apppath:
oauth/tokenpolicy:
Bypass
/oauth/authorize はどうしたか
今回の example-app では /oauth/authorize は Bypass しませんでした。結果として、この path は root の UI 用 Access application によって通常どおり保護される状態になっていました。
これは Cloudflare Access や MCP の一般的な必須構成として断定できるものではなく、今回のアプリ実装に依存する判断です。example-app では /oauth/authorize 側で Cloudflare Access の JWT を使って認証済みユーザーを特定していたため、この path まで Bypass すると都合が悪い構成でした。
一方で、MCP クライアントが直接参照する次の endpoint は 302 で Access ログインへ飛ばされると動作しませんでした。
/api/mcp/.well-known/oauth-authorization-server/.well-known/oauth-protected-resource*/oauth/register/oauth/token
そのため、今回の構成では上記の path を Bypass し、/oauth/authorize は Bypass しない形に落ち着きました。
なぜこの切り分けが必要だったか
MCP クライアントは次のような流れで endpoint を使います。
/api/mcpに接続する401とWWW-Authenticateを受け取る/.well-known/oauth-protected-resource/...を読む/.well-known/oauth-authorization-serverを読む/oauth/registerで client 登録する/oauth/authorizeをブラウザで開く/oauth/tokenで token 交換する
今回の実装では、
metadata
register
token
api/mcp
が Cloudflare Access に先に捕まると失敗しました。
一方で /oauth/authorize は、今回の実装では Cloudflare Access 保護下に置いたまま、そこで認証済みユーザーを解決する前提になっていました。
ハマったポイント
api/mcp だけ Bypass しても足りなかった
最初は /api/mcp だけ Bypass すればよいように見えましたが、それでは不十分でした。
実際には、次も必要でした。
/.well-known/oauth-authorization-server/.well-known/oauth-protected-resource*/oauth/register/oauth/token
このどれかが 302 を返すと、OAuth フローの途中で止まりました。
root の Access app を消すと通常ログインが壊れた
一度 root の Access app を消したところ、通常のログイン画面のボタンが飛ぶ /cdn-cgi/access/login も使えなくなりました。
今回の UI は Cloudflare Access 前提だったため、
UI は root app が必要
MCP は一部 path だけ root app を回避する必要がある
という両立が必要でした。
接続確認に使った curl
metadata 確認
curl -i https://example.shwld.app/.well-known/oauth-authorization-server
curl -i https://example.shwld.app/.well-known/oauth-protected-resource/api/mcp
期待値は、どちらも 200 です。
未認証の MCP 初期化確認
curl -i -X POST https://example.shwld.app/api/mcp \
-H 'content-type: application/json' \
-H 'mcp-protocol-version: 2025-03-26' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.0.0"}}}'
期待値は次のとおりです。
401 UnauthorizedWWW-Authenticateヘッダーあり
register / token が Access に捕まっていないか確認
curl -i https://example.shwld.app/oauth/register
curl -i https://example.shwld.app/oauth/token
期待値は次のとおりです。
302ではないアプリ由来の
405や401が返る
実際の最終確認
Claude Code から /mcp で接続した結果は次でした。
Authentication successful. Connected to example-app.
ここまで出れば、Cloudflare Access 側の path 切り分けは成功していると判断できます。
まとめ
今回の構成では、Cloudflare Access 配下に UI と MCP を同居させるために、MCP クライアントが参照する path だけを Bypass する必要がありました。
重要だったのは次の 2 点です。
metadata / register / token / api endpoint は
Bypass/oauth/authorizeはBypassしない
ただし後者は、今回のアプリ実装に依存する判断です。同じ Cloudflare Access と MCP の組み合わせでも、認証の持ち方によって別の構成を取り得ます。
出典
Cloudflare, "Secure MCP servers with Access for SaaS" https://developers.cloudflare.com/cloudflare-one/access-controls/ai-controls/saas-mcp/
Cloudflare, "Application paths" https://developers.cloudflare.com/cloudflare-one/access-controls/policies/app-paths/
Cloudflare, "Access policies" https://developers.cloudflare.com/cloudflare-one/policies/access/
Model Context Protocol, "Authorization" https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization
shwld