CF rejects deleting McpSessionDO while MCP_SESSION still binds it (code 10061), and won't reuse a deleted class name. Rename the concrete class + all refs to McpSessionDOSqlite, repoint the binding, and have v2 delete the old KV McpSessionDO + create the new SQLite class. Verified the built worker exports McpSessionDOSqlite and nothing named McpSessionDO.
Unified · Split
| @@ -13,7 +13,7 @@ import { ApiKeyService } from "./auth/api-keys"; · 12 unmodified lines | |||
| 13 | import { cloudIdentityFailureStrategy, workosIdentityLayer } from "./auth/workos-auth-provider"; | 13 | import { cloudIdentityFailureStrategy, workosIdentityLayer } from "./auth/workos-auth-provider"; |
| 14 | import { DbService } from "./db/db"; | 14 | import { DbService } from "./db/db"; |
| 15 | import { cloudMcpAuth } from "./mcp"; | 15 | import { cloudMcpAuth } from "./mcp"; |
| 16 | import { McpSessionDO } from "./mcp/session-durable-object"; | 16 | import { McpSessionDOSqlite } from "./mcp/session-durable-object"; |
| 17 | import { ErrorCaptureLive } from "./observability"; | 17 | import { ErrorCaptureLive } from "./observability"; |
| 18 | import { AutumnService } from "./extensions/billing/service"; | 18 | import { AutumnService } from "./extensions/billing/service"; |
| 19 | import { | 19 | import { |
| @@ -112,7 +112,7 @@ const { appLayer, toWebHandler, mcpExport } = ExecutorApp.make({ · 92 unmodified lines | |||
| 112 | failure: cloudIdentityFailureStrategy, | 112 | failure: cloudIdentityFailureStrategy, |
| 113 | // The MCP session Durable Object class — a top-level Workers export a Layer | 113 | // The MCP session Durable Object class — a top-level Workers export a Layer |
| 114 | // can't return; surfaced so `server.ts` can re-export it. | 114 | // can't return; surfaced so `server.ts` can re-export it. |
| 115 | mcpExport: McpSessionDO, | 115 | mcpExport: McpSessionDOSqlite, |
| 116 | }, | 116 | }, |
| 117 | // The long-lived (boot-scoped) context provideMerge'd under everything: the | 117 | // The long-lived (boot-scoped) context provideMerge'd under everything: the |
| 118 | // WorkOS control plane (the raw `WorkOSClient` + `ApiKeyService` the per-request | 118 | // WorkOS control plane (the raw `WorkOSClient` + `ApiKeyService` the per-request |
| @@ -135,7 +135,7 @@ const { appLayer, toWebHandler, mcpExport } = ExecutorApp.make({ · 16 unmodified lines | |||
| 135 | requestScoped: RequestScopedServicesLive, | 135 | requestScoped: RequestScopedServicesLive, |
| 136 | }); | 136 | }); |
| 137 | 137 | ||
| 138 | export { McpSessionDO }; | 138 | export { McpSessionDOSqlite }; |
| 139 | 139 | ||
| 140 | export const CloudAppLayer = appLayer; | 140 | export const CloudAppLayer = appLayer; |
| 141 | export const cloudMcpExport = mcpExport; | 141 | export const cloudMcpExport = mcpExport; |
| @@ -15,7 +15,7 @@ import { · 14 unmodified lines | |||
| 15 | import type { McpSessionProps } from "@executor-js/cloudflare/mcp/agent-durable-object"; | 15 | import type { McpSessionProps } from "@executor-js/cloudflare/mcp/agent-durable-object"; |
| 16 | 16 | ||
| 17 | import { cloudMcpAuth } from "./auth-provider"; | 17 | import { cloudMcpAuth } from "./auth-provider"; |
| 18 | import { McpSessionDO } from "./session-durable-object"; | 18 | import { McpSessionDOSqlite } from "./session-durable-object"; |
| 19 | 19 | ||
| 20 | interface McpAgentSessionStub { | 20 | interface McpAgentSessionStub { |
| 21 | readonly validateMcpSessionOwner: (identity: { | 21 | readonly validateMcpSessionOwner: (identity: { |
| @@ -111,7 +111,7 @@ const propsForPrincipal = ( · 89 unmodified lines | |||
| 111 | }); | 111 | }); |
| 112 | 112 | ||
| 113 | export const makeCloudMcpAgentHandler = () => { | 113 | export const makeCloudMcpAgentHandler = () => { |
| 114 | const serve = McpSessionDO.serve("/mcp", { | 114 | const serve = McpSessionDOSqlite.serve("/mcp", { |
| 115 | binding: "MCP_SESSION", | 115 | binding: "MCP_SESSION", |
| 116 | transport: "streamable-http", | 116 | transport: "streamable-http", |
| 117 | }); | 117 | }); |
| @@ -157,7 +157,7 @@ const makeSessionServices = (dbHandle: CloudSessionDbHandle) => { · 156 unmodified lines | |||
| 157 | // Durable Object | 157 | // Durable Object |
| 158 | // --------------------------------------------------------------------------- | 158 | // --------------------------------------------------------------------------- |
| 159 | 159 | ||
| 160 | export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandle> { | 160 | export class McpSessionDOSqlite extends McpAgentSessionDOBase<Env, CloudSessionDbHandle> { |
| 161 | protected override openSessionDb(): CloudSessionDbHandle { | 161 | protected override openSessionDb(): CloudSessionDbHandle { |
| 162 | return makeDbHandle({ | 162 | return makeDbHandle({ |
| 163 | idleTimeout: LONG_LIVED_DB_IDLE_TIMEOUT_SECONDS, | 163 | idleTimeout: LONG_LIVED_DB_IDLE_TIMEOUT_SECONDS, |
| @@ -181,7 +181,7 @@ export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandl · 17 unmodified lines | |||
| 181 | elicitationMode: token.elicitationMode, | 181 | elicitationMode: token.elicitationMode, |
| 182 | } satisfies SessionMeta; | 182 | } satisfies SessionMeta; |
| 183 | }).pipe( | 183 | }).pipe( |
| 184 | Effect.withSpan("McpSessionDO.resolveSessionMeta"), | 184 | Effect.withSpan("McpSessionDOSqlite.resolveSessionMeta"), |
| 185 | Effect.provide(makeSessionServices(dbHandle)), | 185 | Effect.provide(makeSessionServices(dbHandle)), |
| 186 | Effect.ensuring(Effect.promise(() => dbHandle.end())), | 186 | Effect.ensuring(Effect.promise(() => dbHandle.end())), |
| 187 | // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: a vanished org is a defect; the worker already verified the bearer | 187 | // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: a vanished org is a defect; the worker already verified the bearer |
| @@ -208,7 +208,7 @@ export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandl · 20 unmodified lines | |||
| 208 | // billing service degrades to a no-op tracker, so this stays inert in | 208 | // billing service degrades to a no-op tracker, so this stays inert in |
| 209 | // cloud dev/preview environments that run without a billing backend. | 209 | // cloud dev/preview environments that run without a billing backend. |
| 210 | Effect.provide(CloudMeteredExecutionStackLayer.pipe(Layer.provide(AutumnService.Default))), | 210 | Effect.provide(CloudMeteredExecutionStackLayer.pipe(Layer.provide(AutumnService.Default))), |
| 211 | Effect.withSpan("McpSessionDO.makeExecutionStack"), | 211 | Effect.withSpan("McpSessionDOSqlite.makeExecutionStack"), |
| 212 | ); | 212 | ); |
| 213 | // Build the description here so `executor.connections.list()` stays under | 213 | // Build the description here so `executor.connections.list()` stays under |
| 214 | // the DO startup span and the MCP SDK receives a concrete string instead | 214 | // the DO startup span and the MCP SDK receives a concrete string instead |
| @@ -235,10 +235,10 @@ export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandl · 20 unmodified lines | |||
| 235 | }), | 235 | }), |
| 236 | } | 236 | } |
| 237 | : { mode: sessionElicitationMode }, | 237 | : { mode: sessionElicitationMode }, |
| 238 | }).pipe(Effect.withSpan("McpSessionDO.createExecutorMcpServer")); | 238 | }).pipe(Effect.withSpan("McpSessionDOSqlite.createExecutorMcpServer")); |
| 239 | return { mcpServer, engine } satisfies BuiltMcpServer; | 239 | return { mcpServer, engine } satisfies BuiltMcpServer; |
| 240 | }).pipe( | 240 | }).pipe( |
| 241 | Effect.withSpan("McpSessionDO.buildMcpServer"), | 241 | Effect.withSpan("McpSessionDOSqlite.buildMcpServer"), |
| 242 | Effect.provide(makeSessionServices(dbHandle)), | 242 | Effect.provide(makeSessionServices(dbHandle)), |
| 243 | // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: runtime-build failures surface as the base's tapCause/cleanup defect | 243 | // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: runtime-build failures surface as the base's tapCause/cleanup defect |
| 244 | Effect.orDie, | 244 | Effect.orDie, |
| @@ -12,7 +12,7 @@ import handler from "@tanstack/react-start/server-entry"; · 11 unmodified lines | |||
| 12 | import { isAppOwnedPath } from "./app-paths"; | 12 | import { isAppOwnedPath } from "./app-paths"; |
| 13 | import { makeCloudMcpAgentHandler } from "./mcp/agent-handler"; | 13 | import { makeCloudMcpAgentHandler } from "./mcp/agent-handler"; |
| 14 | import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount"; | 14 | import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount"; |
| 15 | import { McpSessionDO as McpSessionDOBase } from "./mcp/session-durable-object"; | 15 | import { McpSessionDOSqlite as McpSessionDOBase } from "./mcp/session-durable-object"; |
| 16 | import { browserTracesResponse } from "./observability/browser-traces"; | 16 | import { browserTracesResponse } from "./observability/browser-traces"; |
| 17 | import { flushTracerProvider, installTracerProvider } from "./observability/telemetry"; | 17 | import { flushTracerProvider, installTracerProvider } from "./observability/telemetry"; |
| 18 | 18 | ||
| @@ -41,7 +41,7 @@ const sentryOptions = (env: Env) => ({ · 22 unmodified lines | |||
| 41 | // not a global fetch wrapper. | 41 | // not a global fetch wrapper. |
| 42 | // --------------------------------------------------------------------------- | 42 | // --------------------------------------------------------------------------- |
| 43 | 43 | ||
| 44 | export const McpSessionDO = Sentry.instrumentDurableObjectWithSentry( | 44 | export const McpSessionDOSqlite = Sentry.instrumentDurableObjectWithSentry( |
| 45 | sentryOptions, | 45 | sentryOptions, |
| 46 | McpSessionDOBase, | 46 | McpSessionDOBase, |
| 47 | ); | 47 | ); |
| @@ -20,16 +20,17 @@ · 19 unmodified lines | |||
| 20 | "bindings": [ | 20 | "bindings": [ |
| 21 | { | 21 | { |
| 22 | "name": "MCP_SESSION", | 22 | "name": "MCP_SESSION", |
| 23 | "class_name": "McpSessionDO", | 23 | "class_name": "McpSessionDOSqlite", |
| 24 | }, | 24 | }, |
| 25 | ], | 25 | ], |
| 26 | }, | 26 | }, |
| 27 | // The MCP session DO moved to the Cloudflare Agents (`McpAgent`) base, which | 27 | // The MCP session DO moved to the Cloudflare Agents (`McpAgent`) base, which |
| 28 | // stores state in SQLite. `McpSessionDO` was originally created with the | 28 | // stores state in SQLite. The original `McpSessionDO` was created with the |
| 29 | // key-value backend (`new_classes`), and Cloudflare cannot convert a live class | 29 | // key-value backend (`new_classes`), and Cloudflare cannot convert a live class |
| 30 | // to SQLite in place. Session state is ephemeral, so v2 deletes the KV class and | 30 | // to SQLite in place, nor delete a class while a binding still references it. |
| 31 | // v3 recreates the same name on the SQLite backend. Keeping the name avoids any | 31 | // Session state is ephemeral, so v2 repoints the `MCP_SESSION` binding to a new |
| 32 | // binding/export rename. | 32 | // SQLite-backed class (`McpSessionDOSqlite`) and deletes the old KV `McpSessionDO` |
| 33 | // (now unreferenced). The worker exports `McpSessionDOSqlite`, not `McpSessionDO`. | ||
| 33 | "migrations": [ | 34 | "migrations": [ |
| 34 | { | 35 | { |
| 35 | "tag": "v1", | 36 | "tag": "v1", |
| @@ -38,10 +39,7 @@ · 2 unmodified lines | |||
| 38 | { | 39 | { |
| 39 | "tag": "v2", | 40 | "tag": "v2", |
| 40 | "deleted_classes": ["McpSessionDO"], | 41 | "deleted_classes": ["McpSessionDO"], |
| 41 | }, | 42 | "new_sqlite_classes": ["McpSessionDOSqlite"], |
| 42 | { | ||
| 43 | "tag": "v3", | ||
| 44 | "new_sqlite_classes": ["McpSessionDO"], | ||
| 45 | }, | 43 | }, |
| 46 | ], | 44 | ], |
| 47 | "services": [ | 45 | "services": [ |