Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
246f60f
Initial Commit
frankmeszaros Apr 24, 2025
b476309
adding common files
frankmeszaros Apr 24, 2025
5827f18
Working MCP Inspector CF1 CASB Toolchain
Apr 24, 2025
3db8c64
fixes
Apr 24, 2025
2d2350c
fix pnpm
frankmeszaros Apr 24, 2025
f9e6923
wip
frankmeszaros Apr 25, 2025
5e08254
Merge branch 'cloudflare:main' into main
frankmeszaros Apr 25, 2025
dafd609
Merge branch 'main' of https://github.com/frankmeszaros/mcp-server-cl…
frankmeszaros Apr 25, 2025
7b030c5
Merge pull request #1 from frankmeszaros/fm/v0
frankmeszaros Apr 25, 2025
ec28ab9
Merge branch 'main' of https://github.com/frankmeszaros/mcp-server-cl…
frankmeszaros Apr 25, 2025
3631999
Merge branch 'main' of https://github.com/frankmeszaros/mcp-server-cl…
frankmeszaros Apr 25, 2025
af09d94
Merge pull request #2 from frankmeszaros/fm/v0
frankmeszaros Apr 25, 2025
e306c73
remove junk comments
frankmeszaros Apr 25, 2025
7619dda
Refactor withAccount into shared repo
frankmeszaros Apr 25, 2025
76ce174
Merge pull request #3 from frankmeszaros/fm/v0
frankmeszaros Apr 25, 2025
f963b7e
Fix Metrics Server Init
frankmeszaros Apr 26, 2025
9395a80
Merge pull request #4 from frankmeszaros/fm/v0
frankmeszaros Apr 26, 2025
b0f334d
pnpm-lock.yaml
frankmeszaros Apr 26, 2025
f505d4c
Lock with new changes
frankmeszaros Apr 26, 2025
ca00607
never mind
frankmeszaros Apr 26, 2025
e57d80b
never mind
frankmeszaros Apr 26, 2025
0fe1172
Merge branch 'main' of https://github.com/frankmeszaros/mcp-server-cl…
frankmeszaros Apr 26, 2025
cd1ac05
fix: pnpm-lock and gitignore
frankmeszaros Apr 29, 2025
b5ebd44
fix: pnpm-lock and gitignore
frankmeszaros Apr 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ yarn-error.log*
.sentryclirc.lock/
tmp.json
tmp.ts
.idea

apps/sandbox-container/workdir
apps/sandbox-container/workdir
2 changes: 2 additions & 0 deletions apps/cloudflare-one-casb/.dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CLOUDFLARE_CLIENT_ID=
CLOUDFLARE_CLIENT_SECRET=
5 changes: 5 additions & 0 deletions apps/cloudflare-one-casb/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: ['@repo/eslint-config/default.cjs'],
}
31 changes: 31 additions & 0 deletions apps/cloudflare-one-casb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Model Context Protocol (MCP) Server + Cloudflare OAuth

This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that supports remote MCP connections, with Cloudflare OAuth built-in.

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

## Getting Started

- Set secrets via Wrangler

```bash
wrangler secret put CLOUDFLARE_CLIENT_ID
wrangler secret put CLOUDFLARE_CLIENT_SECRET
```

#### Set up a KV namespace

- Create the KV namespace:
`wrangler kv:namespace create "OAUTH_KV"`
- Update the Wrangler file with the KV ID

#### Deploy & Test

Deploy the MCP server to make it available on your workers.dev domain
` wrangler deploy`

Test the remote server using [Inspector](https://modelcontextprotocol.io/docs/tools/inspector):

```
npx wrangler deploy
```
33 changes: 33 additions & 0 deletions apps/cloudflare-one-casb/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "cloudflare-casb-mcp-server",
"version": "0.0.1",
"private": true,
"scripts": {
"check:lint": "run-eslint-workers",
"check:types": "run-tsc",
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"cf-typegen": "wrangler types",
"test": "vitest run"
},
"dependencies": {
"@cloudflare/workers-oauth-provider": "0.0.2",
"@hono/zod-validator": "0.4.3",
"@modelcontextprotocol/sdk": "1.9.0",
"@repo/mcp-common": "workspace:*",
"agents": "0.0.49",
"cloudflare": "4.2.0",
"hono": "4.7.6",
"zod": "3.24.2"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "0.8.14",
"@cloudflare/workers-types": "4.20250410.0",
"@types/jsonwebtoken": "9.0.9",
"prettier": "3.5.3",
"typescript": "5.5.4",
"vitest": "3.0.9",
"wrangler": "4.10.0"
}
}
98 changes: 98 additions & 0 deletions apps/cloudflare-one-casb/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import OAuthProvider from '@cloudflare/workers-oauth-provider'
import { McpAgent } from 'agents/mcp'
import { env } from 'cloudflare:workers'

import {
createAuthHandlers,
handleTokenExchangeCallback,
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
import { registerAccountTools } from '@repo/mcp-common/src/tools/account'

import { MetricsTracker } from '../../../packages/mcp-observability/src'
import { registerIntegrationsTools } from './tools/integrations'

import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
import type { CloudflareMcpAgent } from '@repo/mcp-common/src/types/cloudflare-mcp-agent'

const metrics = new MetricsTracker(env.MCP_METRICS, {
name: env.MCP_SERVER_NAME,
version: env.MCP_SERVER_VERSION,
})

export type Props = {
accessToken: string
user: UserSchema['result']
accounts: AccountSchema['result']
}

export type State = { activeAccountId: string | null }
export class CASBMCP extends McpAgent<Env, State, Props> {
_server: CloudflareMCPServer | undefined
set server(server: CloudflareMCPServer) {
this._server = server
}

get server(): CloudflareMCPServer {
if (!this._server) {
throw new Error('Tried to access server before it was initialized')
}

return this._server
}

constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env)
}

async init() {
this.server = new CloudflareMCPServer(this.props.user.id, this.env.MCP_METRICS, {
name: this.env.MCP_SERVER_NAME,
version: this.env.MCP_SERVER_VERSION,
})

registerAccountTools(this)
registerIntegrationsTools(this)
}

getActiveAccountId() {
try {
return this.state.activeAccountId ?? null
} catch (e) {
console.error('getActiveAccountId failured: ', e)
return null
}
}

setActiveAccountId(accountId: string) {
try {
this.setState({
...this.state,
activeAccountId: accountId,
})
} catch (e) {
return null
}
}
}
const CloudflareOneCasbScopes = {
'account:read': 'See your account info such as account details, analytics, and memberships.',
'user:read': 'See your user info such as name, email address, and account memberships.',
'teams:read': 'See Cloudflare One Resources',
offline_access: 'Grants refresh tokens for long-lived access.',
} as const

export default new OAuthProvider({
apiRoute: '/sse',
// @ts-ignore
apiHandler: CASBMCP.mount('/sse'),
// @ts-ignore
defaultHandler: createAuthHandlers({ scopes: CloudflareOneCasbScopes, metrics }),
authorizeEndpoint: '/oauth/authorize',
tokenEndpoint: '/token',
tokenExchangeCallback: (options) =>
handleTokenExchangeCallback(options, env.CLOUDFLARE_CLIENT_ID, env.CLOUDFLARE_CLIENT_SECRET),
// Cloudflare access token TTL
accessTokenTTL: 3600,
clientRegistrationEndpoint: '/register',
})
Loading