Skip to content

Commit c78d114

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

File tree

8 files changed

+142
-46
lines changed

8 files changed

+142
-46
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+
```

apps/radar/src/index.ts

Lines changed: 1 addition & 0 deletions
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,

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)