Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions apps/docs-autorag/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { CloudflareDocumentationMCP } from './index'
export interface Env {
ENVIRONMENT: 'development' | 'staging' | 'production'
AUTORAG_NAME: 'cloudflare-docs-autorag'
MCP_SERVER_NAME: 'PLACEHOLDER'
MCP_SERVER_VERSION: 'PLACEHOLDER'
MCP_SERVER_NAME: string
MCP_SERVER_VERSION: string
MCP_OBJECT: DurableObjectNamespace<CloudflareDocumentationMCP>
MCP_METRICS: AnalyticsEngineDataset
AI: Ai
Expand Down
8 changes: 4 additions & 4 deletions apps/sandbox-container/server/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type { ContainerManager, ContainerMcpAgent } from './index'

export interface Env {
OAUTH_KV: KVNamespace
CLOUDFLARE_CLIENT_ID: '<PLACEHOLDER>'
CLOUDFLARE_CLIENT_SECRET: '<PLACEHOLDER>'
CLOUDFLARE_CLIENT_ID: string
CLOUDFLARE_CLIENT_SECRET: string
ENVIRONMENT: 'dev'
MCP_SERVER_NAME: '<PLACEHOLDER>'
MCP_SERVER_VERSION: '<PLACEHOLDER>'
MCP_SERVER_NAME: string
MCP_SERVER_VERSION: string
OPENAI_API_KEY: string
CONTAINER_MCP_AGENT: DurableObjectNamespace<ContainerMcpAgent>
CONTAINER_MANAGER: DurableObjectNamespace<ContainerManager>
Expand Down
6 changes: 2 additions & 4 deletions apps/sandbox-container/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
handleTokenExchangeCallback,
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
import { getEnv } from '@repo/mcp-common/src/env'
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
import { MetricsTracker } from '@repo/mcp-observability'

import { ContainerManager } from './containerManager'
Expand Down Expand Up @@ -32,12 +33,10 @@ export type Props = {
}

const ContainerScopes = {
...RequiredScopes,
'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.',
'workers:write':
'See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.',
'workers_observability:read': 'See observability logs for your account',
offline_access: 'Grants refresh tokens for long-lived access.',
} as const

export default {
Expand All @@ -61,7 +60,6 @@ export default {

return new OAuthProvider({
apiRoute: '/sse',
// @ts-ignore
apiHandler: ContainerMcpAgent.mount('/sse', { binding: 'CONTAINER_MCP_AGENT' }),
// @ts-ignore
defaultHandler: createAuthHandlers({ scopes: ContainerScopes, metrics }),
Expand Down
10 changes: 6 additions & 4 deletions apps/workers-bindings/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
import type { WorkersBindingsMCP } from './index'

export interface Env {
OAUTH_KV: KVNamespace
ENVIRONMENT: 'development' | 'staging' | 'production'
MCP_SERVER_NAME: '<PLACEHOLDER>'
MCP_SERVER_VERSION: '<PLACEHOLDER>'
CLOUDFLARE_CLIENT_ID: '<PLACEHOLDER>'
CLOUDFLARE_CLIENT_SECRET: '<PLACEHOLDER>'
MCP_SERVER_NAME: string
MCP_SERVER_VERSION: string
CLOUDFLARE_CLIENT_ID: string
CLOUDFLARE_CLIENT_SECRET: string
MCP_OBJECT: DurableObjectNamespace<WorkersBindingsMCP>
USER_DETAILS: DurableObjectNamespace<UserDetails>
MCP_METRICS: AnalyticsEngineDataset
}
30 changes: 16 additions & 14 deletions apps/workers-bindings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
createAuthHandlers,
handleTokenExchangeCallback,
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
import { getEnv } from '@repo/mcp-common/src/env'
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
import { registerAccountTools } from '@repo/mcp-common/src/tools/account'
import { registerD1Tools } from '@repo/mcp-common/src/tools/d1'
Expand All @@ -17,6 +19,8 @@ import { MetricsTracker } from '@repo/mcp-observability'
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
import type { Env } from './context'

export { UserDetails }

const env = getEnv<Env>()

const metrics = new MetricsTracker(env.MCP_METRICS, {
Expand Down Expand Up @@ -72,42 +76,40 @@ export class WorkersBindingsMCP extends McpAgent<Env, WorkersBindingsMCPState, P
registerR2BucketTools(this)
registerD1Tools(this)
}
getActiveAccountId() {
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch

async getActiveAccountId() {
try {
return this.state.activeAccountId ?? null
// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
// we do this so we can persist activeAccountId across sessions
const userDetails = getUserDetails(env, this.props.user.id)
return await userDetails.getActiveAccountId()
} catch (e) {
this.server.recordError(e)
return null
}
}

setActiveAccountId(accountId: string) {
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
async setActiveAccountId(accountId: string) {
try {
this.setState({
...this.state,
activeAccountId: accountId,
})
const userDetails = getUserDetails(env, this.props.user.id)
await userDetails.setActiveAccountId(accountId)
} catch (e) {
return null
this.server.recordError(e)
}
}
}

const BindingsScopes = {
...RequiredScopes,
'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.',
'workers:write':
'See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.',
'workers_observability:read': 'See observability logs for your account',
'd1:write': 'Create, read, and write to D1 databases',
offline_access: 'Grants refresh tokens for long-lived access.',
} as const

// Export the OAuth handler as the default
export default new OAuthProvider({
apiRoute: '/sse',
// @ts-ignore
apiHandler: WorkersBindingsMCP.mount('/sse'),
// @ts-ignore
defaultHandler: createAuthHandlers({ scopes: BindingsScopes, metrics }),
Expand Down
22 changes: 20 additions & 2 deletions apps/workers-bindings/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
{
"class_name": "WorkersBindingsMCP",
"name": "MCP_OBJECT"
},
{
"class_name": "UserDetails",
"name": "USER_DETAILS"
}
]
},
Expand Down Expand Up @@ -59,6 +63,11 @@
{
"class_name": "WorkersBindingsMCP",
"name": "MCP_OBJECT"
},
{
"class_name": "UserDetails",
"name": "USER_DETAILS",
"script_name": "mcp-cloudflare-workers-observability-staging"
}
]
},
Expand All @@ -69,7 +78,9 @@
}
],
"vars": {
"ENVIRONMENT": "staging"
"ENVIRONMENT": "staging",
"MCP_SERVER_NAME": "workers-bindings-staging",
"MCP_SERVER_VERSION": "1.0.0"
},
"analytics_engine_datasets": [
{
Expand All @@ -87,6 +98,11 @@
{
"class_name": "WorkersBindingsMCP",
"name": "MCP_OBJECT"
},
{
"class_name": "UserDetails",
"name": "USER_DETAILS",
"script_name": "mcp-cloudflare-workers-observability-production"
Copy link
Member Author

@Maximo-Guk Maximo-Guk Apr 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking we'll likely want to create a separate worker which hosts the USER_DETAILS durable objects

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will leave this to a separate PR, once we start storing data that needs to be retained ( we can always prompt the user for their accountId again )

}
]
},
Expand All @@ -97,7 +113,9 @@
}
],
"vars": {
"ENVIRONMENT": "production"
"ENVIRONMENT": "production",
"MCP_SERVER_NAME": "workers-bindings",
"MCP_SERVER_VERSION": "1.0.0"
},
"analytics_engine_datasets": [
{
Expand Down
18 changes: 10 additions & 8 deletions apps/workers-observability/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
import type { ObservabilityMCP } from './index'

export interface Env {
OAUTH_KV: KVNamespace
ENVIRONMENT: 'development' | 'staging' | 'production'
MCP_SERVER_NAME: 'PLACEHOLDER'
MCP_SERVER_VERSION: 'PLACEHOLDER'
CLOUDFLARE_CLIENT_ID: 'PLACEHOLDER'
CLOUDFLARE_CLIENT_SECRET: 'PLACEHOLDER'
MCP_SERVER_NAME: string
MCP_SERVER_VERSION: string
CLOUDFLARE_CLIENT_ID: string
CLOUDFLARE_CLIENT_SECRET: string
MCP_OBJECT: DurableObjectNamespace<ObservabilityMCP>
USER_DETAILS: DurableObjectNamespace<UserDetails>
MCP_METRICS: AnalyticsEngineDataset
SENTRY_ACCESS_CLIENT_ID: 'PLACEHOLDER'
SENTRY_ACCESS_CLIENT_SECRET: 'PLACEHOLDER'
GIT_HASH: 'OVERRIDEN_DURING_DEPLOYMENT'
SENTRY_DSN: 'PLACEHOLDER'
SENTRY_ACCESS_CLIENT_ID: string
SENTRY_ACCESS_CLIENT_SECRET: string
GIT_HASH: string
SENTRY_DSN: string
}
26 changes: 13 additions & 13 deletions apps/workers-observability/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
createAuthHandlers,
handleTokenExchangeCallback,
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
import { getEnv } from '@repo/mcp-common/src/env'
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
import { initSentryWithUser } from '@repo/mcp-common/src/sentry'
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
import { registerAccountTools } from '@repo/mcp-common/src/tools/account'
Expand All @@ -17,6 +19,8 @@ import { registerLogsTools } from './tools/logs'
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
import type { Env } from './context'

export { UserDetails }

const env = getEnv<Env>()

const metrics = new MetricsTracker(env.MCP_METRICS, {
Expand Down Expand Up @@ -71,42 +75,38 @@ export class ObservabilityMCP extends McpAgent<Env, State, Props> {
registerLogsTools(this)
}

getActiveAccountId() {
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
async getActiveAccountId() {
try {
return this.state.activeAccountId ?? null
// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
// we do this so we can persist activeAccountId across sessions
const userDetails = getUserDetails(env, this.props.user.id)
return await userDetails.getActiveAccountId()
} catch (e) {
this.server.recordError(e)
return null
}
}

setActiveAccountId(accountId: string) {
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
async setActiveAccountId(accountId: string) {
try {
this.setState({
...this.state,
activeAccountId: accountId,
})
const userDetails = getUserDetails(env, this.props.user.id)
await userDetails.setActiveAccountId(accountId)
} catch (e) {
this.server.recordError(e)
return null
}
}
}

const ObservabilityScopes = {
...RequiredScopes,
'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.',
'workers:write':
'See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.',
'workers_observability:read': 'See observability logs for your account',
offline_access: 'Grants refresh tokens for long-lived access.',
} as const

export default new OAuthProvider({
apiRoute: '/sse',
// @ts-ignore
apiHandler: ObservabilityMCP.mount('/sse'),
// @ts-ignore
defaultHandler: createAuthHandlers({ scopes: ObservabilityScopes, metrics }),
Expand Down
6 changes: 3 additions & 3 deletions apps/workers-observability/src/tools/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function registerLogsTools(agent: ObservabilityMCP) {
rayId: rayIdParam,
},
async (params) => {
const accountId = agent.getActiveAccountId()
const accountId = await agent.getActiveAccountId()
if (!accountId) {
return {
content: [
Expand Down Expand Up @@ -188,7 +188,7 @@ export function registerLogsTools(agent: ObservabilityMCP) {
minutesAgoParam,
},
async (params) => {
const accountId = agent.getActiveAccountId()
const accountId = await agent.getActiveAccountId()
if (!accountId) {
return {
content: [
Expand Down Expand Up @@ -248,7 +248,7 @@ export function registerLogsTools(agent: ObservabilityMCP) {
'Get available telemetry keys for a Cloudflare Worker',
{ scriptName: workerNameParam, minutesAgoParam },
async (params) => {
const accountId = agent.getActiveAccountId()
const accountId = await agent.getActiveAccountId()
if (!accountId) {
return {
content: [
Expand Down
16 changes: 14 additions & 2 deletions apps/workers-observability/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "mcp-cloudflare-workers-observability-dev",
"migrations": [
{
"new_sqlite_classes": ["ObservabilityMCP"],
"new_sqlite_classes": ["UserDetails", "ObservabilityMCP"],
"tag": "v1"
}
],
Expand All @@ -22,6 +22,10 @@
{
"class_name": "ObservabilityMCP",
"name": "MCP_OBJECT"
},
{
"class_name": "UserDetails",
"name": "USER_DETAILS"
}
]
},
Expand Down Expand Up @@ -59,6 +63,10 @@
{
"class_name": "ObservabilityMCP",
"name": "MCP_OBJECT"
},
{
"class_name": "UserDetails",
"name": "USER_DETAILS"
}
]
},
Expand All @@ -72,7 +80,7 @@
"ENVIRONMENT": "staging",
"GIT_HASH": "OVERRIDEN_DURING_DEPLOYMENT",
"SENTRY_DSN": "https://[email protected]/1764",
"MCP_SERVER_NAME": "workers-staging-observability",
"MCP_SERVER_NAME": "workers-observability-staging",
"MCP_SERVER_VERSION": "1.0.0"
},
"analytics_engine_datasets": [
Expand All @@ -91,6 +99,10 @@
{
"class_name": "ObservabilityMCP",
"name": "MCP_OBJECT"
},
{
"class_name": "UserDetails",
"name": "USER_DETAILS"
}
]
},
Expand Down
Loading