import { describe, expect, it } from "@effect/vitest"; import { Cause, Effect } from "effect"; 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 type { ExecutionEngine, ExecutionResult, ResumeResponse } from "@executor-js/execution"; import { McpAgentSessionDOBase, type McpApprovalOwner, type McpSessionModelResumeResult, type SessionMeta, } from "./agent-session-durable-object"; class MemoryStorage { private readonly data = new Map(); alarm: number | undefined; readonly sql = { exec: () => [], }; async get(key: string): Promise { return this.data.get(key) as T | undefined; } async put(key: string, value: unknown): Promise { this.data.set(key, value); } async setAlarm(time: number | Date): Promise { this.alarm = typeof time === "number" ? time : time.getTime(); } async deleteAlarm(): Promise { this.alarm = undefined; } async delete(key: string | readonly string[]): Promise { if (typeof key === "string") { this.data.delete(key); return; } for (const entry of key) { this.data.delete(entry); } } async deleteAll(): Promise { this.data.clear(); } async list( options: { readonly prefix?: string; readonly limit?: number } = {}, ): Promise> { const rows = new Map(); for (const [key, value] of this.data) { if (options.prefix && !key.startsWith(options.prefix)) continue; rows.set(key, value as T); if (options.limit && rows.size >= options.limit) break; } return rows; } async blockConcurrencyWhile(callback: () => T | Promise): Promise { return callback(); } get id(): { readonly name: string } { return { name: "streamable-http:session-reconnect" }; } get storage(): MemoryStorage { return this; } waitUntil(_promise: Promise): void {} } type HarnessSession = { alarm: () => Promise; ctx: MemoryStorage; dbHandle: { readonly end: () => void } | null; engine: ExecutionEngine | null; getConnections?: () => Iterable; getSessionId: () => string; initialized: boolean; lastActivityMs: number; maxPausedSessionIdleMs: () => number; onStart: () => Promise; pendingApprovalLeases: Map; props: Record; runMcpAgentOnStart: () => Promise; server?: McpServer; sessionMeta: SessionMeta; sessionTimeoutMs: () => number; resumeExecutionForModel: ( executionId: string, identity: McpApprovalOwner, response: ResumeResponse, ) => Promise; validateMcpSessionOwner: (identity: { readonly accountId: string; readonly organizationId: string; }) => Promise<"ok" | "not_found" | "forbidden" | "terminated">; }; class StaleCloseTransport implements Transport { onclose?: () => void; onerror?: (error: Error) => void; onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void; async start(): Promise {} async close(): Promise {} async send(_message: JSONRPCMessage): Promise {} } class RestoredTransport implements Transport { onclose?: () => void; onerror?: (error: Error) => void; onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void; async start(): Promise {} async close(): Promise { this.onclose?.(); } async send(_message: JSONRPCMessage): Promise {} } const makeServer = () => new McpServer({ name: "executor-test", version: "1.0.0" }); const makeDeferred = (): { readonly promise: Promise; readonly resolve: () => void } => { let resolve: () => void = () => undefined; const promise = new Promise((settle) => { resolve = settle; }); return { promise, resolve }; }; type ResumeCall = { readonly executionId: string; readonly response: ResumeResponse; }; const completed = (result: unknown): ExecutionResult => ({ status: "completed", result: { result }, }); const makeEngine = ( resultForResume: (executionId: string, response: ResumeResponse) => ExecutionResult | null = () => completed("resume-result"), ): { readonly calls: ResumeCall[]; readonly engine: ExecutionEngine } => { const calls: ResumeCall[] = []; return { calls, engine: { execute: () => Effect.succeed({ result: "execute-result" }), executeWithPause: () => Effect.succeed(completed("execute-result")), resume: (executionId, response) => Effect.sync(() => { calls.push({ executionId, response }); return resultForResume(executionId, response); }), getPausedExecution: () => Effect.succeed(null), pausedExecutionCount: () => Effect.succeed(0), hasPausedExecutions: () => Effect.succeed(false), getDescription: Effect.succeed("test engine"), }, }; }; const approval = { action: "accept", content: { approved: true }, } satisfies ResumeResponse; const makeHarnessSession = async (): Promise => { const sessionId = "session-reconnect"; const sessionMeta: SessionMeta = { organizationId: "org-1", organizationName: "Org 1", userId: "user-1", resource: defaultMcpResource, }; const storage = new MemoryStorage(); const server = makeServer(); await server.connect(new StaleCloseTransport()); const session = Object.create(McpAgentSessionDOBase.prototype) as HarnessSession; session.ctx = storage; session.dbHandle = { end: () => undefined }; session.engine = makeEngine().engine; session.getSessionId = () => sessionId; session.initialized = true; session.lastActivityMs = Date.now() - 10; session.maxPausedSessionIdleMs = () => 1_000; session.pendingApprovalLeases = new Map(); session.props = {}; session.server = server; session.sessionMeta = sessionMeta; session.sessionTimeoutMs = () => 1; session.runMcpAgentOnStart = async () => { const restored = session.server ?? makeServer(); session.server = restored; await restored.connect(new RestoredTransport()); session.engine = makeEngine().engine; session.initialized = true; }; return session; }; // The negotiated MCP-Apps capability arrives once, at `initialize`, and lives // in the rebuilt server's memory. These pin the storage round-trip that lets a // cold-restored session rebuild with it instead of silently downgrading every // artifact to a deep link. describe("McpAgentSessionDOBase apps capability persistence", () => { type CapabilitySession = HarnessSession & { persistAppsEnabled: (appsEnabled: boolean) => Effect.Effect; loadSessionMeta: () => Effect.Effect; resolveSessionMeta: (token: unknown) => Effect.Effect; resolveAndStoreSessionMeta: (token: unknown) => Effect.Effect; }; const baseMeta: SessionMeta = { organizationId: "org-1", organizationName: "Org 1", userId: "user-1", resource: defaultMcpResource, }; const makeCapabilitySession = async ( stored: SessionMeta = baseMeta, ): Promise<{ session: CapabilitySession; storage: MemoryStorage }> => { const storage = new MemoryStorage(); await storage.put("session-meta", stored); const session = Object.create(McpAgentSessionDOBase.prototype) as CapabilitySession; session.ctx = storage; session.getSessionId = () => "session-caps"; return { session, storage }; }; it("persists the negotiated capability so a later restore can read it back", async () => { const { session, storage } = await makeCapabilitySession(); await Effect.runPromise(session.persistAppsEnabled(true)); expect(await storage.get("session-meta")).toMatchObject({ organizationId: "org-1", appsEnabled: true, }); }); it("records a client that loses apps support just as durably", async () => { const { session, storage } = await makeCapabilitySession({ ...baseMeta, appsEnabled: true }); await Effect.runPromise(session.persistAppsEnabled(false)); expect(await storage.get("session-meta")).toMatchObject({ appsEnabled: false }); }); // `init` runs again on every cold restore and rebuilds meta from the bearer // token, which carries no capabilities. If that overwrite won, restoring the // session would erase the very bit meant to survive it. it("carries the stored capability through the re-resolve on cold restore", async () => { const { session, storage } = await makeCapabilitySession({ ...baseMeta, appsEnabled: true }); // What the token resolves to: no `appsEnabled` anywhere in sight. session.resolveSessionMeta = () => Effect.succeed(baseMeta); const resolved = await Effect.runPromise( session.resolveAndStoreSessionMeta({ organizationId: "org-1", userId: "user-1" }), ); expect(resolved.appsEnabled).toBe(true); expect(await storage.get("session-meta")).toMatchObject({ appsEnabled: true }); }); it("leaves a session with no negotiated capability untouched", async () => { const { session, storage } = await makeCapabilitySession(); session.resolveSessionMeta = () => Effect.succeed(baseMeta); const resolved = await Effect.runPromise( session.resolveAndStoreSessionMeta({ organizationId: "org-1", userId: "user-1" }), ); expect(resolved.appsEnabled).toBeUndefined(); expect(await storage.get("session-meta")).not.toHaveProperty("appsEnabled"); }); // Persistence is best-effort observation of a capability, never a reason to // fail the session that was merely trying to render something. it("stays silent when there is no stored meta to merge into", async () => { const storage = new MemoryStorage(); const session = Object.create(McpAgentSessionDOBase.prototype) as CapabilitySession; session.ctx = storage; session.getSessionId = () => "session-caps"; await expect(Effect.runPromise(session.persistAppsEnabled(true))).resolves.toBeUndefined(); expect(await storage.get("session-meta")).toBeUndefined(); }); }); describe("McpAgentSessionDOBase transport restore", () => { it("preserves hibernated response streams when a cold isolate starts", async () => { const session = await makeHarnessSession(); let closeCalls = 0; session.initialized = false; session.engine = null; session.dbHandle = null; delete session.server; session.getConnections = () => [ { close: () => { closeCalls += 1; }, }, ]; session.runMcpAgentOnStart = async () => { session.server = makeServer(); session.engine = makeEngine().engine; session.initialized = true; }; await session.onStart(); expect(closeCalls).toBe(0); expect(session.initialized).toBe(true); }); it("closes response streams when an in-memory runtime restarts", async () => { const session = await makeHarnessSession(); let closeCalls = 0; session.getConnections = () => [ { close: () => { closeCalls += 1; }, }, ]; session.runMcpAgentOnStart = async () => { session.server = makeServer(); session.engine = makeEngine().engine; session.initialized = true; }; await session.onStart(); expect(closeCalls).toBe(1); expect(session.initialized).toBe(true); }); it("restores a same-session request after idle disposal leaves a stale server transport", async () => { const session = await makeHarnessSession(); await session.alarm(); await expect( session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1" }), ).resolves.toBe("ok"); }); it("single-flights concurrent same-session restore after idle disposal", async () => { const session = await makeHarnessSession(); const firstRestoreEntered = makeDeferred(); const finishRestore = makeDeferred(); let onStartCalls = 0; let restoredServer: McpServer | undefined; session.runMcpAgentOnStart = async () => { onStartCalls += 1; const restored = session.server ?? makeServer(); restoredServer ??= restored; session.server = restored; firstRestoreEntered.resolve(); await finishRestore.promise; await restored.connect(new RestoredTransport()); session.initialized = true; }; await session.alarm(); const first = session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1", }); const second = session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1", }); await firstRestoreEntered.promise; await Promise.resolve(); finishRestore.resolve(); await expect(Promise.all([first, second])).resolves.toEqual(["ok", "ok"]); expect(onStartCalls).toBe(1); expect(session.server).toBe(restoredServer); }); it("single-flights SDK onStart callers with same-session restore", async () => { const session = await makeHarnessSession(); const firstStartEntered = makeDeferred(); const finishStart = makeDeferred(); let onStartCalls = 0; session.runMcpAgentOnStart = async () => { onStartCalls += 1; const restored = session.server ?? makeServer(); session.server = restored; firstStartEntered.resolve(); await finishStart.promise; await restored.connect(new RestoredTransport()); session.initialized = true; }; await session.alarm(); const restore = session.validateMcpSessionOwner({ accountId: "user-1", organizationId: "org-1", }); const sdkStart = session.onStart(); await firstStartEntered.promise; await Promise.resolve(); finishStart.resolve(); await expect(Promise.all([restore, sdkStart])).resolves.toEqual(["ok", undefined]); expect(onStartCalls).toBe(1); }); it("single-flights model resume restore with SDK onStart", async () => { const session = await makeHarnessSession(); const firstStartEntered = makeDeferred(); const finishStart = makeDeferred(); const restoredEngine = makeEngine(() => completed("model-result")); let onStartCalls = 0; session.runMcpAgentOnStart = async () => { onStartCalls += 1; const restored = session.server ?? makeServer(); session.server = restored; firstStartEntered.resolve(); await finishStart.promise; await restored.connect(new RestoredTransport()); session.engine = restoredEngine.engine; session.initialized = true; }; await session.alarm(); const resume = session.resumeExecutionForModel( "exec-model", { accountId: "user-1", organizationId: "org-1" }, approval, ); const sdkStart = session.onStart(); await firstStartEntered.promise; await Promise.resolve(); finishStart.resolve(); const [resumeResult] = await Promise.all([resume, sdkStart]); expect(resumeResult).toMatchObject({ status: "result", result: { structuredContent: { status: "completed", result: "model-result", }, }, }); expect(onStartCalls).toBe(1); expect(restoredEngine.calls).toEqual([{ executionId: "exec-model", response: approval }]); }); });