Model Context Protocol (MCP) ↗ サーバーを構築するときは、ユーザーのログイン(認証)と、MCP クライアントがアカウント上のリソースへアクセスする許可(認可)の両方が必要です。
Model Context Protocol は認可に OAuth 2.1 のサブセット ↗ を使います。OAuth により、ユーザーは API キーなどの資格情報を共有せずに、リソースへの限定アクセスを許可できます。
Cloudflare は OAuth Provider Library ↗ を提供します。OAuth 2.1 プロトコルのプロバイダー側を実装しており、MCP サーバーへ認可を簡単に追加できます。
OAuth Provider Library の使い方は 4 つです。
- Cloudflare Access を OAuth プロバイダーとして使います。
- GitHub や Google などのサードパーティ OAuth プロバイダーと直接連携します。
- 独自の OAuth プロバイダーと連携します。すでに使っている authorization-as-a-service(Stytch、Auth0、WorkOS など)も含みます。
- Worker 自身が認可と認証を扱います。Cloudflare 上の MCP サーバーが OAuth フロー全体を処理します。
以降のセクションで各オプションを説明し、実行可能なコード例へリンクします。
Cloudflare Access を使うと、MCP サーバーへシングルサインオン(SSO)を追加できます。ユーザーは 設定済みの ID プロバイダー または ワンタイム PIN で MCP サーバーに認証します。Access ポリシー に ID が一致する場合だけアクセスが許可されます。
Cloudflare Access を OAuth プロバイダーにした MCP サーバーの例 ↗ をデプロイするには、Access for SaaS で MCP サーバーを保護する を参照してください。
OAuth Provider Library ↗ は、GitHub や Google などのサードパーティ OAuth プロバイダーを使うよう設定できます。完全な例は GitHub の例 を参照してください。
サードパーティ OAuth プロバイダーを使う場合、そのプロバイダーの OAuth フローを実装するハンドラーを OAuthProvider に渡す必要があります。
import MyAuthHandler from "./auth-handler";
export default new OAuthProvider({
apiRoute: "/mcp",
// Your MCP server:
apiHandler: MyMCPServer.serve("/mcp"),
// Replace this handler with your own handler for authentication and authorization with the third-party provider:
defaultHandler: MyAuthHandler,
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
clientRegistrationEndpoint: "/register",
});Model Context Protocol 仕様の定義 ↗ どおり、サードパーティ OAuth プロバイダーを使うとき、MCP サーバー(Worker)は MCP クライアントへ独自のトークンを生成して発行します。
sequenceDiagram
participant B as ユーザーエージェント(ブラウザ)
participant C as MCP クライアント
participant M as MCP サーバー(Worker)
participant T as サードパーティ認証サーバー
C->>M: 最初の OAuth リクエスト
M->>B: サードパーティの /authorize へリダイレクト
B->>T: 認可リクエスト
Note over T: ユーザーが認可
T->>B: MCP サーバーのコールバックへリダイレクト
B->>M: 認可コード
M->>T: コードをトークンと交換
T->>M: サードパーティのアクセストークン
Note over M: 紐付けた MCP トークンを生成
M->>B: MCP クライアントのコールバックへリダイレクト
B->>C: MCP 認可コード
C->>M: コードをトークンと交換
M->>C: MCP アクセストークン
詳細は Workers OAuth Provider Library ↗ のドキュメントを読んでください。
アプリがすでに OAuth プロバイダーを実装している、または authorization-as-a-service を使っている場合は、(2) サードパーティ OAuth プロバイダー と同じ方法で使えます。
認証プロバイダーで次ができます。
- メール、ソーシャルログイン、SSO(シングルサインオン)、MFA(多要素認証)で、ユーザーが MCP サーバーに認証できるようにします。
- MCP ツールに直接対応するスコープと権限を定義します。
- 要求された権限に対応する同意ページをユーザーに表示します。
- 権限を強制し、エージェントが許可されたツールだけを呼べるようにします。
ユーザーがメール、Google ログイン、またはエンタープライズ SSO でサインインし、AI エージェントに自社の OKR の閲覧と管理を許可できる Stytch を使うリモート MCP サーバー ↗ から始めます。Stytch は、組織内のユーザーのロールと権限に基づいて、AI エージェントへ付与するスコープを制限します。MCP クライアントを認可するとき、各ユーザーは同意ページを見ます。エージェントが要求する権限のうち、自分のロールで付与できるものが示されます。
コンシューマー向けには、認証と MCP クライアント認可に Stytch を使う To Do アプリのリモート MCP サーバーをデプロイします。ユーザーはメールでサインインし、アカウントに紐づく To Do リストへすぐアクセスできます。任意の AI アシスタントへ、タスク管理の手伝いを許可できます。
Auth0 でユーザーをメール、ソーシャルログイン、またはエンタープライズ SSO で認証し、AI エージェント経由で To Do と個人データにアクセスするリモート MCP サーバーから始めます。MCP サーバーはユーザーの代わりに API エンドポイントへ安全に接続し、同意後にエージェントがアクセスできるリソースを明示します。この実装では、長時間のやり取り中にアクセストークンが自動更新されます。
セットアップでは、まず保護された API エンドポイントをデプロイします。
次に、Auth0 で認証を扱い、AI エージェントを API エンドポイントへ安全に接続する MCP サーバーをデプロイします。
WorkOS の AuthKit でユーザーを認証し、AI エージェントへ付与する権限を管理するリモート MCP サーバーから始めます。この例では、ユーザーのロールとアクセス権に応じて、MCP サーバーがツールを動的に公開します。認証済みユーザーは全員 add ツールを使えます。WorkOS で image_generation 権限を割り当てられたユーザーだけが、画像生成ツールへのアクセスを AI エージェントに許可できます。認証済みユーザーのロールと権限に基づいて、MCP サーバーが AI エージェントへ機能を条件付きで公開する例です。
Descope ↗ Inbound Apps でユーザーを認証・認可し(メール、ソーシャルログイン、SSO など)、AI エージェント経由でデータへアクセスするリモート MCP サーバーから始めます。Descope のカスタムスコープで権限を定義・管理し、より細かく制御できます。
OAuth Provider Library ↗ を使う MCP サーバーは、サードパーティを介さず OAuth 認可フロー全体を扱えます。
Workers OAuth Provider Library ↗ は fetch() ハンドラー を実装する Cloudflare Worker で、MCP サーバーへの受信リクエストを処理します。
MCP サーバーの API、認証と認可のロジック、OAuth エンドポイントの URI パスは、次のように自分で渡します。
export default new OAuthProvider({
apiRoute: "/mcp",
// Your MCP server:
apiHandler: MyMCPServer.serve("/mcp"),
// Your handler for authentication and authorization:
defaultHandler: MyAuthHandler,
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
clientRegistrationEndpoint: "/register",
});OAuthProvider の完全な使用例(モック認証フロー付き)は Getting started の例 を参照してください。
この場合の認可フローは次のとおりです。
sequenceDiagram
participant B as ユーザーエージェント(ブラウザ)
participant C as MCP クライアント
participant M as MCP サーバー(Worker)
C->>M: MCP リクエスト
M->>C: HTTP 401 Unauthorized
Note over C: code_verifier と code_challenge を生成
C->>B: 認可 URL + code_challenge でブラウザを開く
B->>M: GET /authorize
Note over M: ユーザーがログインして認可
M->>B: 認可コード付きでコールバック URL へリダイレクト
B->>C: 認可コード付きコールバック
C->>M: コード + code_verifier でトークンリクエスト
M->>C: アクセストークン(+ リフレッシュトークン)
C->>M: アクセストークン付き MCP リクエスト
Note over C,M: 通常の MCP メッセージ交換を開始
覚えておいてください。認証と認可は異なります ↗。MCP サーバーは認可を自分で扱いながら、最初のユーザー認証は外部の認証サービスに任せられます。Getting started の例 はモック認証フローです。認証ハンドラーは自分で実装する必要があります。自分で認証するか、外部の認証サービスを使います。
ユーザーが OAuth Provider 経由で認証すると、ツール内で ID 情報を使えます。アクセス方法は McpAgent と createMcpHandler のどちらを使うかで変わります。
このパターンは、非推奨の既存 McpAgent ルートにだけ適用されます。第 3 型パラメーターが認証コンテキストの形を定義します。init() とツールハンドラー内では this.props 経由でアクセスします。
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
type AuthContext = {
claims: { sub: string; name: string; email: string };
permissions: string[];
};
export class MyMCP extends McpAgent<Env, unknown, AuthContext> {
server = new McpServer({ name: "Auth Demo", version: "1.0.0" });
async init() {
this.server.tool("whoami", "Get the current user", {}, async () => ({
content: [{ type: "text", text: `Hello, ${this.props.claims.name}!` }],
}));
}
}互換のある Workers OAuth Provider は、context.http.authInfo に標準のトークンメタデータを渡します。既存アプリの props には getMcpAuthContext() を使います。
import { createMcpHandler, getMcpAuthContext } from "agents/mcp/server";
import { McpServer } from "@modelcontextprotocol/server";
function createServer() {
const server = new McpServer({ name: "Auth Demo", version: "1.0.0" });
server.registerTool(
"whoami",
{ description: "Get the current user", inputSchema: {} },
async (_args, context) => {
const auth = getMcpAuthContext();
const name = (auth?.props.name as string) ?? "anonymous";
return {
content: [
{
type: "text",
text: `${name}: ${context.http?.authInfo?.clientId}`,
},
],
};
},
);
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;生のアクセストークンをログに出したり返したりしないでください。
ユーザー権限に応じて、使えるツールを制御できます。方法は 2 つです。ツールハンドラー内で権限を確認する、または条件付きでツールを登録します。
export class MyMCP extends McpAgent<Env, unknown, AuthContext> {
server = new McpServer({ name: "Permissions Demo", version: "1.0.0" });
async init() {
this.server.tool("publicTool", "Available to all users", {}, async () => ({
content: [{ type: "text", text: "Public result" }],
}));
this.server.tool(
"adminAction",
"Requires admin permission",
{},
async () => {
if (!this.props.permissions?.includes("admin")) {
return {
content: [
{ type: "text", text: "Permission denied: requires admin" },
],
};
}
return {
content: [{ type: "text", text: "Admin action completed" }],
};
},
);
if (this.props.permissions?.includes("special_feature")) {
this.server.tool("specialTool", "Special feature", {}, async () => ({
content: [{ type: "text", text: "Special feature result" }],
}));
}
}
}ハンドラー内で確認すると、LLM へエラーメッセージが返り、拒否理由をユーザーへ説明できます。条件付きでツールを登録すると、ユーザーが使えないツールは LLM から見えません。呼び出そうとすること自体がありません。