Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ You can protect the following types of web applications:
- [**Public hostname applications**](/cloudflare-one/applications/configure-apps/self-hosted-public-app/) are web applications that have public DNS records. Anyone on the Internet can access the application by entering the URL in their browser and authenticating through Cloudflare Access. Securing access to a public website requires a Cloudflare DNS [full setup](/dns/zone-setups/full-setup/) or [partial CNAME setup](/dns/zone-setups/partial-setup/).
- [**Private network applications**](/cloudflare-one/applications/non-http/self-hosted-private-app/) do not have public DNS records, meaning they are not reachable from the public Internet. To connect using a private IP or private hostname, the user's traffic must route through Cloudflare Gateway. The preferred method is to install the WARP client on the user's device, but you could also forward device traffic from a [network location](/magic-wan/) or use an agentless option such as [PAC files](/cloudflare-one/connections/connect-devices/agentless/pac-files/) or [Clientless Web Isolation](/cloudflare-one/policies/browser-isolation/setup/clientless-browser-isolation/).

- [**Model Context Protocol (MCP) servers**](/cloudflare-one/applications/configure-apps/mcp-servers/) are web applications that enable generative AI tools to read and write data within your business applications. For example, Salesforce provides an [MCP server](https://github.com/salesforcecli/mcp) for developers to interact with resources in their Salesforce tenant using GitHub Copilot or other AI code editors.

- [**Cloudflare Dashboard SSO**](/cloudflare-one/applications/configure-apps/dash-sso-apps/) is a special type of SaaS application that manages SSO settings for the Cloudflare dashboard and has limited permissions for administrator edits.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
pcx_content_type: navigation
title: MCP servers
sidebar:
order: 3
group:
hideIndex: true
---

import { DirectoryListing } from "~/components";

<DirectoryListing />
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
pcx_content_type: how-to
title: Authenticate MCP server to self-hosted apps
sidebar:
order: 2
label: Enable MCP OAuth to self-hosted apps
---

import { Render, GlossaryTooltip, APIRequest } from "~/components"

Cloudflare Access can delegate access from any [self-hosted application](/cloudflare-one/applications/configure-apps/self-hosted-public-app/) to an [Access for SaaS MCP server](/cloudflare-one/applications/configure-apps/mcp-servers/saas-mcp/) via [OAuth](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). The OAuth access token authorizes the MCP server to make requests to your self-hosted applications on behalf of the user, using the user's specific permissions and scopes.

For example, your organization may wish to deploy an MCP server that helps employees interact with internal applications. You can configure [Access policies](/cloudflare-one/policies/access/#selectors) to ensure that only authorized users can access those applications, either directly or by using an <GlossaryTooltip term="MCP client">MCP client</GlossaryTooltip>.

```mermaid
flowchart LR
accTitle: Link MCP servers and self-hosted applications in Access
subgraph SaaS["Access for SaaS <br> OIDC app"]
mcp["MCP server <br> for internal apps"]
end

subgraph "Access self-hosted app"
app1[Admin dashboard]
end

subgraph "Access self-hosted app"
app2[Company wiki]
end

User --> client["MCP client"]
client --> mcp
mcp -- Access token --> app1
mcp -- Access token --> app2
idp[Identity provider] <--> SaaS
```

This guide covers how to use the Cloudflare API to link a self-hosted application to a remote MCP server. The core of this feature is the `linked_app_token` rule type, which allows an Access policy on one application to accept OAuth access tokens generated for another.

## Prerequisites

- A [self-hosted Access application](/cloudflare-one/applications/configure-apps/self-hosted-public-app/)

## 1. Secure the MCP server with Access for SaaS

The first step is to add the MCP server to Cloudflare Access as an OIDC-based SaaS application. For step-by-step instructions on how to add an MCP server, refer to [Secure MCP servers with Access for SaaS](/cloudflare-one/applications/configure-apps/mcp-servers/saas-mcp/).

## 2. Get the SaaS application ID

Get the `id` of the MCP server SaaS application:

<APIRequest
path="/accounts/{account_id}/access/apps"
method="GET"
/>

```json title="Response"
{
"id": "3537a672-e4d8-4d89-aab9-26cb622918a1",
"uid": "3537a672-e4d8-4d89-aab9-26cb622918a1",
"type": "saas",
"name": "mcp-server-cf-access",
...
}
```

## 3. Create an Access policy

1. Create the following Access policy, replacing the `app_uid` value with the `id` of your SaaS application:

<APIRequest
path="/accounts/{account_id}/access/policies"
method="POST"
json={{
name: "Allow MCP server",
decision: "non_identity",
include: [
{
linked_app_token: {
app_uid: "3537a672-e4d8-4d89-aab9-26cb622918a1"
}
}
]
}}
/>

:::note
The `linked_app_token` rule type only works with [`non_identity` decisions](/cloudflare-one/policies/access/#service-auth), similar to service token rules.
:::

2. Copy the Access policy `id` returned in the response:

```json title="Response" {5}
{
"created_at": "2025-08-06T20:06:23Z",
"decision": "non_identity",
"exclude": [],
"id": "a38ab4d4-336d-4f49-9e97-eff8550c13fa",
"include": [
{
"linked_app_token": {
"app_uid": "6cdc3892-f9f1-4813-a5ce-38c2753e1208"
}
}
],
"name": "Allow MCP server",
...
}
```

This policy will allow requests if they present a valid OAuth access token that was issued for the specified SaaS application.

## 4. Update the self-hosted application

You can add the `linked_app_token` policy to any `self_hosted` application in your Zero Trust account. Other app types (such as `saas`) are [not currently supported](#known-limitations).

1. Get your existing self-hosted application configuration:

<APIRequest
path="/accounts/{account_id}/access/apps/{app_id}"
method="GET"
/>

2. Add the Access policy to the self-hosted application. To avoid overwriting your existing configuration, the `PUT` request body should contain all fields returned by the previous `GET` request.

<APIRequest
path="/accounts/{account_id}/access/apps/{app_id}"
method="PUT"
json={{
policies: [
"a38ab4d4-336d-4f49-9e97-eff8550c13fa"
],
}}
/>

## 5. Configure the MCP server

With the policy in place, every API request to the self-hosted application must now include a valid `access_token` from Cloudflare Access. You will need to configure the MCP server to forward the `access_token` in an HTTP request header:

```txt
Authorization: Bearer ACCESS_TOKEN
```

The end-to-end authorization flow is as follows:
1. The MCP server authenticates against the Access for SaaS app via OAuth.
2. Upon success, the MCP server receives an `access_token`.
3. The MCP server makes an API request to the self-hosted application with the token in the request headers.
4. Cloudflare Access intercepts the request to the self-hosted app, inspects the token, and validates it against the `linked_app_token` rule in the policy.
5. If the token is valid and was issued for the linked SaaS app, the request is allowed. Otherwise, it is blocked.

## Known limitations

The MCP OAuth feature only works with self-hosted applications that rely on the [Cloudflare Access JWT](/cloudflare-one/identity/authorization-cookie/validating-json/) to authenticate and identify the user. If the application implements its own layer of authentication after Cloudflare Access, then this feature is at best a partial solution. Requests that are successfully authenticated by Access may still be blocked by the application itself, resulting in a 401 or 403 error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
pcx_content_type: how-to
title: Secure MCP servers with Access for SaaS
sidebar:
order: 1
label: Secure MCP servers with Access for SaaS
---

import { Render, GlossaryTooltip, Tabs, TabItem, APIRequest } from "~/components"

You can secure <GlossaryTooltip term="MCP server">Model Context Protocol (MCP) servers</GlossaryTooltip> by using Cloudflare Access as an OAuth Single Sign-On (SSO) provider.

This guide walks through how to deploy a remote MCP server on [Cloudflare Workers](/workers/) that requires Cloudflare Access for authentication. When users connect to the MCP server using an <GlossaryTooltip term="MCP client">MCP client</GlossaryTooltip>, they will be prompted to log in to your [identity provider](/cloudflare-one/identity/idp-integration/) and are only granted access if they pass your [Access policies](/cloudflare-one/policies/access/#selectors).

## Prerequisites

- Add an [identity provider](/cloudflare-one/identity/idp-integration/) to Cloudflare Zero Trust
- Install [npm](https://docs.npmjs.com/getting-started)
- Install [Node.js](https://nodejs.org/en/)

## 1. Deploy an example MCP server

To deploy our [example MCP server](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-cf-access) on Workers:

1. Open a terminal and clone our example project:

```sh
npm create cloudflare@latest -- mcp-server-cf-access --template=cloudflare/ai/demos/remote-mcp-cf-access
```

When asked if you want to deploy to Cloudflare, select **No**.

2. Go to the project directory:

```sh
cd mcp-server-cf-access
```

3. Create a [Workers KV namespace](/kv/concepts/kv-namespaces/) to store the key. The binding name should be `OAUTH_KV` if you want to run the example as written.

```sh
npx wrangler kv namespace create "OAUTH_KV"
```

The command will output the binding name and KV namespace ID:

```sh output
{
"kv_namespaces": [
{
"binding": "OAUTH_KV",
"id": "<YOUR_KV_NAMESPACE_ID>"
}
]
}
```

4. Open `wrangler.jsonc` in an editor and insert your `OAUTH_KV` namespace ID:

```jsonc
"kv_namespaces": [
{
"binding": "OAUTH_KV",
"id": "<YOUR_KV_NAMESPACE_ID>"
}
],
```


5. You can now deploy the Worker to Cloudflare's global network:

```sh
npx wrangler deploy
```

The Worker will be deployed to your `*.workers.dev` subdomain at `mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev`.

## 2. Create an Access for SaaS app

<Tabs syncKey="dashPlusAPI">
<TabItem label="Dashboard">

1. In [Zero Trust](https://one.dash.cloudflare.com), go to **Access** > **Applications**.
2. Select **SaaS**.
3. In **Application**, enter a custom name (for example, `MCP server`) and select the textbox that appears below.
4. Select **OIDC** as the authentication protocol.
5. Select **Add application**.
6. In **Redirect URLs**, enter the authorization callback URL for your MCP server. The callback URL for our [example MCP server](#1-deploy-an-example-mcp-server) is
```txt
https://mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev/callback
```
7. Copy the following values to input into our example MCP server. Other MCP servers may require different sets of input values.
- **Client secret**
- **Client ID**
- **Token endpoint**
- **Authorization endpoint**
- **Key endpoint**

8. (Optional) Under **Advanced settings**, turn on [**Refresh tokens**](/cloudflare-one/applications/configure-apps/saas-apps/generic-oidc-saas/#advanced-settings) if you want to reduce the number of times a user needs to log in to the identity provider.
9. Configure [Access policies](/cloudflare-one/policies/access/) to define the users who can access the MCP server.
10. Save the application.

</TabItem>
<TabItem label="API">

1. Make a `POST` request to the [Access applications](/api/resources/zero_trust/subresources/access/subresources/applications/methods/create/) endpoint:

<APIRequest
path="/accounts/{account_id}/access/apps"
method="POST"
json={{
name: "MCP server",
type: "saas",
saas_app: {
auth_type: "oidc",
redirect_uris: [
"https://mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev/callback"
],
grant_type: [
"authorization_code",
"refresh_tokens"
],
refresh_token_options: {
lifetime: "90d"
}
},
policies: [
"f174e90a-fafe-4643-bbbc-4a0ed4fc8415"
],
allowed_idps: []
}}
/>

2. Copy the `client_id` and `client_secret` returned in the response.
3. To determine the OAuth endpoint URLs for the SaaS application, refer to the [generic OIDC documentation](/cloudflare-one/applications/configure-apps/saas-apps/generic-oidc-saas/#2-add-your-application-to-access).

</TabItem>
</Tabs>

## 3. Configure your MCP server

Your MCP server needs to perform an OAuth 2.0 authorization flow to get an `access_token` from the SaaS app created in [Step 1](#1-create-an-access-for-saas-app). When setting up the OAuth client on your MCP server, you will need to paste in the OAuth endpoints and credentials from the SaaS app.

To add OAuth endpoints and credentials to our [example MCP server](#1-deploy-an-example-mcp-server):

1. Create the following [Workers secrets](/workers/configuration/secrets/):

```sh
wrangler secret put ACCESS_CLIENT_ID
wrangler secret put ACCESS_CLIENT_SECRET
wrangler secret put ACCESS_TOKEN_URL
wrangler secret put ACCESS_AUTHORIZATION_URL
wrangler secret put ACCESS_JWKS_URL
```

2. When prompted to enter a secret value, paste the corresponding values from your SaaS app:

| Workers secret | SaaS app field |
| ------------- | -------------- |
| `ACCESS_CLIENT_ID`| Client ID |
| `ACCESS_CLIENT_SECRET` | Client secret |
| `ACCESS_TOKEN_URL` | Token endpoint |
| `ACCESS_AUTHORIZATION_URL` | Authorization endpoint |
| `ACCESS_JWKS_URL` | Key endpoint |

3. Configure a cookie encryption key:

a. Generate a random string:

```sh
openssl rand -hex 32
```

b. Store the string in a Workers secret:

```sh
wrangler secret put COOKIE_ENCRYPTION_KEY
```

## 4. Test the connection

You should now be able to connect to your MCP server running at `https://mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev/sse` using [Workers AI Playground](https://playground.ai.cloudflare.com/), [MCP inspector](https://github.com/modelcontextprotocol/inspector), or [other MCP clients](/agents/guides/remote-mcp-server/#connect-your-mcp-server-to-claude-and-other-mcp-clients) that support remote MCP servers.

To test in Workers AI Playground:

1. Go to [Workers AI Playground](https://playground.ai.cloudflare.com/).

2. Under **MCP Servers**, enter `https://mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev/sse` for the MCP server URL.

3. Select **Connect**.

4. A popup window will appear requesting access to the MCP server. Select **Approve**.

5. Follow the prompts to log in to your identity provider.

Workers AI Playground will show a **Connected** status. The MCP server should successfully obtain an `access_token` from Cloudflare Access.
8 changes: 8 additions & 0 deletions src/content/glossary/cloudflare-one.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ entries:
general_definition: |-
a network location, such as an office, that is associated with a specific WARP client device profile.

- term: MCP client
general_definition: |-
a Model Context Protocol (MCP) client is a user-facing AI tool that can request information and receive responses from an MCP server. Examples of MCP clients include Claude Desktop, Cursor AI, and Windsurf.

- term: MCP server
general_definition: |-
a web application that allows generative AI tools to access third-party data sources and APIs using the Model Context Protocol (MCP). For example, you can use an MCP server to connect an AI assistant to your Google Drive account.

- term: MDM file
general_definition: |-
a Mobile Device Management (MDM) file is a configuration file that allows organizations to manage the software, settings, and certificates installed on their devices.
Expand Down
Loading