Skip to content

Commit d67db27

Browse files
committed
Add dev mode auth for all servers and update to root CONTRIBUTING.md
1 parent 146ca70 commit d67db27

File tree

10 files changed

+157
-59
lines changed

10 files changed

+157
-59
lines changed

CONTRIBUTING.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
1-
## Developing
1+
# Developing
22

3-
### Packages
3+
## Architecture
44

5-
- eslint-config: Eslint config used by all apps and packages.
6-
- typescript-config: tsconfig used by all apps and packages.
7-
- mcp-common: Shared common tools and scripts to help manage this repo.
5+
The monorepo has two top-level directories:
86

9-
For more details on development in this monorepo, take a look at apps/workers-observability
7+
- apps: Contains the Workers for each MCP server
8+
- apps/workers-observability
9+
- apps/workers-bindings
10+
- [apps/radar/CONTRIBUTING.md](apps/radar/CONTRIBUTING.md)
11+
- apps/cloudflare-one-casb
12+
- packages: Contains shared packages used across our various apps.
13+
- packages/eslint-config: Eslint config used by all apps and packages.
14+
- packages/typescript-config: tsconfig used by all apps and packages.
15+
- packages/mcp-common: Shared common tools and scripts to help manage this repo.
1016

11-
## Testing
17+
We use [TurboRepo](https://turbo.build/) and [pnpm](https://pnpm.io/) to manage this repository. TurboRepo manages the monorepo by ensuring commands are run across all apps.
1218

13-
The project uses Vitest as the testing framework with MSW (Mock Service Worker) for API mocking.
19+
## Getting Started
1420

15-
### Running Tests
21+
This section will guide you through setting up your developer environment and running tests.
22+
23+
For more details on development in this monorepo, take a look at apps/workers-observability/CONTRIBUTING.md[/apps/workers-observability/CONTRIBUTING.md]
24+
25+
### Testing
26+
27+
The project uses Vitest as the testing framework with [fetchMock](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/) for API mocking.
28+
29+
#### Running Tests
1630

1731
To run all tests:
1832

@@ -30,4 +44,4 @@ To run tests in watch mode (useful during development):
3044

3145
```bash
3246
pnpm test:watch
33-
```
47+
```

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# Cloudflare MCP Server
22

3-
Model Context Protocol (MCP) is a [new, standardized protocol](https://modelcontextprotocol.io/introduction) for managing context between large language models (LLMs) and external systems. In this repository, you can find several MCP servers allowing you to connect to Cloudflare's service from an MCP client (e.g. Cursor, Claude Desktop) and use natural language to accomplish things on your Cloudflare account. The following servers are included in this repository:
3+
Model Context Protocol (MCP) is a [new, standardized protocol](https://modelcontextprotocol.io/introduction) for managing context between large language models (LLMs) and external systems. In this repository, you can find several MCP servers allowing you to connect to Cloudflare's service from an MCP client (e.g. Cursor, Claude Desktop) and use natural language to accomplish things on your Cloudflare account. The following servers are included in this repository:
44

5-
| Server Name | Description | Server URL |
6-
|--------------------------|-----------------------------------------------------------------------------|-------|
7-
| [**Documentation server**](/apps/docs-autorag) | Get up to date reference information on Cloudflare | `https://docs.mcp.cloudflare.com/sse` |
8-
| [**Workers Bindings server**](/apps/bindings) | Build Workers applications with storage, AI, and compute primitives | `https://bindings.mcp.cloudflare.com/sse` |
9-
| [**Observability server**](/apps/observability) | Debug and get insight into your application’s logs and analytics | `https://observability.mcp.cloudflare.com/sse` |
10-
| [**Radar server**](/apps/radar) | Get global Internet traffic insights, trends, URL scans, and other utilities | `https://radar.mcp.cloudflare.com/sse` |
5+
| Server Name | Description | Server URL |
6+
| ----------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------- |
7+
| [**Documentation server**](/apps/docs-autorag) | Get up to date reference information on Cloudflare | `https://docs.mcp.cloudflare.com/sse` |
8+
| [**Workers Bindings server**](/apps/bindings) | Build Workers applications with storage, AI, and compute primitives | `https://bindings.mcp.cloudflare.com/sse` |
9+
| [**Observability server**](/apps/observability) | Debug and get insight into your application’s logs and analytics | `https://observability.mcp.cloudflare.com/sse` |
10+
| [**Radar server**](/apps/radar) | Get global Internet traffic insights, trends, URL scans, and other utilities | `https://radar.mcp.cloudflare.com/sse` |
1111

12-
## Access the remote MCP server from any MCP client
12+
## Access the remote MCP server from any MCP client
1313

1414
If your MCP client has first class support for remote MCP servers, the client will provide a way to accept the server URL directly within its interface (e.g. [Cloudflare AI Playground](https://playground.ai.cloudflare.com/))
1515

@@ -21,23 +21,23 @@ If your client does not yet support remote MCP servers, you will need to set up
2121
"cloudflare-observability": {
2222
"command": "npx",
2323
"args": ["mcp-remote", "https://observability.mcp.cloudflare.com/sse"]
24-
},
24+
},
2525
"cloudflare-bindings": {
2626
"command": "npx",
2727
"args": ["mcp-remote", "https://bindings.mcp.cloudflare.com/sse"]
28-
},
28+
}
2929
}
3030
}
3131
```
3232

3333
## Need access to more Cloudflare tools?
3434

35-
We're continuing to add more functionality to this remote MCP server repo. If you'd like to leave feedback, file a bug or provide a feature request, [please open an issue](https://github.com/cloudflare/mcp-server-cloudflare/issues/new/choose) on this repository
35+
We're continuing to add more functionality to this remote MCP server repo. If you'd like to leave feedback, file a bug or provide a feature request, [please open an issue](https://github.com/cloudflare/mcp-server-cloudflare/issues/new/choose) on this repository
3636

3737
## Paid Features
3838

3939
Some features may require a paid Cloudflare Workers plan. Ensure your Cloudflare account has the necessary subscription level for the features you intend to use.
4040

4141
## Contributing
4242

43-
Interested in contributing, and running this server locally? See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
43+
Interested in contributing, and running this server locally? See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.

apps/radar/CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ If you'd like to iterate and test your MCP server, you can do so in local develo
77
1. Create a `.dev.vars` file in your project root:
88

99
If you're a Cloudflare employee:
10+
1011
```
1112
CLOUDFLARE_CLIENT_ID=your_development_cloudflare_client_id
1213
CLOUDFLARE_CLIENT_SECRET=your_development_cloudflare_client_secret
1314
URL_SCANNER_API_TOKEN=your_development_url_scanner_api_token
1415
```
1516

1617
If you're an external contributor, you can provide a development API token:
18+
1719
```
1820
DEV_DISABLE_OAUTH=true
1921
# This is your global api token

apps/radar/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const RadarScopes = {
7575
offline_access: 'Grants refresh tokens for long-lived access.',
7676
} as const
7777

78+
// TODO: Move this in to wci-common
7879
async function handleDevMode(req: Request, env: Env, ctx: ExecutionContext) {
7980
const { user, accounts } = await getUserAndAccounts(env.DEV_CLOUDFLARE_API_TOKEN, {
8081
'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
@@ -110,6 +111,6 @@ export default {
110111
// Cloudflare access token TTL
111112
accessTokenTTL: 3600,
112113
clientRegistrationEndpoint: '/register',
113-
}).fetch(req, env,ctx)
114+
}).fetch(req, env, ctx)
114115
},
115116
}

apps/sandbox-container/server/context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ export interface Env {
1212
CONTAINER_MANAGER: DurableObjectNamespace<ContainerManager>
1313
MCP_METRICS: AnalyticsEngineDataset
1414
AI: Ai
15+
DEV_DISABLE_OAUTH: string
16+
DEV_CLOUDFLARE_API_TOKEN: string
17+
DEV_CLOUDFLARE_EMAIL: string
1518
}

apps/sandbox-container/server/index.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import OAuthProvider from '@cloudflare/workers-oauth-provider'
22

33
import {
44
createAuthHandlers,
5+
getUserAndAccounts,
56
handleTokenExchangeCallback,
67
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
78
import { getEnv } from '@repo/mcp-common/src/env'
@@ -39,8 +40,22 @@ const ContainerScopes = {
3940
'See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.',
4041
} as const
4142

43+
// TODO: Move this in to wci-common
44+
async function handleDevMode(req: Request, env: Env, ctx: ExecutionContext) {
45+
const { user, accounts } = await getUserAndAccounts(env.DEV_CLOUDFLARE_API_TOKEN, {
46+
'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
47+
'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
48+
})
49+
ctx.props = {
50+
accessToken: env.DEV_CLOUDFLARE_API_TOKEN,
51+
user,
52+
accounts,
53+
} as Props
54+
return ContainerMcpAgent.mount('/sse').fetch(req, env, ctx)
55+
}
56+
4257
export default {
43-
fetch: (req: Request, env: Env, ctx: ExecutionContext) => {
58+
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
4459
// @ts-ignore
4560
if (env.ENVIRONMENT === 'test') {
4661
ctx.props = {
@@ -58,6 +73,10 @@ export default {
5873
)
5974
}
6075

76+
if (env.ENVIRONMENT === 'dev' && env.DEV_DISABLE_OAUTH === 'true') {
77+
return await handleDevMode(req, env, ctx)
78+
}
79+
6180
return new OAuthProvider({
6281
apiRoute: '/sse',
6382
apiHandler: ContainerMcpAgent.mount('/sse', { binding: 'CONTAINER_MCP_AGENT' }),

apps/workers-bindings/src/context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ export interface Env {
99
CLOUDFLARE_CLIENT_SECRET: '<PLACEHOLDER>'
1010
MCP_OBJECT: DurableObjectNamespace<WorkersBindingsMCP>
1111
MCP_METRICS: AnalyticsEngineDataset
12+
DEV_DISABLE_OAUTH: string
13+
DEV_CLOUDFLARE_API_TOKEN: string
14+
DEV_CLOUDFLARE_EMAIL: string
1215
}

apps/workers-bindings/src/index.ts

Lines changed: 40 additions & 14 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'
@@ -103,17 +104,42 @@ const BindingsScopes = {
103104
'd1:write': 'Create, read, and write to D1 databases',
104105
} as const
105106

106-
// Export the OAuth handler as the default
107-
export default new OAuthProvider({
108-
apiRoute: '/sse',
109-
apiHandler: WorkersBindingsMCP.mount('/sse'),
110-
// @ts-ignore
111-
defaultHandler: createAuthHandlers({ scopes: BindingsScopes, metrics }),
112-
authorizeEndpoint: '/oauth/authorize',
113-
tokenEndpoint: '/token',
114-
tokenExchangeCallback: (options) =>
115-
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
116-
// Cloudflare access token TTL
117-
accessTokenTTL: 3600,
118-
clientRegistrationEndpoint: '/register',
119-
})
107+
// TODO: Move this in to wci-common
108+
async function handleDevMode(req: Request, env: Env, ctx: ExecutionContext) {
109+
const { user, accounts } = await getUserAndAccounts(env.DEV_CLOUDFLARE_API_TOKEN, {
110+
'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
111+
'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
112+
})
113+
ctx.props = {
114+
accessToken: env.DEV_CLOUDFLARE_API_TOKEN,
115+
user,
116+
accounts,
117+
} as Props
118+
return WorkersBindingsMCP.mount('/sse').fetch(req, env, ctx)
119+
}
120+
121+
export default {
122+
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
123+
if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') {
124+
return await handleDevMode(req, env, ctx)
125+
}
126+
127+
return new OAuthProvider({
128+
apiRoute: '/sse',
129+
apiHandler: WorkersBindingsMCP.mount('/sse'),
130+
// @ts-ignore
131+
defaultHandler: createAuthHandlers({ scopes: BindingsScopes, metrics }),
132+
authorizeEndpoint: '/oauth/authorize',
133+
tokenEndpoint: '/token',
134+
tokenExchangeCallback: (options) =>
135+
handleTokenExchangeCallback(
136+
options,
137+
env.CLOUDFLARE_CLIENT_ID,
138+
env.CLOUDFLARE_CLIENT_SECRET
139+
),
140+
// Cloudflare access token TTL
141+
accessTokenTTL: 3600,
142+
clientRegistrationEndpoint: '/register',
143+
}).fetch(req, env, ctx)
144+
},
145+
}

apps/workers-observability/src/context.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import type { ObservabilityMCP } from './index'
33
export interface Env {
44
OAUTH_KV: KVNamespace
55
ENVIRONMENT: 'development' | 'staging' | 'production'
6-
MCP_SERVER_NAME: 'PLACEHOLDER'
7-
MCP_SERVER_VERSION: 'PLACEHOLDER'
8-
CLOUDFLARE_CLIENT_ID: 'PLACEHOLDER'
9-
CLOUDFLARE_CLIENT_SECRET: 'PLACEHOLDER'
6+
MCP_SERVER_NAME: string
7+
MCP_SERVER_VERSION: string
8+
CLOUDFLARE_CLIENT_ID: string
9+
CLOUDFLARE_CLIENT_SECRET: string
1010
MCP_OBJECT: DurableObjectNamespace<ObservabilityMCP>
1111
MCP_METRICS: AnalyticsEngineDataset
12-
SENTRY_ACCESS_CLIENT_ID: 'PLACEHOLDER'
13-
SENTRY_ACCESS_CLIENT_SECRET: 'PLACEHOLDER'
14-
GIT_HASH: 'OVERRIDEN_DURING_DEPLOYMENT'
15-
SENTRY_DSN: 'PLACEHOLDER'
12+
SENTRY_ACCESS_CLIENT_ID: string
13+
SENTRY_ACCESS_CLIENT_SECRET: string
14+
GIT_HASH: string
15+
SENTRY_DSN: string
16+
DEV_DISABLE_OAUTH: string
17+
DEV_CLOUDFLARE_API_TOKEN: string
18+
DEV_CLOUDFLARE_EMAIL: string
1619
}

apps/workers-observability/src/index.ts

Lines changed: 40 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'
@@ -104,16 +105,42 @@ const ObservabilityScopes = {
104105
'workers_observability:read': 'See observability logs for your account',
105106
} as const
106107

107-
export default new OAuthProvider({
108-
apiRoute: '/sse',
109-
apiHandler: ObservabilityMCP.mount('/sse'),
110-
// @ts-ignore
111-
defaultHandler: createAuthHandlers({ scopes: ObservabilityScopes, metrics }),
112-
authorizeEndpoint: '/oauth/authorize',
113-
tokenEndpoint: '/token',
114-
tokenExchangeCallback: (options) =>
115-
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
116-
// Cloudflare access token TTL
117-
accessTokenTTL: 3600,
118-
clientRegistrationEndpoint: '/register',
119-
})
108+
// TODO: Move this in to wci-common
109+
async function handleDevMode(req: Request, env: Env, ctx: ExecutionContext) {
110+
const { user, accounts } = await getUserAndAccounts(env.DEV_CLOUDFLARE_API_TOKEN, {
111+
'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
112+
'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
113+
})
114+
ctx.props = {
115+
accessToken: env.DEV_CLOUDFLARE_API_TOKEN,
116+
user,
117+
accounts,
118+
} as Props
119+
return ObservabilityMCP.mount('/sse').fetch(req, env, ctx)
120+
}
121+
122+
export default {
123+
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
124+
if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') {
125+
return await handleDevMode(req, env, ctx)
126+
}
127+
128+
return new OAuthProvider({
129+
apiRoute: '/sse',
130+
apiHandler: ObservabilityMCP.mount('/sse'),
131+
// @ts-ignore
132+
defaultHandler: createAuthHandlers({ scopes: ObservabilityScopes, metrics }),
133+
authorizeEndpoint: '/oauth/authorize',
134+
tokenEndpoint: '/token',
135+
tokenExchangeCallback: (options) =>
136+
handleTokenExchangeCallback(
137+
options,
138+
env.CLOUDFLARE_CLIENT_ID,
139+
env.CLOUDFLARE_CLIENT_SECRET
140+
),
141+
// Cloudflare access token TTL
142+
accessTokenTTL: 3600,
143+
clientRegistrationEndpoint: '/register',
144+
}).fetch(req, env, ctx)
145+
},
146+
}

0 commit comments

Comments
 (0)