Skip to content
Cloudflare Docs

Authorization

When building a Model Context Protocol (MCP) server, you need both a way to allow users to login (authentication) and allow them to grant the MCP client access to resources on their account (authorization).

The Model Context Protocol uses a subset of OAuth 2.1 for authorization. OAuth allows your users to grant limited access to resources, without them having to share API keys or other credentials.

Cloudflare provides an OAuth Provider Library that implements the provider side of the OAuth 2.1 protocol, allowing you to easily add authorization to your MCP server.

You can use the OAuth Provider Library in four ways:

  1. Use Cloudflare Access as an OAuth provider.
  2. Integrate directly with a third-party OAuth provider, such as GitHub or Google.
  3. Integrate with your own OAuth provider, including authorization-as-a-service providers you might already rely on, such as Stytch, Auth0, or WorkOS.
  4. Your Worker handles authorization and authentication itself. Your MCP server, running on Cloudflare, handles the complete OAuth flow.

The following sections describe each of these options and link to runnable code examples for each.

Authorization options

(1) Cloudflare Access OAuth provider

Cloudflare Access allows you to add Single Sign-On (SSO) functionality to your MCP server. Users authenticate to your MCP server using a configured identity provider or a one-time PIN, and they are only granted access if their identity matches your Access policies.

To deploy an example MCP server with Cloudflare Access as the OAuth provider, refer to Secure MCP servers with Access for SaaS.

(2) Third-party OAuth Provider

The OAuth Provider Library can be configured to use a third-party OAuth provider, such as GitHub or Google. You can see a complete example of this in the GitHub example.

When you use a third-party OAuth provider, you must provide a handler to the OAuthProvider that implements the OAuth flow for the third-party provider.

TypeScript
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",
});

Note that as defined in the Model Context Protocol specification when you use a third-party OAuth provider, the MCP Server (your Worker) generates and issues its own token to the MCP client:

sequenceDiagram
    participant B as User-Agent (Browser)
    participant C as MCP Client
    participant M as MCP Server (your Worker)
    participant T as Third-Party Auth Server

    C->>M: Initial OAuth Request
    M->>B: Redirect to Third-Party /authorize
    B->>T: Authorization Request
    Note over T: User authorizes
    T->>B: Redirect to MCP Server callback
    B->>M: Authorization code
    M->>T: Exchange code for token
    T->>M: Third-party access token
    Note over M: Generate bound MCP token
    M->>B: Redirect to MCP Client callback
    B->>C: MCP authorization code
    C->>M: Exchange code for token
    M->>C: MCP access token

Read the docs for the Workers OAuth Provider Library for more details.

(3) Bring your own OAuth Provider

If your application already implements an OAuth Provider itself, or you use an authorization-as-a-service provider, you can use this in the same way that you would use a third-party OAuth provider, described above in (2) Third-party OAuth Provider.

You can use the auth provider to:

  • Allow users to authenticate to your MCP server through email, social logins, SSO (single sign-on), and MFA (multi-factor authentication).
  • Define scopes and permissions that directly map to your MCP tools.
  • Present users with a consent page corresponding with the requested permissions.
  • Enforce the permissions so that agents can only invoke permitted tools.

Stytch

Get started with a remote MCP server that uses Stytch to allow users to sign in with email, Google login or enterprise SSO and authorize their AI agent to view and manage their company's OKRs on their behalf. Stytch will handle restricting the scopes granted to the AI agent based on the user's role and permissions within their organization. When authorizing the MCP Client, each user will see a consent page that outlines the permissions that the agent is requesting that they are able to grant based on their role.

Deploy to Cloudflare

For more consumer use cases, deploy a remote MCP server for a To Do app that uses Stytch for authentication and MCP client authorization. Users can sign in with email and immediately access the To Do lists associated with their account, and grant access to any AI assistant to help them manage their tasks.

Deploy to Cloudflare

Auth0

Get started with a remote MCP server that uses Auth0 to authenticate users through email, social logins, or enterprise SSO to interact with their todos and personal data through AI agents. The MCP server securely connects to API endpoints on behalf of users, showing exactly which resources the agent will be able to access once it gets consent from the user. In this implementation, access tokens are automatically refreshed during long running interactions.

To set it up, first deploy the protected API endpoint:

Deploy to Cloudflare

Then, deploy the MCP server that handles authentication through Auth0 and securely connects AI agents to your API endpoint.

Deploy to Cloudflare

WorkOS

Get started with a remote MCP server that uses WorkOS's AuthKit to authenticate users and manage the permissions granted to AI agents. In this example, the MCP server dynamically exposes tools based on the user's role and access rights. All authenticated users get access to the add tool, but only users who have been assigned the image_generation permission in WorkOS can grant the AI agent access to the image generation tool. This showcases how MCP servers can conditionally expose capabilities to AI agents based on the authenticated user's role and permission.

Deploy to Cloudflare

Descope

Get started with a remote MCP server that uses Descope Inbound Apps to authenticate and authorize users (for example, email, social login, SSO) to interact with their data through AI agents. Leverage Descope custom scopes to define and manage permissions for more fine-grained control.

Deploy to Cloudflare

(4) Your MCP Server handles authorization and authentication itself

Your MCP Server, using the OAuth Provider Library, can handle the complete OAuth authorization flow, without any third-party involvement.

The Workers OAuth Provider Library is a Cloudflare Worker that implements a fetch() handler, and handles incoming requests to your MCP server.

You provide your own handlers for your MCP Server's API, and authentication and authorization logic, and URI paths for the OAuth endpoints, as shown below:

TypeScript
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",
});

Refer to the getting started example for a complete example of the OAuthProvider in use, with a mock authentication flow.

The authorization flow in this case works like this:

sequenceDiagram
    participant B as User-Agent (Browser)
    participant C as MCP Client
    participant M as MCP Server (your Worker)

    C->>M: MCP Request
    M->>C: HTTP 401 Unauthorized
    Note over C: Generate code_verifier and code_challenge
    C->>B: Open browser with authorization URL + code_challenge
    B->>M: GET /authorize
    Note over M: User logs in and authorizes
    M->>B: Redirect to callback URL with auth code
    B->>C: Callback with authorization code
    C->>M: Token Request with code + code_verifier
    M->>C: Access Token (+ Refresh Token)
    C->>M: MCP Request with Access Token
    Note over C,M: Begin standard MCP message exchange

Remember — authentication is different from authorization. Your MCP Server can handle authorization itself, while still relying on an external authentication service to first authenticate users. The example in getting started provides a mock authentication flow. You will need to implement your own authentication handler — either handling authentication yourself, or using an external authentication services.

Using authentication context in tools

When a user authenticates through the OAuth Provider, their identity information is available inside your tools. How you access it depends on whether you use McpAgent or createMcpHandler.

With McpAgent

The third type parameter on McpAgent defines the shape of the authentication context. Access it via this.props inside init() and tool handlers.

TypeScript
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}!` }],
}));
}
}

With createMcpHandler

Use getMcpAuthContext() to access the same information from within a tool handler. This uses AsyncLocalStorage under the hood.

TypeScript
import { createMcpHandler, getMcpAuthContext } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
function createServer() {
const server = new McpServer({ name: "Auth Demo", version: "1.0.0" });
server.tool("whoami", "Get the current user", {}, async () => {
const auth = getMcpAuthContext();
const name = (auth?.props?.name as string) ?? "anonymous";
return {
content: [{ type: "text", text: `Hello, ${name}!` }],
};
});
return server;
}

Permission-based tool access

You can control which tools are available based on user permissions. There are two approaches: check permissions inside the tool handler, or conditionally register tools.

TypeScript
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" }],
}));
}
}
}

Checking inside the handler returns an error message to the LLM, which can explain the denial to the user. Conditionally registering tools means the LLM never sees tools the user cannot access — it cannot attempt to call them at all.

Next steps