Skip to content

Commit 246f60f

Browse files
committed
Initial Commit
1 parent 2d76979 commit 246f60f

File tree

12 files changed

+6525
-0
lines changed

12 files changed

+6525
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CLOUDFLARE_CLIENT_ID=
2+
CLOUDFLARE_CLIENT_SECRET=
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @type {import("eslint").Linter.Config} */
2+
module.exports = {
3+
root: true,
4+
extends: ['@repo/eslint-config/default.cjs'],
5+
}

apps/cloudflare-one-casb/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Model Context Protocol (MCP) Server + Cloudflare OAuth
2+
3+
This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that supports remote MCP connections, with Cloudflare OAuth built-in.
4+
5+
You should use this as a template to build an MCP server for Cloudflare, provided by Cloudflare at `server-name.mcp.cloudflare.com`. It has a basic set of tools `apps/template-start-here/src/tools/logs.ts` — you can modify these to do what you need
6+
7+
## Getting Started
8+
9+
10+
- Set secrets via Wrangler (ask in the `Cloudflare's Own MCP Servers` internal channel to get credentials)
11+
12+
```bash
13+
wrangler secret put CLOUDFLARE_CLIENT_ID
14+
wrangler secret put CLOUDFLARE_CLIENT_SECRET
15+
```
16+
17+
#### Set up a KV namespace
18+
19+
- Create the KV namespace:
20+
`wrangler kv:namespace create "OAUTH_KV"`
21+
- Update the Wrangler file with the KV ID
22+
23+
#### Deploy & Test
24+
25+
Deploy the MCP server to make it available on your workers.dev domain
26+
` wrangler deploy`
27+
28+
Test the remote server using [Inspector](https://modelcontextprotocol.io/docs/tools/inspector):
29+
30+
```
31+
npx @modelcontextprotocol/inspector@latest
32+
```
33+
34+
## Deploying to production
35+
36+
- You will need to liberate the zone (LTZ) for your `<server-name>.mcp.cloudflare.com`

apps/cloudflare-one-casb/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "example-cloudflare-mcp-server",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"check:lint": "run-eslint-workers",
7+
"check:types": "run-tsc",
8+
"deploy": "wrangler deploy",
9+
"dev": "wrangler dev",
10+
"start": "wrangler dev",
11+
"cf-typegen": "wrangler types",
12+
"test": "vitest run"
13+
},
14+
"dependencies": {
15+
"@cloudflare/workers-oauth-provider": "0.0.2",
16+
"@hono/zod-validator": "0.4.3",
17+
"@modelcontextprotocol/sdk": "1.8.0",
18+
"@repo/mcp-common": "workspace:*",
19+
"agents": "0.0.49",
20+
"cloudflare": "4.2.0",
21+
"hono": "4.7.6",
22+
"zod": "3.24.2"
23+
},
24+
"devDependencies": {
25+
"@cloudflare/vitest-pool-workers": "0.8.14",
26+
"@cloudflare/workers-types": "4.20250410.0",
27+
"@types/jsonwebtoken": "9.0.9",
28+
"prettier": "3.5.3",
29+
"typescript": "5.5.4",
30+
"vitest": "3.0.9",
31+
"wrangler": "4.10.0"
32+
}
33+
}

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import OAuthProvider from '@cloudflare/workers-oauth-provider'
2+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
3+
import { McpAgent } from 'agents/mcp'
4+
import { env } from 'cloudflare:workers'
5+
6+
import {
7+
CloudflareAuthHandler,
8+
handleTokenExchangeCallback,
9+
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
10+
import { registerAccountTools } from '@repo/mcp-common/src/tools/account'
11+
12+
import { registerIntegrationsTools } from './tools/integrations'
13+
14+
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
15+
16+
// Context from the auth process, encrypted & stored in the auth token
17+
// and provided to the DurableMCP as this.props
18+
export type Props = {
19+
accessToken: string
20+
user: UserSchema['result']
21+
accounts: AccountSchema['result']
22+
}
23+
24+
export type State = { activeAccountId: string | null }
25+
26+
export class MyMCP extends McpAgent<Env, State, Props> {
27+
server = new McpServer({
28+
name: 'Remote MCP Server with Workers Observability',
29+
version: '1.0.0',
30+
})
31+
32+
initialState: State = {
33+
activeAccountId: null,
34+
}
35+
36+
async init() {
37+
registerAccountTools(this)
38+
39+
registerIntegrationsTools(this, env.CLOUDFLARE_GLOBAL_API_KEY)
40+
41+
// TODO: registerFindingsTools @mleslie
42+
}
43+
44+
getActiveAccountId() {
45+
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
46+
try {
47+
return this.state.activeAccountId ?? null
48+
} catch (e) {
49+
console.error('getActiveAccountId failured: ', e)
50+
return null
51+
}
52+
}
53+
54+
setActiveAccountId(accountId: string) {
55+
console.log('Setting account ID: ', accountId)
56+
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
57+
try {
58+
this.setState({
59+
...this.state,
60+
activeAccountId: accountId,
61+
})
62+
} catch (e) {
63+
return null
64+
}
65+
}
66+
}
67+
68+
export default new OAuthProvider({
69+
apiRoute: '/sse',
70+
// @ts-ignore
71+
apiHandler: MyMCP.mount('/sse'),
72+
// @ts-ignore
73+
defaultHandler: CloudflareAuthHandler,
74+
authorizeEndpoint: '/oauth/authorize',
75+
tokenEndpoint: '/token',
76+
tokenExchangeCallback: (options) =>
77+
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
78+
// Cloudflare access token TTL
79+
accessTokenTTL: 3600,
80+
clientRegistrationEndpoint: '/register',
81+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// TODO: @mleslie start here

0 commit comments

Comments
 (0)