本文へ移動
AI開発 AI生成 文責 blackstrawberry

workers-oauth-provider v1、MCP の認可サーバーを別 Worker に分ける構成

Cloudflare の workers-oauth-provider v1 で認可サーバーと MCP サーバーを別 Worker に分離できる。MCP 2026-07-28 対応と 0.x からの移行点を整理する。

Cloudflare Workers で MCP サーバーの OAuth を組んでいるなら、@cloudflare/workers-oauth-provider の v1 で取れる構成が一つ増えた。サインインとトークン発行は認可サーバー役の Worker に任せ、MCP サーバーはリソースサーバーとして別の Worker で動かせる。トークンを受け取った MCP サーバーは Service Binding で認可サーバーに検証を頼むので、その通信は公開インターネットを通らない。Cloudflare の changelog(2026年10月1日)にそう書かれている。

今の構成をすぐに作り替える必要はない。認可サーバーと MCP サーバーを OAuthProvider 一つで兼ねる従来の書き方も v1 で使える。changelog によると、0.x の大半のデプロイで必須の変更は resourceMetadata: { resource } の追加だけだ。ただし公式 migration guide では、使っている API によって追加変更が必要になる。例えば resourceMatchOriginOnly は削除、resolveExternalToken は audience の返却が必要で、該当するコードがないか先に検索するよう案内している。

Worker を分けると運用で何が変わるか

changelog が挙げる分割のメリットは、どれも運用面の話だ。

  • 認可サーバーとリソースサーバーに、別々の WAF ルールとレート制限ルールをかけられる
  • 一つの認可サーバーから、複数の MCP サーバー向けにトークンを発行できる
  • 例の calendar Worker には専用の KV namespace が要らない(検証は Service Binding 越しに認可サーバーが行う)

サインイン画面を狙うトラフィックと MCP の呼び出しは性質が違う。Worker ごとにルールを変えられれば、片方に合わせて締めたルールでもう片方が詰まることがなくなる。

リソースサーバー側のコード

公式の例から、calendar MCP の Worker 部分を抜き出す。

// calendar MCP Worker: checks tokens with AuthServer over a Service Binding.
export const calendar = new OAuthResourceServer<Env, AuthProps>({
  resourceMetadata: {
    resource: "https://calendar.example.com/mcp",
    authorization_servers: ["https://auth.example.com"],
  },
  requiredScopes: ["calendar:read"],
  validateToken: (env) => env.AUTH_SERVER.validateToken,
  handler: {
    fetch(request, env, ctx) {
      if (
        request.method === "POST" &&
        !ctx.auth.scope.includes("calendar:write")
      ) {
        return insufficientScope(ctx.auth, ["calendar:read", "calendar:write"]);
      }
      return handleMcp(request, ctx.props);
    },
  },
});

env.AUTH_SERVER.validateToken の呼び出しが Service Binding だ。バインディングは wrangler の設定で、認可サーバー側 Worker のエントリポイント AuthServer を指す。

{
  "name": "calendar-mcp",
  "main": "src/index.ts",
  // Set this to today's date
  "compatibility_date": "2026-10-02",
  "services": [
    {
      "binding": "AUTH_SERVER",
      "service": "auth-server",
      "entrypoint": "AuthServer",
    },
  ],
}

OAuthResourceServer は、MCP クライアントが認可サーバーを見つけるための RFC 9728 の protected resource metadata を公開する。トークンなしのリクエストには、そのメタデータを指す 401 のチャレンジを返す。別のリソース向けに発行されたトークンは拒否するので、同じ認可サーバーから calendar と drive の両方にトークンを出しても、drive 用のトークンで calendar は叩けない。

認可サーバー側は OAuthAuthorizationServer に issuer、発行先の resources(例では calendar と drive の 2 つの URL)、scopesSupported を渡す。WorkerEntrypoint を継承した AuthServer クラスが validateToken(resource, token) を公開し、これがリソースサーバーから呼ばれる。/authorize は自前の同意画面に回し、それ以外のパスはライブラリの fetch に流す形になっている。

MCP 2026-07-28 と既存クライアント

v1 は MCP 2026-07-28 の認可仕様に対応し、Client ID Metadata Documents と issuer identification を含む。changelog は、Dynamic Client Registration を使う古いクライアントも引き続き動くとしている。

公式の例では、認可サーバーの設定に clientIdMetadataDocumentEnabled: true を明示している。省略した場合の既定値は changelog からは分からない。新しい仕様のクライアントを受け入れたいなら、この値を明示しておくほうが確実だ。

スコープが足りないときの step-up

上のコードの insufficientScope() が step-up 認可の部分だ。最初は requiredScopes で calendar:read だけを要求し、書き込み(POST)の時点で calendar:write がなければ、必要なスコープを添えて追加の認可を求める。changelog の言い方では「1行で」書ける。返るレスポンスの具体的な形式までは changelog に載っていない。

0.x から上げるときに見るもの

更新は npm i @cloudflare/workers-oauth-provider@latest(yarn、pnpm、bun の例もある)。npm パッケージに移行用の skill が同梱されていて、コーディングエージェントに node_modules/@cloudflare/workers-oauth-provider/skills/migrate-to-1.0/SKILL.md を読ませれば更新作業を任せられる。

手で進める場合、公式 migration guide の「Am I affected?」で自分のコードにある項目だけを拾う。0.x から 1.x では少なくとも次を確認する。

  • OAuthProvider に resourceMetadata がなければ canonical resource を追加する
  • resourceMatchOriginOnly を使っていれば削除する
  • resolveExternalToken を使っていれば、検証したトークンの audience を返す
  • 最新の 1.x まで上げる場合は、requiredScopes、redirect URI、トークンやクライアントの有効期限など、guide に列挙された後続変更も使用箇所だけ確認する

分割 API は必須ではない。既存 OAuthProvider の互換対応を先に終え、その後に WAF やレート制限を分けたい場合だけ Worker を分割する、という段階的な移行もできる。v1 ではほかに次のものが入った。

  • MCP の confused deputy 対策を実装した同意ページとアップストリームサインインのヘルパー
  • refreshTokenIdleTTL によるリフレッシュトークンのスライディング有効期限
  • purgeExpiredData() による再開可能な KV のクリーンアップ
  • onError に渡るすべてのエラーへの内部 reason の付与

自前で同意画面や上流 IdP へのサインインを書いている場合は、confused deputy 対策のヘルパーに置き換えられるかを最初に確認しておきたい。

出典 種別について