Skip to content

Commit c009a13

Browse files
authored
Persist activeAccountId in UserDetails DO so we can persist activeAccountId selection across sessions (#81)
* Add RequiredScopes, and remove unnecessary observability scope from container and bindings servers * Persist activeAccountId in UserDetails DO so we can persist activeAccountId selection across sessions
1 parent 57f7cd7 commit c009a13

File tree

18 files changed

+280
-84
lines changed

18 files changed

+280
-84
lines changed

apps/docs-autorag/src/context.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { CloudflareDocumentationMCP } from './index'
33
export interface Env {
44
ENVIRONMENT: 'development' | 'staging' | 'production'
55
AUTORAG_NAME: 'cloudflare-docs-autorag'
6-
MCP_SERVER_NAME: 'PLACEHOLDER'
7-
MCP_SERVER_VERSION: 'PLACEHOLDER'
6+
MCP_SERVER_NAME: string
7+
MCP_SERVER_VERSION: string
88
MCP_OBJECT: DurableObjectNamespace<CloudflareDocumentationMCP>
99
MCP_METRICS: AnalyticsEngineDataset
1010
AI: Ai

apps/sandbox-container/server/context.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import type { ContainerManager, ContainerMcpAgent } from './index'
22

33
export interface Env {
44
OAUTH_KV: KVNamespace
5-
CLOUDFLARE_CLIENT_ID: '<PLACEHOLDER>'
6-
CLOUDFLARE_CLIENT_SECRET: '<PLACEHOLDER>'
5+
CLOUDFLARE_CLIENT_ID: string
6+
CLOUDFLARE_CLIENT_SECRET: string
77
ENVIRONMENT: 'dev'
8-
MCP_SERVER_NAME: '<PLACEHOLDER>'
9-
MCP_SERVER_VERSION: '<PLACEHOLDER>'
8+
MCP_SERVER_NAME: string
9+
MCP_SERVER_VERSION: string
1010
OPENAI_API_KEY: string
1111
CONTAINER_MCP_AGENT: DurableObjectNamespace<ContainerMcpAgent>
1212
CONTAINER_MANAGER: DurableObjectNamespace<ContainerManager>

apps/workers-bindings/src/context.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
12
import type { WorkersBindingsMCP } from './index'
23

34
export interface Env {
45
OAUTH_KV: KVNamespace
56
ENVIRONMENT: 'development' | 'staging' | 'production'
6-
MCP_SERVER_NAME: '<PLACEHOLDER>'
7-
MCP_SERVER_VERSION: '<PLACEHOLDER>'
8-
CLOUDFLARE_CLIENT_ID: '<PLACEHOLDER>'
9-
CLOUDFLARE_CLIENT_SECRET: '<PLACEHOLDER>'
7+
MCP_SERVER_NAME: string
8+
MCP_SERVER_VERSION: string
9+
CLOUDFLARE_CLIENT_ID: string
10+
CLOUDFLARE_CLIENT_SECRET: string
1011
MCP_OBJECT: DurableObjectNamespace<WorkersBindingsMCP>
12+
USER_DETAILS: DurableObjectNamespace<UserDetails>
1113
MCP_METRICS: AnalyticsEngineDataset
1214
}

apps/workers-bindings/src/index.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
createAuthHandlers,
66
handleTokenExchangeCallback,
77
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
8+
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
89
import { getEnv } from '@repo/mcp-common/src/env'
910
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
1011
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
@@ -18,6 +19,8 @@ import { MetricsTracker } from '@repo/mcp-observability'
1819
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
1920
import type { Env } from './context'
2021

22+
export { UserDetails }
23+
2124
const env = getEnv<Env>()
2225

2326
const metrics = new MetricsTracker(env.MCP_METRICS, {
@@ -73,24 +76,25 @@ export class WorkersBindingsMCP extends McpAgent<Env, WorkersBindingsMCPState, P
7376
registerR2BucketTools(this)
7477
registerD1Tools(this)
7578
}
76-
getActiveAccountId() {
77-
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
79+
80+
async getActiveAccountId() {
7881
try {
79-
return this.state.activeAccountId ?? null
82+
// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
83+
// we do this so we can persist activeAccountId across sessions
84+
const userDetails = getUserDetails(env, this.props.user.id)
85+
return await userDetails.getActiveAccountId()
8086
} catch (e) {
87+
this.server.recordError(e)
8188
return null
8289
}
8390
}
8491

85-
setActiveAccountId(accountId: string) {
86-
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
92+
async setActiveAccountId(accountId: string) {
8793
try {
88-
this.setState({
89-
...this.state,
90-
activeAccountId: accountId,
91-
})
94+
const userDetails = getUserDetails(env, this.props.user.id)
95+
await userDetails.setActiveAccountId(accountId)
9296
} catch (e) {
93-
return null
97+
this.server.recordError(e)
9498
}
9599
}
96100
}

apps/workers-bindings/wrangler.jsonc

+20-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
{
2020
"class_name": "WorkersBindingsMCP",
2121
"name": "MCP_OBJECT"
22+
},
23+
{
24+
"class_name": "UserDetails",
25+
"name": "USER_DETAILS"
2226
}
2327
]
2428
},
@@ -59,6 +63,11 @@
5963
{
6064
"class_name": "WorkersBindingsMCP",
6165
"name": "MCP_OBJECT"
66+
},
67+
{
68+
"class_name": "UserDetails",
69+
"name": "USER_DETAILS",
70+
"script_name": "mcp-cloudflare-workers-observability-staging"
6271
}
6372
]
6473
},
@@ -69,7 +78,9 @@
6978
}
7079
],
7180
"vars": {
72-
"ENVIRONMENT": "staging"
81+
"ENVIRONMENT": "staging",
82+
"MCP_SERVER_NAME": "workers-bindings-staging",
83+
"MCP_SERVER_VERSION": "1.0.0"
7384
},
7485
"analytics_engine_datasets": [
7586
{
@@ -87,6 +98,11 @@
8798
{
8899
"class_name": "WorkersBindingsMCP",
89100
"name": "MCP_OBJECT"
101+
},
102+
{
103+
"class_name": "UserDetails",
104+
"name": "USER_DETAILS",
105+
"script_name": "mcp-cloudflare-workers-observability-production"
90106
}
91107
]
92108
},
@@ -97,7 +113,9 @@
97113
}
98114
],
99115
"vars": {
100-
"ENVIRONMENT": "production"
116+
"ENVIRONMENT": "production",
117+
"MCP_SERVER_NAME": "workers-bindings",
118+
"MCP_SERVER_VERSION": "1.0.0"
101119
},
102120
"analytics_engine_datasets": [
103121
{
+10-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
12
import type { ObservabilityMCP } from './index'
23

34
export interface Env {
45
OAUTH_KV: KVNamespace
56
ENVIRONMENT: 'development' | 'staging' | 'production'
6-
MCP_SERVER_NAME: 'PLACEHOLDER'
7-
MCP_SERVER_VERSION: 'PLACEHOLDER'
8-
CLOUDFLARE_CLIENT_ID: 'PLACEHOLDER'
9-
CLOUDFLARE_CLIENT_SECRET: 'PLACEHOLDER'
7+
MCP_SERVER_NAME: string
8+
MCP_SERVER_VERSION: string
9+
CLOUDFLARE_CLIENT_ID: string
10+
CLOUDFLARE_CLIENT_SECRET: string
1011
MCP_OBJECT: DurableObjectNamespace<ObservabilityMCP>
12+
USER_DETAILS: DurableObjectNamespace<UserDetails>
1113
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'
14+
SENTRY_ACCESS_CLIENT_ID: string
15+
SENTRY_ACCESS_CLIENT_SECRET: string
16+
GIT_HASH: string
17+
SENTRY_DSN: string
1618
}

apps/workers-observability/src/index.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
createAuthHandlers,
66
handleTokenExchangeCallback,
77
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
8+
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details'
89
import { getEnv } from '@repo/mcp-common/src/env'
910
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
1011
import { initSentryWithUser } from '@repo/mcp-common/src/sentry'
@@ -18,6 +19,8 @@ import { registerLogsTools } from './tools/logs'
1819
import type { AccountSchema, UserSchema } from '@repo/mcp-common/src/cloudflare-oauth-handler'
1920
import type { Env } from './context'
2021

22+
export { UserDetails }
23+
2124
const env = getEnv<Env>()
2225

2326
const metrics = new MetricsTracker(env.MCP_METRICS, {
@@ -72,26 +75,24 @@ export class ObservabilityMCP extends McpAgent<Env, State, Props> {
7275
registerLogsTools(this)
7376
}
7477

75-
getActiveAccountId() {
76-
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
78+
async getActiveAccountId() {
7779
try {
78-
return this.state.activeAccountId ?? null
80+
// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
81+
// we do this so we can persist activeAccountId across sessions
82+
const userDetails = getUserDetails(env, this.props.user.id)
83+
return await userDetails.getActiveAccountId()
7984
} catch (e) {
8085
this.server.recordError(e)
8186
return null
8287
}
8388
}
8489

85-
setActiveAccountId(accountId: string) {
86-
// TODO: Figure out why this fail sometimes, and why we need to wrap this in a try catch
90+
async setActiveAccountId(accountId: string) {
8791
try {
88-
this.setState({
89-
...this.state,
90-
activeAccountId: accountId,
91-
})
92+
const userDetails = getUserDetails(env, this.props.user.id)
93+
await userDetails.setActiveAccountId(accountId)
9294
} catch (e) {
9395
this.server.recordError(e)
94-
return null
9596
}
9697
}
9798
}

apps/workers-observability/src/tools/logs.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export function registerLogsTools(agent: ObservabilityMCP) {
123123
rayId: rayIdParam,
124124
},
125125
async (params) => {
126-
const accountId = agent.getActiveAccountId()
126+
const accountId = await agent.getActiveAccountId()
127127
if (!accountId) {
128128
return {
129129
content: [
@@ -188,7 +188,7 @@ export function registerLogsTools(agent: ObservabilityMCP) {
188188
minutesAgoParam,
189189
},
190190
async (params) => {
191-
const accountId = agent.getActiveAccountId()
191+
const accountId = await agent.getActiveAccountId()
192192
if (!accountId) {
193193
return {
194194
content: [
@@ -248,7 +248,7 @@ export function registerLogsTools(agent: ObservabilityMCP) {
248248
'Get available telemetry keys for a Cloudflare Worker',
249249
{ scriptName: workerNameParam, minutesAgoParam },
250250
async (params) => {
251-
const accountId = agent.getActiveAccountId()
251+
const accountId = await agent.getActiveAccountId()
252252
if (!accountId) {
253253
return {
254254
content: [

apps/workers-observability/wrangler.jsonc

+14-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"name": "mcp-cloudflare-workers-observability-dev",
1111
"migrations": [
1212
{
13-
"new_sqlite_classes": ["ObservabilityMCP"],
13+
"new_sqlite_classes": ["UserDetails", "ObservabilityMCP"],
1414
"tag": "v1"
1515
}
1616
],
@@ -22,6 +22,10 @@
2222
{
2323
"class_name": "ObservabilityMCP",
2424
"name": "MCP_OBJECT"
25+
},
26+
{
27+
"class_name": "UserDetails",
28+
"name": "USER_DETAILS"
2529
}
2630
]
2731
},
@@ -59,6 +63,10 @@
5963
{
6064
"class_name": "ObservabilityMCP",
6165
"name": "MCP_OBJECT"
66+
},
67+
{
68+
"class_name": "UserDetails",
69+
"name": "USER_DETAILS"
6270
}
6371
]
6472
},
@@ -72,7 +80,7 @@
7280
"ENVIRONMENT": "staging",
7381
"GIT_HASH": "OVERRIDEN_DURING_DEPLOYMENT",
7482
"SENTRY_DSN": "https://[email protected]/1764",
75-
"MCP_SERVER_NAME": "workers-staging-observability",
83+
"MCP_SERVER_NAME": "workers-observability-staging",
7684
"MCP_SERVER_VERSION": "1.0.0"
7785
},
7886
"analytics_engine_datasets": [
@@ -91,6 +99,10 @@
9199
{
92100
"class_name": "ObservabilityMCP",
93101
"name": "MCP_OBJECT"
102+
},
103+
{
104+
"class_name": "UserDetails",
105+
"name": "USER_DETAILS"
94106
}
95107
]
96108
},

0 commit comments

Comments
 (0)