From e839525de676f168c85ddcd48a4f25d076e78d18 Mon Sep 17 00:00:00 2001 From: Don Pansacola <1178461+donmasakayan@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:03:07 +1200 Subject: [PATCH 1/4] test(host-cloudflare): classify toolkit MCP paths --- apps/host-cloudflare/src/mcp/resource.test.ts | 29 +++++++++++++++++++ apps/host-cloudflare/src/mcp/resource.ts | 10 +++++++ 2 files changed, 39 insertions(+) create mode 100644 apps/host-cloudflare/src/mcp/resource.test.ts create mode 100644 apps/host-cloudflare/src/mcp/resource.ts diff --git a/apps/host-cloudflare/src/mcp/resource.test.ts b/apps/host-cloudflare/src/mcp/resource.test.ts new file mode 100644 index 0000000000..a29383aa0c --- /dev/null +++ b/apps/host-cloudflare/src/mcp/resource.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "@effect/vitest"; + +import { mcpResourceFromPath } from "./resource"; + +describe("mcpResourceFromPath", () => { + it("classifies the default MCP path", () => { + expect(mcpResourceFromPath("/mcp")).toEqual({ kind: "default" }); + }); + + it("classifies a toolkit MCP path", () => { + expect(mcpResourceFromPath("/mcp/toolkits/calendar-tools")).toEqual({ + kind: "toolkit", + slug: "calendar-tools", + }); + }); + + it.each([ + "/", + "/mcp/", + "/mcp/toolkits", + "/mcp/toolkits/", + "/mcp//toolkits/calendar-tools", + "/mcp/toolkits//calendar-tools", + "/mcp/toolkits/calendar-tools/extra", + "/api/toolkits/calendar-tools", + ])("rejects the non-serving path %s", (pathname) => { + expect(mcpResourceFromPath(pathname)).toBeNull(); + }); +}); diff --git a/apps/host-cloudflare/src/mcp/resource.ts b/apps/host-cloudflare/src/mcp/resource.ts new file mode 100644 index 0000000000..9d1d45f905 --- /dev/null +++ b/apps/host-cloudflare/src/mcp/resource.ts @@ -0,0 +1,10 @@ +import { defaultMcpResource, type McpResource } from "@executor-js/host-mcp"; + +export const mcpResourceFromPath = (pathname: string): McpResource | null => { + if (pathname === "/mcp") return defaultMcpResource; + + const toolkitMatch = /^\/mcp\/toolkits\/([^/]+)$/.exec(pathname); + return toolkitMatch?.[1] + ? { kind: "toolkit", slug: toolkitMatch[1] } + : null; +}; From 5aecad4b60f7df091f5053a3b00226dcb177a456 Mon Sep 17 00:00:00 2001 From: Don Pansacola <1178461+donmasakayan@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:08:14 +1200 Subject: [PATCH 2/4] fix(cloudflare): bind MCP sessions to their resource --- apps/cloud/src/mcp/agent-handler.ts | 17 +++++--- apps/host-cloudflare/src/mcp/agent-handler.ts | 43 +++++++++++++------ .../mcp/agent-session-durable-object.test.ts | 37 ++++++++++++---- .../src/mcp/agent-session-durable-object.ts | 17 +++++--- .../hosts/cloudflare/src/mcp/session-stub.ts | 2 + 5 files changed, 84 insertions(+), 32 deletions(-) diff --git a/apps/cloud/src/mcp/agent-handler.ts b/apps/cloud/src/mcp/agent-handler.ts index 0ec697c911..fb373db576 100644 --- a/apps/cloud/src/mcp/agent-handler.ts +++ b/apps/cloud/src/mcp/agent-handler.ts @@ -250,14 +250,22 @@ export const makeCloudMcpAgentHandler = () => { }); } + const resource = resourceFromPath(request); + if (sessionId) { let owner: "ok" | "not_found" | "forbidden" | "terminated"; // oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary: a Durable Object stub RPC rejects with a plain platform Error, never a typed failure try { - owner = await mcpSessionStub(env.MCP_SESSION, sessionId).validateMcpSessionOwner({ - accountId: outcome.principal.accountId, - organizationId: outcome.principal.organizationId, - }); + owner = await mcpSessionStub( + env.MCP_SESSION, + sessionId, + ).validateMcpSessionOwner( + { + accountId: outcome.principal.accountId, + organizationId: outcome.principal.organizationId, + }, + resource, + ); } catch (error) { // The sibling stub touchpoints in this handler are both guarded — the // `_cf_scheduleDestroy` call above with `Effect.ignore`, the @@ -286,7 +294,6 @@ export const makeCloudMcpAgentHandler = () => { } } - const resource = resourceFromPath(request); const props = await runTraced(request, propsForPrincipal(request, outcome.principal, resource)); (ctx as ExecutionContext & { props?: McpSessionProps }).props = props; const forwarded = withVerifiedIdentityHeaders( diff --git a/apps/host-cloudflare/src/mcp/agent-handler.ts b/apps/host-cloudflare/src/mcp/agent-handler.ts index a870277401..c069d45f3c 100644 --- a/apps/host-cloudflare/src/mcp/agent-handler.ts +++ b/apps/host-cloudflare/src/mcp/agent-handler.ts @@ -3,8 +3,8 @@ import { Effect, Predicate } from "effect"; import { McpAuthProvider, jsonRpcErrorBody, - defaultMcpResource, type AuthOutcome, + type McpResource, type Principal, } from "@executor-js/host-mcp"; import { @@ -19,6 +19,7 @@ import { mcpSessionStub } from "@executor-js/cloudflare/mcp/session-stub"; import type { CloudflareConfig, CloudflareEnv } from "../config"; import { cloudflareAccessMcpAuth } from "./auth"; +import { mcpResourceFromPath } from "./resource"; import { McpSessionDO } from "./session-durable-object"; const corsPreflightResponse = (): Response => @@ -72,6 +73,7 @@ const authenticate = (request: Request, config: CloudflareConfig) => const propsForPrincipal = ( request: Request, principal: Principal, + resource: McpResource, ): Effect.Effect => Effect.gen(function* () { const propagation = yield* currentPropagationHeaders(request); @@ -82,10 +84,7 @@ const propsForPrincipal = ( elicitationMode: readElicitationMode(request), artifactsEnabled: readArtifactsEnabled(request), searchToolsEnabled: readSearchToolsEnabled(request), - // host-cloudflare only routes the bare `/mcp` endpoint to the Agent - // bridge (see worker.ts), so the session always serves the default - // resource. - resource: defaultMcpResource, + resource, webOrigin: new URL(request.url).origin, }, propagation, @@ -93,10 +92,12 @@ const propsForPrincipal = ( }); export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => { - const serve = McpSessionDO.serve("/mcp", { + const serveOptions = { binding: "MCP_SESSION", transport: "streamable-http", - }); + } as const; + const serveDefault = McpSessionDO.serve("/mcp", serveOptions); + const serveToolkit = McpSessionDO.serve("/mcp/toolkits/:slug", serveOptions); return async (request: Request, env: CloudflareEnv, ctx: ExecutionContext): Promise => { if (request.method === "OPTIONS") return corsPreflightResponse(); @@ -116,15 +117,26 @@ export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => { return renderAuthError(auth, request, outcome); } + const resource = mcpResourceFromPath(new URL(request.url).pathname); + if (resource === null) { + return jsonRpcResponse(404, -32001, "MCP route not found"); + } + if (!sessionId && request.method === "DELETE") { return new Response(null, { status: 204, headers: { "access-control-allow-origin": "*" } }); } if (sessionId) { - const owner = await mcpSessionStub(env.MCP_SESSION, sessionId).validateMcpSessionOwner({ - accountId: outcome.principal.accountId, - organizationId: outcome.principal.organizationId, - }); + const owner = await mcpSessionStub( + env.MCP_SESSION, + sessionId, + ).validateMcpSessionOwner( + { + accountId: outcome.principal.accountId, + organizationId: outcome.principal.organizationId, + }, + resource, + ); if (owner === "not_found") { return jsonRpcResponse(404, -32001, "Session not found"); } @@ -138,7 +150,9 @@ export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => { } } - const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal)); + const props = await Effect.runPromise( + propsForPrincipal(request, outcome.principal, resource), + ); (ctx as ExecutionContext & { props?: McpSessionProps }).props = props; const forwarded = withVerifiedIdentityHeaders( request, @@ -146,8 +160,9 @@ export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => { accountId: outcome.principal.accountId, organizationId: outcome.principal.organizationId, }, - defaultMcpResource, + resource, ); - return serve.fetch(forwarded, env, ctx); + const target = resource.kind === "toolkit" ? serveToolkit : serveDefault; + return target.fetch(forwarded, env, ctx); }; }; diff --git a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts index 37c6b05107..b9aa7679af 100644 --- a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts +++ b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts @@ -6,7 +6,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import type { JSONRPCMessage, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js"; -import { defaultMcpResource } from "@executor-js/host-mcp"; +import { defaultMcpResource, type McpResource } from "@executor-js/host-mcp"; import type { ExecutionEngine, ExecutionResult, ResumeResponse } from "@executor-js/execution"; import { @@ -161,7 +161,7 @@ type HarnessSession = { validateMcpSessionOwner: (identity: { readonly accountId: string; readonly organizationId: string; - }) => Promise<"ok" | "not_found" | "forbidden" | "terminated">; + }, resource: McpResource) => Promise<"ok" | "not_found" | "forbidden" | "terminated">; }; class StaleCloseTransport implements Transport { @@ -539,10 +539,28 @@ describe("McpAgentSessionDOBase transport restore", () => { await session.alarm(); await expect( - session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1" }), + session.validateMcpSessionOwner( + { accountId: "user-1", organizationId: "org-1" }, + defaultMcpResource, + ), ).resolves.toBe("ok"); }); + it("rejects the same owner on a different MCP resource", async () => { + const session = await makeHarnessSession(); + const identity = { accountId: "user-1", organizationId: "org-1" }; + + await expect( + session.validateMcpSessionOwner(identity, defaultMcpResource), + ).resolves.toBe("ok"); + await expect( + session.validateMcpSessionOwner(identity, { + kind: "toolkit", + slug: "other-toolkit", + }), + ).resolves.toBe("forbidden"); + }); + it("single-flights concurrent same-session restore after idle disposal", async () => { const session = await makeHarnessSession(); const firstRestoreEntered = makeDeferred(); @@ -566,11 +584,11 @@ describe("McpAgentSessionDOBase transport restore", () => { const first = session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1", - }); + }, defaultMcpResource); const second = session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1", - }); + }, defaultMcpResource); await firstRestoreEntered.promise; await Promise.resolve(); @@ -602,7 +620,7 @@ describe("McpAgentSessionDOBase transport restore", () => { const restore = session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1", - }); + }, defaultMcpResource); const sdkStart = session.onStart(); await firstStartEntered.promise; @@ -710,7 +728,7 @@ describe("McpAgentSessionDOBase init survives a platform reset of its bookkeepin buildMcpServer: () => Effect.Effect<{ mcpServer: McpServer; engine: unknown }>; openSessionDb: () => { readonly end: () => void }; resolveSessionMeta: () => Effect.Effect; - validateMcpSessionOwner: (identity: McpApprovalOwner) => Promise; + validateMcpSessionOwner: (identity: McpApprovalOwner, resource: McpResource) => Promise; }; const sessionMeta: SessionMeta = { @@ -783,7 +801,10 @@ describe("McpAgentSessionDOBase init survives a platform reset of its bookkeepin expect(storage.alarm, "the write that failed left no alarm").toBeUndefined(); await expect( - session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1" }), + session.validateMcpSessionOwner( + { accountId: "user-1", organizationId: "org-1" }, + defaultMcpResource, + ), ).resolves.toBe("ok"); expect(storage.alarm, "the next request re-establishes the idle clock").toBeGreaterThan(0); }); diff --git a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts index dcdfc90519..f3f36198db 100644 --- a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts +++ b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts @@ -18,7 +18,11 @@ import { type PausedExecutionHooks, type ResumeFallbackOutcome, } from "@executor-js/host-mcp/tool-server"; -import { defaultMcpResource, type McpResource } from "@executor-js/host-mcp"; +import { + defaultMcpResource, + mcpResourceKey, + type McpResource, +} from "@executor-js/host-mcp"; import type { IncomingPropagationHeaders, McpElicitationMode } from "./do-headers"; import { classifyDurableObjectError, type DurableObjectFailure } from "./durable-object-errors"; @@ -1533,6 +1537,7 @@ export abstract class McpAgentSessionDOBase< async validateMcpSessionOwner( identity: McpApprovalOwner, + resource: McpResource, ): Promise<"ok" | "not_found" | "forbidden" | "terminated"> { const self = this; return Effect.runPromise( @@ -1561,10 +1566,12 @@ export abstract class McpAgentSessionDOBase< Effect.withSpan("McpSessionDO.restore_transport_runtime"), ); } - return identity.accountId === sessionMeta.userId && - identity.organizationId === sessionMeta.organizationId - ? ("ok" as const) - : ("forbidden" as const); + const ownerMatches = + identity.accountId === sessionMeta.userId && + identity.organizationId === sessionMeta.organizationId; + const resourceMatches = + mcpResourceKey(resource) === mcpResourceKey(sessionMeta.resource); + return ownerMatches && resourceMatches ? ("ok" as const) : ("forbidden" as const); }).pipe( Effect.withSpan("McpSessionDO.validateMcpSessionOwner"), // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: DO RPC exposes Promise results diff --git a/packages/hosts/cloudflare/src/mcp/session-stub.ts b/packages/hosts/cloudflare/src/mcp/session-stub.ts index 2e17f3a412..f2e9139240 100644 --- a/packages/hosts/cloudflare/src/mcp/session-stub.ts +++ b/packages/hosts/cloudflare/src/mcp/session-stub.ts @@ -1,4 +1,5 @@ import type { ResumeResponse } from "@executor-js/execution"; +import type { McpResource } from "@executor-js/host-mcp"; import type { IncomingTraceHeaders, @@ -17,6 +18,7 @@ export interface McpSessionNamespace { export interface McpSessionStub { readonly validateMcpSessionOwner: ( identity: McpApprovalOwner, + resource: McpResource, ) => Promise<"ok" | "not_found" | "forbidden" | "terminated">; readonly _cf_scheduleDestroy: () => Promise; readonly getPausedExecutionForApproval: ( From bf2bc48d91808047120097151e200a317ba7a68d Mon Sep 17 00:00:00 2001 From: Don Pansacola <1178461+donmasakayan@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:08:48 +1200 Subject: [PATCH 3/4] fix(host-cloudflare): serve toolkit MCP routes --- .../src/worker.e2e.node.test.ts | 71 +++++++++++++++++++ apps/host-cloudflare/src/worker.ts | 10 +-- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/apps/host-cloudflare/src/worker.e2e.node.test.ts b/apps/host-cloudflare/src/worker.e2e.node.test.ts index 738a358827..f64919c06b 100644 --- a/apps/host-cloudflare/src/worker.e2e.node.test.ts +++ b/apps/host-cloudflare/src/worker.e2e.node.test.ts @@ -302,6 +302,77 @@ describe("cloudflare host e2e (workerd/miniflare)", () => { expect(toolNames).toContain("execute"); }, 60_000); + it("serves toolkit MCP sessions and rejects cross-resource session reuse", async () => { + const createToolkit = await worker.fetch("/api/toolkits", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + owner: "org", + name: `Cloudflare Toolkit ${runId}`, + slug: `cloudflare-toolkit-${runId}`, + }), + }); + expect(createToolkit.status).toBe(200); + const toolkit = (await createToolkit.json()) as { id: string; slug: string }; + + const addConnection = await worker.fetch(`/api/toolkits/${toolkit.id}/connections`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ pattern: "executor.*" }), + }); + expect(addConnection.status).toBe(200); + + const accept = "application/json, text/event-stream"; + const toolkitPath = `/mcp/toolkits/${toolkit.slug}`; + const rpc = (path: string, sessionId: string | null, body: unknown) => + worker.fetch(path, { + method: "POST", + headers: { + "content-type": "application/json", + accept, + ...(sessionId ? { "mcp-session-id": sessionId } : {}), + }, + body: JSON.stringify(body), + }); + + const init = await rpc(toolkitPath, null, { + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { + protocolVersion: "2025-03-26", + capabilities: {}, + clientInfo: { name: "toolkit-route-test", version: "1" }, + }, + }); + expect(init.status).toBe(200); + const sessionId = init.headers.get("mcp-session-id"); + expect(sessionId).toBeTruthy(); + + await rpc(toolkitPath, sessionId, { + jsonrpc: "2.0", + method: "notifications/initialized", + }); + + const list = await rpc(toolkitPath, sessionId, { + jsonrpc: "2.0", + id: 2, + method: "tools/list", + }); + expect(list.status).toBe(200); + const listed = await readMcpJson<{ + result?: { tools?: ReadonlyArray<{ name: string }> }; + }>(list); + expect(listed.result?.tools?.map((tool) => tool.name)).toContain("execute"); + + const reusedOnDefault = await rpc("/mcp", sessionId, { + jsonrpc: "2.0", + id: 3, + method: "tools/list", + }); + expect(reusedOnDefault.status).toBe(403); + }, 60_000); + it("serves streamable HTTP GET only for initialized sessions", async () => { const missing = await worker.fetch("/mcp", { method: "GET", diff --git a/apps/host-cloudflare/src/worker.ts b/apps/host-cloudflare/src/worker.ts index ac9c1b30b7..b9964fac4a 100644 --- a/apps/host-cloudflare/src/worker.ts +++ b/apps/host-cloudflare/src/worker.ts @@ -4,6 +4,7 @@ import { missingCloudflareAccessVars, type CloudflareEnv, } from "./config"; +import { mcpResourceFromPath } from "./mcp/resource"; // The MCP Durable Object classes, bound in wrangler.jsonc. They must be exported // at the Worker entry module scope for the runtime to find them. @@ -11,9 +12,9 @@ export { McpExecutionOwnerDirectoryDO, McpSessionDO } from "./mcp"; // --------------------------------------------------------------------------- // The Worker fetch entry. Most requests go to `ExecutorApp.make`'s Effect web -// handler. `/mcp` stays at this edge boundary because `McpAgent.serve()` needs -// the Cloudflare `ExecutionContext` to pass authenticated session props into the -// hibernatable Durable Object bridge. +// handler. `/mcp` and `/mcp/toolkits/:slug` stay at this edge boundary because +// `McpAgent.serve()` needs the Cloudflare `ExecutionContext` to pass +// authenticated session props into the hibernatable Durable Object bridge. // --------------------------------------------------------------------------- let handlerPromise: Promise<{ @@ -48,7 +49,8 @@ export default { } const serve = await resolveHandler(env); - if (new URL(request.url).pathname === "/mcp") { + const resource = mcpResourceFromPath(new URL(request.url).pathname); + if (resource !== null) { return serve.mcp(request, env, ctx); } return serve.app(request); From 38fb43bb7076214bd1480e9726eeb4dffa5c3496 Mon Sep 17 00:00:00 2001 From: Don Pansacola <1178461+donmasakayan@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:21:24 +1200 Subject: [PATCH 4/4] style: format toolkit route changes --- apps/cloud/src/mcp/agent-handler.ts | 5 +- apps/host-cloudflare/src/mcp/agent-handler.ts | 9 +--- apps/host-cloudflare/src/mcp/resource.ts | 4 +- .../mcp/agent-session-durable-object.test.ts | 48 +++++++++++-------- .../src/mcp/agent-session-durable-object.ts | 9 +--- 5 files changed, 35 insertions(+), 40 deletions(-) diff --git a/apps/cloud/src/mcp/agent-handler.ts b/apps/cloud/src/mcp/agent-handler.ts index fb373db576..ad424bf80f 100644 --- a/apps/cloud/src/mcp/agent-handler.ts +++ b/apps/cloud/src/mcp/agent-handler.ts @@ -256,10 +256,7 @@ export const makeCloudMcpAgentHandler = () => { let owner: "ok" | "not_found" | "forbidden" | "terminated"; // oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary: a Durable Object stub RPC rejects with a plain platform Error, never a typed failure try { - owner = await mcpSessionStub( - env.MCP_SESSION, - sessionId, - ).validateMcpSessionOwner( + owner = await mcpSessionStub(env.MCP_SESSION, sessionId).validateMcpSessionOwner( { accountId: outcome.principal.accountId, organizationId: outcome.principal.organizationId, diff --git a/apps/host-cloudflare/src/mcp/agent-handler.ts b/apps/host-cloudflare/src/mcp/agent-handler.ts index c069d45f3c..fef2c2780c 100644 --- a/apps/host-cloudflare/src/mcp/agent-handler.ts +++ b/apps/host-cloudflare/src/mcp/agent-handler.ts @@ -127,10 +127,7 @@ export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => { } if (sessionId) { - const owner = await mcpSessionStub( - env.MCP_SESSION, - sessionId, - ).validateMcpSessionOwner( + const owner = await mcpSessionStub(env.MCP_SESSION, sessionId).validateMcpSessionOwner( { accountId: outcome.principal.accountId, organizationId: outcome.principal.organizationId, @@ -150,9 +147,7 @@ export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => { } } - const props = await Effect.runPromise( - propsForPrincipal(request, outcome.principal, resource), - ); + const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal, resource)); (ctx as ExecutionContext & { props?: McpSessionProps }).props = props; const forwarded = withVerifiedIdentityHeaders( request, diff --git a/apps/host-cloudflare/src/mcp/resource.ts b/apps/host-cloudflare/src/mcp/resource.ts index 9d1d45f905..193a3efc49 100644 --- a/apps/host-cloudflare/src/mcp/resource.ts +++ b/apps/host-cloudflare/src/mcp/resource.ts @@ -4,7 +4,5 @@ export const mcpResourceFromPath = (pathname: string): McpResource | null => { if (pathname === "/mcp") return defaultMcpResource; const toolkitMatch = /^\/mcp\/toolkits\/([^/]+)$/.exec(pathname); - return toolkitMatch?.[1] - ? { kind: "toolkit", slug: toolkitMatch[1] } - : null; + return toolkitMatch?.[1] ? { kind: "toolkit", slug: toolkitMatch[1] } : null; }; diff --git a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts index b9aa7679af..b0354288ab 100644 --- a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts +++ b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.test.ts @@ -158,10 +158,13 @@ type HarnessSession = { identity: McpApprovalOwner, response: ResumeResponse, ) => Promise; - validateMcpSessionOwner: (identity: { - readonly accountId: string; - readonly organizationId: string; - }, resource: McpResource) => Promise<"ok" | "not_found" | "forbidden" | "terminated">; + validateMcpSessionOwner: ( + identity: { + readonly accountId: string; + readonly organizationId: string; + }, + resource: McpResource, + ) => Promise<"ok" | "not_found" | "forbidden" | "terminated">; }; class StaleCloseTransport implements Transport { @@ -550,9 +553,7 @@ describe("McpAgentSessionDOBase transport restore", () => { const session = await makeHarnessSession(); const identity = { accountId: "user-1", organizationId: "org-1" }; - await expect( - session.validateMcpSessionOwner(identity, defaultMcpResource), - ).resolves.toBe("ok"); + await expect(session.validateMcpSessionOwner(identity, defaultMcpResource)).resolves.toBe("ok"); await expect( session.validateMcpSessionOwner(identity, { kind: "toolkit", @@ -581,14 +582,20 @@ describe("McpAgentSessionDOBase transport restore", () => { await session.alarm(); - const first = session.validateMcpSessionOwner({ - accountId: "user-1", - organizationId: "org-1", - }, defaultMcpResource); - const second = session.validateMcpSessionOwner({ - accountId: "user-1", - organizationId: "org-1", - }, defaultMcpResource); + const first = session.validateMcpSessionOwner( + { + accountId: "user-1", + organizationId: "org-1", + }, + defaultMcpResource, + ); + const second = session.validateMcpSessionOwner( + { + accountId: "user-1", + organizationId: "org-1", + }, + defaultMcpResource, + ); await firstRestoreEntered.promise; await Promise.resolve(); @@ -617,10 +624,13 @@ describe("McpAgentSessionDOBase transport restore", () => { await session.alarm(); - const restore = session.validateMcpSessionOwner({ - accountId: "user-1", - organizationId: "org-1", - }, defaultMcpResource); + const restore = session.validateMcpSessionOwner( + { + accountId: "user-1", + organizationId: "org-1", + }, + defaultMcpResource, + ); const sdkStart = session.onStart(); await firstStartEntered.promise; diff --git a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts index f3f36198db..3055b060af 100644 --- a/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts +++ b/packages/hosts/cloudflare/src/mcp/agent-session-durable-object.ts @@ -18,11 +18,7 @@ import { type PausedExecutionHooks, type ResumeFallbackOutcome, } from "@executor-js/host-mcp/tool-server"; -import { - defaultMcpResource, - mcpResourceKey, - type McpResource, -} from "@executor-js/host-mcp"; +import { defaultMcpResource, mcpResourceKey, type McpResource } from "@executor-js/host-mcp"; import type { IncomingPropagationHeaders, McpElicitationMode } from "./do-headers"; import { classifyDurableObjectError, type DurableObjectFailure } from "./durable-object-errors"; @@ -1569,8 +1565,7 @@ export abstract class McpAgentSessionDOBase< const ownerMatches = identity.accountId === sessionMeta.userId && identity.organizationId === sessionMeta.organizationId; - const resourceMatches = - mcpResourceKey(resource) === mcpResourceKey(sessionMeta.resource); + const resourceMatches = mcpResourceKey(resource) === mcpResourceKey(sessionMeta.resource); return ownerMatches && resourceMatches ? ("ok" as const) : ("forbidden" as const); }).pipe( Effect.withSpan("McpSessionDO.validateMcpSessionOwner"),