Skip to content

Commit 94351b2

Browse files
authored
Support streamable transport and move handleDevMode to mcp-common (#99)
1 parent 2aa610f commit 94351b2

File tree

12 files changed

+132
-128
lines changed

12 files changed

+132
-128
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
CLOUDFLARE_CLIENT_ID=
2-
CLOUDFLARE_CLIENT_SECRET=
2+
CLOUDFLARE_CLIENT_SECRET=
3+
DEV_DISABLE_OAUTH=
4+
DEV_CLOUDFLARE_API_TOKEN=
5+
DEV_CLOUDFLARE_EMAIL=

apps/cloudflare-one-casb/src/index.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import OAuthProvider from '@cloudflare/workers-oauth-provider'
22
import { McpAgent } from 'agents/mcp'
33

4+
import { createApiHandler } from '@repo/mcp-common/src/api-handler'
45
import {
56
createAuthHandlers,
67
handleTokenExchangeCallback,
78
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
9+
import { handleDevMode } from '@repo/mcp-common/src/dev-mode'
810
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
911
import { getEnv } from '@repo/mcp-common/src/env'
1012
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
@@ -14,7 +16,7 @@ import { registerAccountTools } from '@repo/mcp-common/src/tools/account'
1416
import { MetricsTracker } from '../../../packages/mcp-observability/src'
1517
import { registerIntegrationsTools } from './tools/integrations'
1618

17-
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
19+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
1820
import type { Env } from './context'
1921

2022
export { UserDetails }
@@ -26,13 +28,11 @@ const metrics = new MetricsTracker(env.MCP_METRICS, {
2628
version: env.MCP_SERVER_VERSION,
2729
})
2830

29-
export type Props = {
30-
accessToken: string
31-
user: UserSchema['result']
32-
accounts: AccountSchema['result']
33-
}
31+
// Context from the auth process, encrypted & stored in the auth token
32+
// and provided to the DurableMCP as this.props
33+
type Props = AuthProps
3434

35-
export type State = { activeAccountId: string | null }
35+
type State = { activeAccountId: string | null }
3636
export class CASBMCP extends McpAgent<Env, State, Props> {
3737
_server: CloudflareMCPServer | undefined
3838
set server(server: CloudflareMCPServer) {
@@ -92,17 +92,29 @@ const CloudflareOneCasbScopes = {
9292
'teams:read': 'See Cloudflare One Resources',
9393
} as const
9494

95-
export default new OAuthProvider({
96-
apiRoute: '/sse',
97-
// @ts-ignore
98-
apiHandler: CASBMCP.mount('/sse'),
99-
// @ts-ignore
100-
defaultHandler: createAuthHandlers({ scopes: CloudflareOneCasbScopes, metrics }),
101-
authorizeEndpoint: '/oauth/authorize',
102-
tokenEndpoint: '/token',
103-
tokenExchangeCallback: (options) =>
104-
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
105-
// Cloudflare access token TTL
106-
accessTokenTTL: 3600,
107-
clientRegistrationEndpoint: '/register',
108-
})
95+
export default {
96+
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
97+
if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') {
98+
return await handleDevMode(CASBMCP, req, env, ctx)
99+
}
100+
101+
return new OAuthProvider({
102+
apiRoute: ['/mcp', '/sse'],
103+
// @ts-ignore
104+
apiHandler: createApiHandler(CASBMCP),
105+
// @ts-ignore
106+
defaultHandler: createAuthHandlers({ scopes: CloudflareOneCasbScopes, metrics }),
107+
authorizeEndpoint: '/oauth/authorize',
108+
tokenEndpoint: '/token',
109+
tokenExchangeCallback: (options) =>
110+
handleTokenExchangeCallback(
111+
options,
112+
env.CLOUDFLARE_CLIENT_ID,
113+
env.CLOUDFLARE_CLIENT_SECRET
114+
),
115+
// Cloudflare access token TTL
116+
accessTokenTTL: 3600,
117+
clientRegistrationEndpoint: '/register',
118+
}).fetch(req, env, ctx)
119+
},
120+
}

apps/dex-analysis/.dev.vars.example

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

apps/dex-analysis/src/index.ts

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import OAuthProvider from '@cloudflare/workers-oauth-provider'
22
import { McpAgent } from 'agents/mcp'
33

4+
import { createApiHandler } from '@repo/mcp-common/src/api-handler'
45
import {
56
createAuthHandlers,
6-
getUserAndAccounts,
77
handleTokenExchangeCallback,
88
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
9+
import { handleDevMode } from '@repo/mcp-common/src/dev-mode'
910
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
1011
import { getEnv } from '@repo/mcp-common/src/env'
1112
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
@@ -15,7 +16,7 @@ import { MetricsTracker } from '@repo/mcp-observability'
1516

1617
import { registerDEXTools } from './tools/dex'
1718

18-
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
19+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
1920
import type { Env } from './context'
2021

2122
export { UserDetails }
@@ -29,13 +30,9 @@ const metrics = new MetricsTracker(env.MCP_METRICS, {
2930

3031
// Context from the auth process, encrypted & stored in the auth token
3132
// and provided to the DurableMCP as this.props
32-
export type Props = {
33-
accessToken: string
34-
user: UserSchema['result']
35-
accounts: AccountSchema['result']
36-
}
33+
type Props = AuthProps
3734

38-
export type State = { activeAccountId: string | null }
35+
type State = { activeAccountId: string | null }
3936

4037
export class CloudflareDEXMCP extends McpAgent<Env, State, Props> {
4138
_server: CloudflareMCPServer | undefined
@@ -97,29 +94,15 @@ const DexScopes = {
9794
'dex:read': 'See Cloudflare Cloudflare DEX data for your account',
9895
} as const
9996

100-
// TODO: Move this in to mcp-common
101-
async function handleDevMode(req: Request, env: Env, ctx: ExecutionContext) {
102-
const { user, accounts } = await getUserAndAccounts(env.DEV_CLOUDFLARE_API_TOKEN, {
103-
'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
104-
'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
105-
})
106-
ctx.props = {
107-
accessToken: env.DEV_CLOUDFLARE_API_TOKEN,
108-
user,
109-
accounts,
110-
} as Props
111-
return CloudflareDEXMCP.mount('/sse').fetch(req, env, ctx)
112-
}
113-
11497
export default {
11598
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
11699
if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') {
117-
return await handleDevMode(req, env, ctx)
100+
return await handleDevMode(CloudflareDEXMCP, req, env, ctx)
118101
}
119102

120103
return new OAuthProvider({
121-
apiRoute: '/sse',
122-
apiHandler: CloudflareDEXMCP.mount('/sse'),
104+
apiRoute: ['/mcp', '/sse'],
105+
apiHandler: createApiHandler(CloudflareDEXMCP),
123106
// @ts-ignore
124107
defaultHandler: createAuthHandlers({ scopes: DexScopes, metrics }),
125108
authorizeEndpoint: '/oauth/authorize',

apps/radar/src/index.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import OAuthProvider from '@cloudflare/workers-oauth-provider'
22
import { McpAgent } from 'agents/mcp'
33

4+
import { createApiHandler } from '@repo/mcp-common/src/api-handler'
45
import {
56
createAuthHandlers,
6-
getUserAndAccounts,
77
handleTokenExchangeCallback,
88
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
9+
import { handleDevMode } from '@repo/mcp-common/src/dev-mode'
910
import { getEnv } from '@repo/mcp-common/src/env'
1011
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
1112
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
@@ -14,7 +15,7 @@ import { MetricsTracker } from '@repo/mcp-observability'
1415
import { registerRadarTools } from './tools/radar'
1516
import { registerUrlScannerTools } from './tools/url-scanner'
1617

17-
import type { UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
18+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
1819
import type { Env } from './context'
1920

2021
const env = getEnv<Env>()
@@ -26,12 +27,9 @@ const metrics = new MetricsTracker(env.MCP_METRICS, {
2627

2728
// Context from the auth process, encrypted & stored in the auth token
2829
// and provided to the DurableMCP as this.props
29-
export type Props = {
30-
accessToken: string
31-
user: UserSchema['result']
32-
}
30+
type Props = AuthProps
3331

34-
export type State = never
32+
type State = never
3533

3634
export class RadarMCP extends McpAgent<Env, State, Props> {
3735
_server: CloudflareMCPServer | undefined
@@ -73,29 +71,15 @@ export class RadarMCP extends McpAgent<Env, State, Props> {
7371
// Also remove URL_SCANNER_API_TOKEN env var
7472
const RadarScopes = { ...RequiredScopes } as const
7573

76-
// TODO: Move this in to mcp-common
77-
async function handleDevMode(req: Request, env: Env, ctx: ExecutionContext) {
78-
const { user, accounts } = await getUserAndAccounts(env.DEV_CLOUDFLARE_API_TOKEN, {
79-
'X-Auth-Email': env.DEV_CLOUDFLARE_EMAIL,
80-
'X-Auth-Key': env.DEV_CLOUDFLARE_API_TOKEN,
81-
})
82-
ctx.props = {
83-
accessToken: env.DEV_CLOUDFLARE_API_TOKEN,
84-
user,
85-
accounts,
86-
} as Props
87-
return RadarMCP.mount('/sse').fetch(req, env, ctx)
88-
}
89-
9074
export default {
9175
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
9276
if (env.ENVIRONMENT === 'development' && env.DEV_DISABLE_OAUTH === 'true') {
93-
return await handleDevMode(req, env, ctx)
77+
return await handleDevMode(RadarMCP, req, env, ctx)
9478
}
9579

9680
return new OAuthProvider({
97-
apiRoute: '/sse',
98-
apiHandler: RadarMCP.mount('/sse'),
81+
apiRoute: ['/mcp', '/sse'],
82+
apiHandler: createApiHandler(RadarMCP),
9983
// @ts-ignore
10084
defaultHandler: createAuthHandlers({ scopes: RadarScopes, metrics }),
10185
authorizeEndpoint: '/oauth/authorize',

apps/sandbox-container/server/index.ts

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import OAuthProvider from '@cloudflare/workers-oauth-provider'
22

3+
import { createApiHandler } from '@repo/mcp-common/src/api-handler'
34
import {
45
createAuthHandlers,
5-
getUserAndAccounts,
66
handleTokenExchangeCallback,
77
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
8+
import { handleDevMode } from '@repo/mcp-common/src/dev-mode'
89
import { getEnv } from '@repo/mcp-common/src/env'
910
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
1011
import { MetricsTracker } from '@repo/mcp-observability'
@@ -13,7 +14,7 @@ import { ContainerManager } from './containerManager'
1314
import { ContainerMcpAgent } from './containerMcp'
1415

1516
import type { McpAgent } from 'agents/mcp'
16-
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
17+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
1718
import type { Env } from './context'
1819

1920
export { ContainerManager, ContainerMcpAgent }
@@ -27,11 +28,7 @@ const metrics = new MetricsTracker(env.MCP_METRICS, {
2728

2829
// Context from the auth process, encrypted & stored in the auth token
2930
// and provided to the DurableMCP as this.props
30-
export type Props = {
31-
accessToken: string
32-
user: UserSchema['result']
33-
accounts: AccountSchema['result']
34-
}
31+
export type Props = AuthProps
3532

3633
const ContainerScopes = {
3734
...RequiredScopes,
@@ -40,20 +37,6 @@ const ContainerScopes = {
4037
'See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.',
4138
} as const
4239

43-
// TODO: Move this in to mcp-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-
5740
export default {
5841
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
5942
// @ts-ignore
@@ -74,12 +57,12 @@ export default {
7457
}
7558

7659
if (env.ENVIRONMENT === 'dev' && env.DEV_DISABLE_OAUTH === 'true') {
77-
return await handleDevMode(req, env, ctx)
60+
return await handleDevMode(ContainerMcpAgent, req, env, ctx)
7861
}
7962

8063
return new OAuthProvider({
81-
apiRoute: '/sse',
82-
apiHandler: ContainerMcpAgent.mount('/sse', { binding: 'CONTAINER_MCP_AGENT' }),
64+
apiRoute: ['/mcp', '/sse'],
65+
apiHandler: createApiHandler(ContainerMcpAgent, { binding: 'CONTAINER_MCP_AGENT' }),
8366
// @ts-ignore
8467
defaultHandler: createAuthHandlers({ scopes: ContainerScopes, metrics }),
8568
authorizeEndpoint: '/oauth/authorize',

apps/workers-bindings/src/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import OAuthProvider from '@cloudflare/workers-oauth-provider'
22
import { McpAgent } from 'agents/mcp'
33

4+
import { createApiHandler } from '@repo/mcp-common/src/api-handler'
45
import {
56
createAuthHandlers,
67
getUserAndAccounts,
@@ -17,7 +18,7 @@ import { registerR2BucketTools } from '@repo/mcp-common/src/tools/r2_bucket'
1718
import { registerWorkersTools } from '@repo/mcp-common/src/tools/worker'
1819
import { MetricsTracker } from '@repo/mcp-observability'
1920

20-
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
21+
import type { AuthProps } from '@repo/mcp-common/src/cloudflare-oauth-handler'
2122
import type { Env } from './context'
2223

2324
export { UserDetails }
@@ -33,11 +34,7 @@ export type WorkersBindingsMCPState = { activeAccountId: string | null }
3334

3435
// Context from the auth process, encrypted & stored in the auth token
3536
// and provided to the DurableMCP as this.props
36-
export type Props = {
37-
accessToken: string
38-
user: UserSchema['result']
39-
accounts: AccountSchema['result']
40-
}
37+
type Props = AuthProps
4138

4239
export class WorkersBindingsMCP extends McpAgent<Env, WorkersBindingsMCPState, Props> {
4340
_server: CloudflareMCPServer | undefined
@@ -129,8 +126,8 @@ export default {
129126
}
130127

131128
return new OAuthProvider({
132-
apiRoute: '/sse',
133-
apiHandler: WorkersBindingsMCP.mount('/sse'),
129+
apiRoute: ['/mcp', '/sse'],
130+
apiHandler: createApiHandler(WorkersBindingsMCP),
134131
// @ts-ignore
135132
defaultHandler: createAuthHandlers({ scopes: BindingsScopes, metrics }),
136133
authorizeEndpoint: '/oauth/authorize',
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CLOUDFLARE_CLIENT_ID=
2+
CLOUDFLARE_CLIENT_SECRET=
3+
DEV_DISABLE_OAUTH=
4+
DEV_CLOUDFLARE_API_TOKEN=
5+
DEV_CLOUDFLARE_EMAIL=

0 commit comments

Comments
 (0)