History

fix(cloud): use a new SQLite DO class name so the binding can move off the deleted KV class

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.

Rhys Sullivan committed 4 months ago

Commit 7fca0961 parent 2a8b1e05 files · +19 −21Browse files

Unified · Split

5 files
  1. apps/cloud/src/app.ts +3 −3
  2. apps/cloud/src/mcp/agent-handler.ts +2 −2
  3. apps/cloud/src/mcp/session-durable-object.ts +5 −5
  4. apps/cloud/src/server.ts +2 −2
  5. apps/cloud/wrangler.jsonc +7 −9
apps/cloud/src/app.ts+3−3
@@ -13,7 +13,7 @@ import { ApiKeyService } from "./auth/api-keys"; · 12 unmodified lines
1313 import { cloudIdentityFailureStrategy, workosIdentityLayer } from "./auth/workos-auth-provider";
1414 import { DbService } from "./db/db";
1515 import { cloudMcpAuth } from "./mcp";
16−import { McpSessionDO } from "./mcp/session-durable-object";
16+import { McpSessionDOSqlite } from "./mcp/session-durable-object";
1717 import { ErrorCaptureLive } from "./observability";
1818 import { AutumnService } from "./extensions/billing/service";
1919 import {
@@ -112,7 +112,7 @@ const { appLayer, toWebHandler, mcpExport } = ExecutorApp.make({ · 92 unmodified lines
112112 failure: cloudIdentityFailureStrategy,
113113 // The MCP session Durable Object class — a top-level Workers export a Layer
114114 // can't return; surfaced so `server.ts` can re-export it.
115− mcpExport: McpSessionDO,
115+ mcpExport: McpSessionDOSqlite,
116116 },
117117 // The long-lived (boot-scoped) context provideMerge'd under everything: the
118118 // WorkOS control plane (the raw `WorkOSClient` + `ApiKeyService` the per-request
@@ -135,7 +135,7 @@ const { appLayer, toWebHandler, mcpExport } = ExecutorApp.make({ · 16 unmodified lines
135135 requestScoped: RequestScopedServicesLive,
136136 });
137137
138−export { McpSessionDO };
138+export { McpSessionDOSqlite };
139139
140140 export const CloudAppLayer = appLayer;
141141 export const cloudMcpExport = mcpExport;
apps/cloud/src/mcp/agent-handler.ts+2−2
@@ -15,7 +15,7 @@ import { · 14 unmodified lines
1515 import type { McpSessionProps } from "@executor-js/cloudflare/mcp/agent-durable-object";
1616
1717 import { cloudMcpAuth } from "./auth-provider";
18−import { McpSessionDO } from "./session-durable-object";
18+import { McpSessionDOSqlite } from "./session-durable-object";
1919
2020 interface McpAgentSessionStub {
2121 readonly validateMcpSessionOwner: (identity: {
@@ -111,7 +111,7 @@ const propsForPrincipal = ( · 89 unmodified lines
111111 });
112112
113113 export const makeCloudMcpAgentHandler = () => {
114− const serve = McpSessionDO.serve("/mcp", {
114+ const serve = McpSessionDOSqlite.serve("/mcp", {
115115 binding: "MCP_SESSION",
116116 transport: "streamable-http",
117117 });
apps/cloud/src/mcp/session-durable-object.ts+5−5
@@ -157,7 +157,7 @@ const makeSessionServices = (dbHandle: CloudSessionDbHandle) => { · 156 unmodified lines
157157 // Durable Object
158158 // ---------------------------------------------------------------------------
159159
160−export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandle> {
160+export class McpSessionDOSqlite extends McpAgentSessionDOBase<Env, CloudSessionDbHandle> {
161161 protected override openSessionDb(): CloudSessionDbHandle {
162162 return makeDbHandle({
163163 idleTimeout: LONG_LIVED_DB_IDLE_TIMEOUT_SECONDS,
@@ -181,7 +181,7 @@ export class McpSessionDO extends McpAgentSessionDOBase<Env, CloudSessionDbHandl · 17 unmodified lines
181181 elicitationMode: token.elicitationMode,
182182 } satisfies SessionMeta;
183183 }).pipe(
184− Effect.withSpan("McpSessionDO.resolveSessionMeta"),
184+ Effect.withSpan("McpSessionDOSqlite.resolveSessionMeta"),
185185 Effect.provide(makeSessionServices(dbHandle)),
186186 Effect.ensuring(Effect.promise(() => dbHandle.end())),
187187 // 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
208208 // billing service degrades to a no-op tracker, so this stays inert in
209209 // cloud dev/preview environments that run without a billing backend.
210210 Effect.provide(CloudMeteredExecutionStackLayer.pipe(Layer.provide(AutumnService.Default))),
211− Effect.withSpan("McpSessionDO.makeExecutionStack"),
211+ Effect.withSpan("McpSessionDOSqlite.makeExecutionStack"),
212212 );
213213 // Build the description here so `executor.connections.list()` stays under
214214 // 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
235235 }),
236236 }
237237 : { mode: sessionElicitationMode },
238− }).pipe(Effect.withSpan("McpSessionDO.createExecutorMcpServer"));
238+ }).pipe(Effect.withSpan("McpSessionDOSqlite.createExecutorMcpServer"));
239239 return { mcpServer, engine } satisfies BuiltMcpServer;
240240 }).pipe(
241− Effect.withSpan("McpSessionDO.buildMcpServer"),
241+ Effect.withSpan("McpSessionDOSqlite.buildMcpServer"),
242242 Effect.provide(makeSessionServices(dbHandle)),
243243 // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: runtime-build failures surface as the base's tapCause/cleanup defect
244244 Effect.orDie,
apps/cloud/src/server.ts+2−2
@@ -12,7 +12,7 @@ import handler from "@tanstack/react-start/server-entry"; · 11 unmodified lines
1212 import { isAppOwnedPath } from "./app-paths";
1313 import { makeCloudMcpAgentHandler } from "./mcp/agent-handler";
1414 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";
1616 import { browserTracesResponse } from "./observability/browser-traces";
1717 import { flushTracerProvider, installTracerProvider } from "./observability/telemetry";
1818
@@ -41,7 +41,7 @@ const sentryOptions = (env: Env) => ({ · 22 unmodified lines
4141 // not a global fetch wrapper.
4242 // ---------------------------------------------------------------------------
4343
44−export const McpSessionDO = Sentry.instrumentDurableObjectWithSentry(
44+export const McpSessionDOSqlite = Sentry.instrumentDurableObjectWithSentry(
4545 sentryOptions,
4646 McpSessionDOBase,
4747 );
apps/cloud/wrangler.jsonc+7−9
@@ -20,16 +20,17 @@ · 19 unmodified lines
2020 "bindings": [
2121 {
2222 "name": "MCP_SESSION",
23− "class_name": "McpSessionDO",
23+ "class_name": "McpSessionDOSqlite",
2424 },
2525 ],
2626 },
2727 // 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
2929 // 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
31− // v3 recreates the same name on the SQLite backend. Keeping the name avoids any
32− // binding/export rename.
30+ // to SQLite in place, nor delete a class while a binding still references it.
31+ // Session state is ephemeral, so v2 repoints the `MCP_SESSION` binding to a new
32+ // SQLite-backed class (`McpSessionDOSqlite`) and deletes the old KV `McpSessionDO`
33+ // (now unreferenced). The worker exports `McpSessionDOSqlite`, not `McpSessionDO`.
3334 "migrations": [
3435 {
3536 "tag": "v1",
@@ -38,10 +39,7 @@ · 2 unmodified lines
3839 {
3940 "tag": "v2",
4041 "deleted_classes": ["McpSessionDO"],
41− },
42− {
43− "tag": "v3",
44− "new_sqlite_classes": ["McpSessionDO"],
42+ "new_sqlite_classes": ["McpSessionDOSqlite"],
4543 },
4644 ],
4745 "services": [