Skip to content

Commit 463ab4f

Browse files
committed
Test
1 parent 8360974 commit 463ab4f

File tree

6 files changed

+150
-97
lines changed

6 files changed

+150
-97
lines changed

apps/radar/.dev.vars.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
CLOUDFLARE_CLIENT_ID=
22
CLOUDFLARE_CLIENT_SECRET=
33
URL_SCANNER_API_TOKEN=
4+
DEV_DISABLE_OAUTH=
5+
DEV_CLOUDFLARE_API_TOKEN=
6+
DEV_CLOUDFLARE_EMAIL=

apps/radar/CONTRIBUTING.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Setup
2+
3+
## Local Development
4+
5+
If you'd like to iterate and test your MCP server, you can do so in local development.
6+
7+
1. Create a `.dev.vars` file in your project root:
8+
9+
If you're a Cloudflare employee:
10+
```
11+
CLOUDFLARE_CLIENT_ID=your_development_cloudflare_client_id
12+
CLOUDFLARE_CLIENT_SECRET=your_development_cloudflare_client_secret
13+
URL_SCANNER_API_TOKEN=your_development_url_scanner_api_token
14+
```
15+
16+
If you're an external contributor, you can provide a development API token like so:
17+
```
18+
DEV_DISABLE_OAUTH=true
19+
# This is your global api token
20+
DEV_CLOUDFLARE_API_TOKEN=your_development_api_token
21+
URL_SCANNER_API_TOKEN=your_development_url_scanner_api_token
22+
```
23+
24+
2. Start the local development server:
25+
26+
```bash
27+
npx wrangler dev
28+
```
29+
30+
3. To test locally, open Inspector, and connect to `http://localhost:8976/sse`.
31+
Once you follow the prompts, you'll be able to "List Tools".
32+
33+
You can also connect to Claude Desktop.
34+
35+
## Deploying the Worker ( Cloudflare employees only )
36+
37+
Set secrets via Wrangler:
38+
39+
```bash
40+
npx wrangler secret put CLOUDFLARE_CLIENT_ID -e <ENVIRONMENT>
41+
npx wrangler secret put CLOUDFLARE_CLIENT_SECRET -e <ENVIRONMENT>
42+
npx wrangler secret put URL_SCANNER_API_TOKEN -e <ENVIRONMENT>
43+
```
44+
45+
## Set up a KV namespace
46+
47+
Create the KV namespace:
48+
49+
```bash
50+
npx wrangler kv namespace create "OAUTH_KV"
51+
```
52+
53+
Then, update the Wrangler file with the generated KV namespace ID.
54+
55+
## Deploy & Test
56+
57+
Deploy the MCP server to make it available on your workers.dev domain:
58+
59+
```bash
60+
npx wrangler deploy -e <ENVIRONMENT>
61+
```
62+
63+
Test the remote server using [Inspector](https://modelcontextprotocol.io/docs/tools/inspector):
64+
65+
```bash
66+
npx @modelcontextprotocol/inspector@latest
67+
```

apps/radar/README.md

Lines changed: 1 addition & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -56,62 +56,4 @@ Once you restart Claude Desktop, a browser window will open showing your OAuth l
5656
Complete the authentication flow to grant Claude access to your MCP server.
5757
After you grant access, the tools will become available for you to use.
5858

59-
## Setup
60-
61-
#### Secrets
62-
63-
Set secrets via Wrangler:
64-
65-
```bash
66-
npx wrangler secret put CLOUDFLARE_CLIENT_ID -e <ENVIRONMENT>
67-
npx wrangler secret put CLOUDFLARE_CLIENT_SECRET -e <ENVIRONMENT>
68-
npx wrangler secret put URL_SCANNER_API_TOKEN -e <ENVIRONMENT>
69-
```
70-
71-
#### Set up a KV namespace
72-
73-
Create the KV namespace:
74-
75-
```bash
76-
npx wrangler kv namespace create "OAUTH_KV"
77-
```
78-
79-
Then, update the Wrangler file with the generated KV namespace ID.
80-
81-
#### Deploy & Test
82-
83-
Deploy the MCP server to make it available on your workers.dev domain:
84-
85-
```bash
86-
npx wrangler deploy -e <ENVIRONMENT>
87-
```
88-
89-
Test the remote server using [Inspector](https://modelcontextprotocol.io/docs/tools/inspector):
90-
91-
```bash
92-
npx @modelcontextprotocol/inspector@latest
93-
```
94-
95-
## Local Development
96-
97-
If you'd like to iterate and test your MCP server, you can do so in local development.
98-
This will require you to create another OAuth App on Cloudflare:
99-
100-
1. Create a `.dev.vars` file in your project root with:
101-
102-
```
103-
CLOUDFLARE_CLIENT_ID=your_development_cloudflare_client_id
104-
CLOUDFLARE_CLIENT_SECRET=your_development_cloudflare_client_secret
105-
URL_SCANNER_API_TOKEN=your_development_url_scanner_api_token
106-
```
107-
108-
2. Start the local development server:
109-
110-
```bash
111-
npx wrangler dev
112-
```
113-
114-
3. To test locally, open Inspector, and connect to `http://localhost:8976/sse`.
115-
Once you follow the prompts, you'll be able to "List Tools".
116-
117-
You can also connect to Claude Desktop.
59+
Interested in contributing, and running this server locally? See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.

apps/radar/src/context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ export interface Env {
1111
URL_SCANNER_API_TOKEN: string
1212
MCP_OBJECT: DurableObjectNamespace<RadarMCP>
1313
MCP_METRICS: AnalyticsEngineDataset
14+
DEV_DISABLE_OAUTH: string
15+
DEV_CLOUDFLARE_API_TOKEN: string
16+
DEV_CLOUDFLARE_EMAIL: string
1417
}

apps/radar/src/index.ts

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { McpAgent } from 'agents/mcp'
33

44
import {
55
createAuthHandlers,
6+
getUserAndAccounts,
67
handleTokenExchangeCallback,
78
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
89
import { getEnv } from '@repo/mcp-common/src/env'
@@ -74,16 +75,41 @@ const RadarScopes = {
7475
offline_access: 'Grants refresh tokens for long-lived access.',
7576
} as const
7677

77-
export default new OAuthProvider({
78-
apiRoute: '/sse',
79-
apiHandler: RadarMCP.mount('/sse'),
80-
// @ts-ignore
81-
defaultHandler: createAuthHandlers({ scopes: RadarScopes, metrics }),
82-
authorizeEndpoint: '/oauth/authorize',
83-
tokenEndpoint: '/token',
84-
tokenExchangeCallback: (options) =>
85-
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
86-
// Cloudflare access token TTL
87-
accessTokenTTL: 3600,
88-
clientRegistrationEndpoint: '/register',
89-
})
78+
async function handleDevMode(req: Request, env: Env, ctx: ExecutionContext) {
79+
const { user, accounts } = await getUserAndAccounts(env.DEV_CLOUDFLARE_API_TOKEN, {
80+
'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
81+
'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
82+
})
83+
ctx.props = {
84+
accessToken: env.DEV_CLOUDFLARE_API_TOKEN,
85+
user,
86+
accounts,
87+
} as Props
88+
return RadarMCP.mount('/sse').fetch(req, env, ctx)
89+
}
90+
91+
export default {
92+
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
93+
if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') {
94+
return await handleDevMode(req, env, ctx)
95+
}
96+
97+
return new OAuthProvider({
98+
apiRoute: '/sse',
99+
apiHandler: RadarMCP.mount('/sse'),
100+
// @ts-ignore
101+
defaultHandler: createAuthHandlers({ scopes: RadarScopes, metrics }),
102+
authorizeEndpoint: '/oauth/authorize',
103+
tokenEndpoint: '/token',
104+
tokenExchangeCallback: (options) =>
105+
handleTokenExchangeCallback(
106+
options,
107+
env.CLOUDFLARE_CLIENT_ID,
108+
env.CLOUDFLARE_CLIENT_SECRET
109+
),
110+
// Cloudflare access token TTL
111+
accessTokenTTL: 3600,
112+
clientRegistrationEndpoint: '/register',
113+
}).fetch(req, env,ctx)
114+
},
115+
}

packages/mcp-common/src/cloudflare-oauth-handler.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -63,34 +63,22 @@ const AccountResponseSchema = z.object({
6363
),
6464
})
6565

66-
async function getTokenAndUser(
67-
c: Context<AuthContext>,
68-
code: string,
69-
code_verifier: string
70-
): Promise<{
71-
accessToken: string
72-
refreshToken: string
73-
user: UserSchema['result']
74-
accounts: AccountSchema['result']
75-
}> {
76-
// Exchange the code for an access token
77-
const { access_token: accessToken, refresh_token: refreshToken } = await getAuthToken({
78-
client_id: c.env.CLOUDFLARE_CLIENT_ID,
79-
client_secret: c.env.CLOUDFLARE_CLIENT_SECRET,
80-
redirect_uri: new URL('/oauth/callback', c.req.url).href,
81-
code,
82-
code_verifier,
83-
})
66+
export async function getUserAndAccounts(
67+
accessToken: string,
68+
devModeHeaders?: HeadersInit
69+
): Promise<{ user: UserSchema['result']; accounts: AccountSchema['result'] }> {
70+
const headers = devModeHeaders
71+
? devModeHeaders
72+
: {
73+
Authorization: `Bearer ${accessToken}`,
74+
}
75+
8476
const [userResponse, accountsResponse] = await Promise.all([
8577
fetch('https://api.cloudflare.com/client/v4/user', {
86-
headers: {
87-
Authorization: `Bearer ${accessToken}`,
88-
},
78+
headers,
8979
}),
9080
fetch('https://api.cloudflare.com/client/v4/accounts', {
91-
headers: {
92-
Authorization: `Bearer ${accessToken}`,
93-
},
81+
headers,
9482
}),
9583
])
9684

@@ -107,6 +95,30 @@ async function getTokenAndUser(
10795
const { result: user } = UserResponseSchema.parse(await userResponse.json())
10896
const { result: accounts } = AccountResponseSchema.parse(await accountsResponse.json())
10997

98+
return { user, accounts }
99+
}
100+
101+
async function getTokenAndUserDetails(
102+
c: Context<AuthContext>,
103+
code: string,
104+
code_verifier: string
105+
): Promise<{
106+
accessToken: string
107+
refreshToken: string
108+
user: UserSchema['result']
109+
accounts: AccountSchema['result']
110+
}> {
111+
// Exchange the code for an access token
112+
const { access_token: accessToken, refresh_token: refreshToken } = await getAuthToken({
113+
client_id: c.env.CLOUDFLARE_CLIENT_ID,
114+
client_secret: c.env.CLOUDFLARE_CLIENT_SECRET,
115+
redirect_uri: new URL('/oauth/callback', c.req.url).href,
116+
code,
117+
code_verifier,
118+
})
119+
120+
const { user, accounts } = await getUserAndAccounts(accessToken)
121+
110122
return { accessToken, refreshToken, user, accounts }
111123
}
112124

@@ -217,7 +229,7 @@ export function createAuthHandlers({
217229
}
218230

219231
const [{ accessToken, refreshToken, user, accounts }] = await Promise.all([
220-
getTokenAndUser(c, code, oauthReqInfo.codeVerifier),
232+
getTokenAndUserDetails(c, code, oauthReqInfo.codeVerifier),
221233
c.env.OAUTH_PROVIDER.createClient({
222234
clientId: oauthReqInfo.clientId,
223235
tokenEndpointAuthMethod: 'none',

0 commit comments

Comments
 (0)