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 を読み取り、ブラウザ認証へ進めます。
最終的に動作したのは次の構成です。
name: example-app
host: example.shwld.app
path: 空欄
policy: Allow
これは通常の Web UI 用です。
name: example-mcp-api
host: example.shwld.app
path: api/mcp
policy: Bypass
name: example-mcp-authz-server
host: example.shwld.app
path: .well-known/oauth-authorization-server
policy: Bypass
name: example-mcp-protected-resource
host: example.shwld.app
path: .well-known/oauth-protected-resource*
policy: Bypass
name: example-mcp-register
host: example.shwld.app
path: oauth/register
policy: Bypass
name: example-mcp-token
host: example.shwld.app
path: oauth/token
policy: 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 を消したところ、通常のログイン画面のボタンが飛ぶ /cdn-cgi/access/login も使えなくなりました。
今回の UI は Cloudflare Access 前提だったため、
UI は root app が必要
MCP は一部 path だけ root app を回避する必要がある
という両立が必要でした。
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 です。
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 Unauthorized
WWW-Authenticate ヘッダーあり
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