Skip to content

Commit 438cf21

Browse files
ranbelmaxvp
andauthored
[ZT] Access MCP servers (#24156)
* define mcp servers * mcp client definition * edit client definition * add intro text and diagram * split content into two pages * SaaS app UI instructions * generic mcp server config * incorporate Workers example * fix link typo * api example * add API calls * access_token header * known limitation * non_identity decision * replace atlassian example * Update src/content/docs/cloudflare-one/applications/configure-apps/mcp-servers/linked-apps.mdx Co-authored-by: Max Phillips <[email protected]> --------- Co-authored-by: Max Phillips <[email protected]>
1 parent 002762f commit 438cf21

File tree

5 files changed

+370
-0
lines changed

5 files changed

+370
-0
lines changed

src/content/docs/cloudflare-one/applications/configure-apps/index.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ You can protect the following types of web applications:
2020
- [**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/).
2121
- [**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/).
2222

23+
- [**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.
24+
2325
- [**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.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
pcx_content_type: navigation
3+
title: MCP servers
4+
sidebar:
5+
order: 3
6+
group:
7+
hideIndex: true
8+
---
9+
10+
import { DirectoryListing } from "~/components";
11+
12+
<DirectoryListing />
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
pcx_content_type: how-to
3+
title: Authenticate MCP server to self-hosted apps
4+
sidebar:
5+
order: 2
6+
label: Enable MCP OAuth to self-hosted apps
7+
---
8+
9+
import { Render, GlossaryTooltip, APIRequest } from "~/components"
10+
11+
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.
12+
13+
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>.
14+
15+
```mermaid
16+
flowchart LR
17+
accTitle: Link MCP servers and self-hosted applications in Access
18+
subgraph SaaS["Access for SaaS <br> OIDC app"]
19+
mcp["MCP server <br> for internal apps"]
20+
end
21+
22+
subgraph "Access self-hosted app"
23+
app1[Admin dashboard]
24+
end
25+
26+
subgraph "Access self-hosted app"
27+
app2[Company wiki]
28+
end
29+
30+
User --> client["MCP client"]
31+
client --> mcp
32+
mcp -- Access token --> app1
33+
mcp -- Access token --> app2
34+
idp[Identity provider] <--> SaaS
35+
```
36+
37+
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.
38+
39+
## Prerequisites
40+
41+
- A [self-hosted Access application](/cloudflare-one/applications/configure-apps/self-hosted-public-app/)
42+
43+
## 1. Secure the MCP server with Access for SaaS
44+
45+
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/).
46+
47+
## 2. Get the SaaS application ID
48+
49+
Get the `id` of the MCP server SaaS application:
50+
51+
<APIRequest
52+
path="/accounts/{account_id}/access/apps"
53+
method="GET"
54+
/>
55+
56+
```json title="Response"
57+
{
58+
"id": "3537a672-e4d8-4d89-aab9-26cb622918a1",
59+
"uid": "3537a672-e4d8-4d89-aab9-26cb622918a1",
60+
"type": "saas",
61+
"name": "mcp-server-cf-access",
62+
...
63+
}
64+
```
65+
66+
## 3. Create an Access policy
67+
68+
1. Create the following Access policy, replacing the `app_uid` value with the `id` of your SaaS application:
69+
70+
<APIRequest
71+
path="/accounts/{account_id}/access/policies"
72+
method="POST"
73+
json={{
74+
name: "Allow MCP server",
75+
decision: "non_identity",
76+
include: [
77+
{
78+
linked_app_token: {
79+
app_uid: "3537a672-e4d8-4d89-aab9-26cb622918a1"
80+
}
81+
}
82+
]
83+
}}
84+
/>
85+
86+
:::note
87+
The `linked_app_token` rule type only works with [`non_identity` decisions](/cloudflare-one/policies/access/#service-auth), similar to service token rules.
88+
:::
89+
90+
2. Copy the Access policy `id` returned in the response:
91+
92+
```json title="Response" {5}
93+
{
94+
"created_at": "2025-08-06T20:06:23Z",
95+
"decision": "non_identity",
96+
"exclude": [],
97+
"id": "a38ab4d4-336d-4f49-9e97-eff8550c13fa",
98+
"include": [
99+
{
100+
"linked_app_token": {
101+
"app_uid": "6cdc3892-f9f1-4813-a5ce-38c2753e1208"
102+
}
103+
}
104+
],
105+
"name": "Allow MCP server",
106+
...
107+
}
108+
```
109+
110+
This policy will allow requests if they present a valid OAuth access token that was issued for the specified SaaS application.
111+
112+
## 4. Update the self-hosted application
113+
114+
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).
115+
116+
1. Get your existing self-hosted application configuration:
117+
118+
<APIRequest
119+
path="/accounts/{account_id}/access/apps/{app_id}"
120+
method="GET"
121+
/>
122+
123+
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.
124+
125+
<APIRequest
126+
path="/accounts/{account_id}/access/apps/{app_id}"
127+
method="PUT"
128+
json={{
129+
policies: [
130+
"a38ab4d4-336d-4f49-9e97-eff8550c13fa"
131+
],
132+
}}
133+
/>
134+
135+
## 5. Configure the MCP server
136+
137+
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:
138+
139+
```txt
140+
Authorization: Bearer ACCESS_TOKEN
141+
```
142+
143+
The end-to-end authorization flow is as follows:
144+
1. The MCP server authenticates against the Access for SaaS app via OAuth.
145+
2. Upon success, the MCP server receives an `access_token`.
146+
3. The MCP server makes an API request to the self-hosted application with the token in the request headers.
147+
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.
148+
5. If the token is valid and was issued for the linked SaaS app, the request is allowed. Otherwise, it is blocked.
149+
150+
## Known limitations
151+
152+
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 an HTTP `401` or `403` error.
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
---
2+
pcx_content_type: how-to
3+
title: Secure MCP servers with Access for SaaS
4+
sidebar:
5+
order: 1
6+
label: Secure MCP servers with Access for SaaS
7+
---
8+
9+
import { Render, GlossaryTooltip, Tabs, TabItem, APIRequest } from "~/components"
10+
11+
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.
12+
13+
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).
14+
15+
## Prerequisites
16+
17+
- Add an [identity provider](/cloudflare-one/identity/idp-integration/) to Cloudflare Zero Trust
18+
- Install [npm](https://docs.npmjs.com/getting-started)
19+
- Install [Node.js](https://nodejs.org/en/)
20+
21+
## 1. Deploy an example MCP server
22+
23+
To deploy our [example MCP server](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-cf-access) on Workers:
24+
25+
1. Open a terminal and clone our example project:
26+
27+
```sh
28+
npm create cloudflare@latest -- mcp-server-cf-access --template=cloudflare/ai/demos/remote-mcp-cf-access
29+
```
30+
31+
When asked if you want to deploy to Cloudflare, select **No**.
32+
33+
2. Go to the project directory:
34+
35+
```sh
36+
cd mcp-server-cf-access
37+
```
38+
39+
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.
40+
41+
```sh
42+
npx wrangler kv namespace create "OAUTH_KV"
43+
```
44+
45+
The command will output the binding name and KV namespace ID:
46+
47+
```sh output
48+
{
49+
"kv_namespaces": [
50+
{
51+
"binding": "OAUTH_KV",
52+
"id": "<YOUR_KV_NAMESPACE_ID>"
53+
}
54+
]
55+
}
56+
```
57+
58+
4. Open `wrangler.jsonc` in an editor and insert your `OAUTH_KV` namespace ID:
59+
60+
```jsonc
61+
"kv_namespaces": [
62+
{
63+
"binding": "OAUTH_KV",
64+
"id": "<YOUR_KV_NAMESPACE_ID>"
65+
}
66+
],
67+
```
68+
69+
70+
5. You can now deploy the Worker to Cloudflare's global network:
71+
72+
```sh
73+
npx wrangler deploy
74+
```
75+
76+
The Worker will be deployed to your `*.workers.dev` subdomain at `mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev`.
77+
78+
## 2. Create an Access for SaaS app
79+
80+
<Tabs syncKey="dashPlusAPI">
81+
<TabItem label="Dashboard">
82+
83+
1. In [Zero Trust](https://one.dash.cloudflare.com), go to **Access** > **Applications**.
84+
2. Select **SaaS**.
85+
3. In **Application**, enter a custom name (for example, `MCP server`) and select the textbox that appears below.
86+
4. Select **OIDC** as the authentication protocol.
87+
5. Select **Add application**.
88+
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
89+
```txt
90+
https://mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev/callback
91+
```
92+
7. Copy the following values to input into our example MCP server. Other MCP servers may require different sets of input values.
93+
- **Client secret**
94+
- **Client ID**
95+
- **Token endpoint**
96+
- **Authorization endpoint**
97+
- **Key endpoint**
98+
99+
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.
100+
9. Configure [Access policies](/cloudflare-one/policies/access/) to define the users who can access the MCP server.
101+
10. Save the application.
102+
103+
</TabItem>
104+
<TabItem label="API">
105+
106+
1. Make a `POST` request to the [Access applications](/api/resources/zero_trust/subresources/access/subresources/applications/methods/create/) endpoint:
107+
108+
<APIRequest
109+
path="/accounts/{account_id}/access/apps"
110+
method="POST"
111+
json={{
112+
name: "MCP server",
113+
type: "saas",
114+
saas_app: {
115+
auth_type: "oidc",
116+
redirect_uris: [
117+
"https://mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev/callback"
118+
],
119+
grant_type: [
120+
"authorization_code",
121+
"refresh_tokens"
122+
],
123+
refresh_token_options: {
124+
lifetime: "90d"
125+
}
126+
},
127+
policies: [
128+
"f174e90a-fafe-4643-bbbc-4a0ed4fc8415"
129+
],
130+
allowed_idps: []
131+
}}
132+
/>
133+
134+
2. Copy the `client_id` and `client_secret` returned in the response.
135+
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).
136+
137+
</TabItem>
138+
</Tabs>
139+
140+
## 3. Configure your MCP server
141+
142+
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.
143+
144+
To add OAuth endpoints and credentials to our [example MCP server](#1-deploy-an-example-mcp-server):
145+
146+
1. Create the following [Workers secrets](/workers/configuration/secrets/):
147+
148+
```sh
149+
wrangler secret put ACCESS_CLIENT_ID
150+
wrangler secret put ACCESS_CLIENT_SECRET
151+
wrangler secret put ACCESS_TOKEN_URL
152+
wrangler secret put ACCESS_AUTHORIZATION_URL
153+
wrangler secret put ACCESS_JWKS_URL
154+
```
155+
156+
2. When prompted to enter a secret value, paste the corresponding values from your SaaS app:
157+
158+
| Workers secret | SaaS app field |
159+
| ------------- | -------------- |
160+
| `ACCESS_CLIENT_ID`| Client ID |
161+
| `ACCESS_CLIENT_SECRET` | Client secret |
162+
| `ACCESS_TOKEN_URL` | Token endpoint |
163+
| `ACCESS_AUTHORIZATION_URL` | Authorization endpoint |
164+
| `ACCESS_JWKS_URL` | Key endpoint |
165+
166+
3. Configure a cookie encryption key:
167+
168+
a. Generate a random string:
169+
170+
```sh
171+
openssl rand -hex 32
172+
```
173+
174+
b. Store the string in a Workers secret:
175+
176+
```sh
177+
wrangler secret put COOKIE_ENCRYPTION_KEY
178+
```
179+
180+
## 4. Test the connection
181+
182+
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.
183+
184+
To test in Workers AI Playground:
185+
186+
1. Go to [Workers AI Playground](https://playground.ai.cloudflare.com/).
187+
188+
2. Under **MCP Servers**, enter `https://mcp-server-cf-access.<YOUR_SUBDOMAIN>.workers.dev/sse` for the MCP server URL.
189+
190+
3. Select **Connect**.
191+
192+
4. A popup window will appear requesting access to the MCP server. Select **Approve**.
193+
194+
5. Follow the prompts to log in to your identity provider.
195+
196+
Workers AI Playground will show a **Connected** status. The MCP server should successfully obtain an `access_token` from Cloudflare Access.

src/content/glossary/cloudflare-one.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ entries:
119119
general_definition: |-
120120
a network location, such as an office, that is associated with a specific WARP client device profile.
121121
122+
- term: MCP client
123+
general_definition: |-
124+
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.
125+
126+
- term: MCP server
127+
general_definition: |-
128+
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.
129+
122130
- term: MDM file
123131
general_definition: |-
124132
a Mobile Device Management (MDM) file is a configuration file that allows organizations to manage the software, settings, and certificates installed on their devices.

0 commit comments

Comments
 (0)