From ac2cc78d4a4aa9508acb65ad5e9dab319dd1962a Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 11 Oct 2026 11:42:58 -0700 Subject: [PATCH] fix(client): tolerate blocked cookies in MCP app sandboxes (#17027) Co-authored-by: Julius Marminge --- apps/server/src/http.test.ts | 3 +- apps/server/src/http.ts | 4 +- packages/shared/src/mcpApp.test.ts | 59 ++++++++++++++++++++++++++++++ packages/shared/src/mcpApp.ts | 16 ++++++++ 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/apps/server/src/http.test.ts b/apps/server/src/http.test.ts index 0ee1ca9c186f8bfc23e0160547203f25f1aca9f9..9d3686a46692b59fbb80841b685b828ed08fb9bf 100644 --- a/apps/server/src/http.test.ts +++ b/apps/server/src/http.test.ts @@ -18,6 +18,7 @@ import { openMediaFile } from "./assets/MediaFile.ts"; import { injectMcpAppCsp, + injectMcpAppCookieBootstrap, injectMcpAppThemeBootstrap, MCP_APP_MAX_HTML_BYTES, } from "@t3tools/shared/mcpApp"; @@ -58,7 +59,7 @@ describe("MCP app asset responses", () => { method, ), ); - const expected = injectMcpAppThemeBootstrap(html); + const expected = injectMcpAppCookieBootstrap(injectMcpAppThemeBootstrap(html)); expect(response.status).toBe(200); expect(response.headers.get("content-length")).toBe( String(new TextEncoder().encode(expected).byteLength), diff --git a/apps/server/src/http.ts b/apps/server/src/http.ts index 1e9c3d714d1cea54e9ca8e59526ed7ae0390b58c..13f54ef352998efc9c9dda091c1304cb120f3b0b 100644 --- a/apps/server/src/http.ts +++ b/apps/server/src/http.ts @@ -5,7 +5,7 @@ import { EnvironmentHttpApi, } from "@t3tools/contracts"; import { isDevProxiedPath } from "@t3tools/shared/devProxy"; -import { injectMcpAppThemeBootstrap } from "@t3tools/shared/mcpApp"; +import { injectMcpAppCookieBootstrap, injectMcpAppThemeBootstrap } from "@t3tools/shared/mcpApp"; import { decodeOtlpTraceRecords } from "@t3tools/shared/observability"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; @@ -185,7 +185,7 @@ export const assetFileResponse = Effect.fn("assetFileResponse")(function* ( return HttpServerResponse.text("MCP app is too large to preview.", { status: 413 }); } const body = new TextEncoder().encode( - injectMcpAppThemeBootstrap(new TextDecoder().decode(bytes)), + injectMcpAppCookieBootstrap(injectMcpAppThemeBootstrap(new TextDecoder().decode(bytes))), ); headers["Content-Length"] = String(body.byteLength); return method === "HEAD" diff --git a/packages/shared/src/mcpApp.test.ts b/packages/shared/src/mcpApp.test.ts index bca145a4fba88dc4ee9daac8fa7377ff3fa68cc5..7dd50690adf389986f44330de3dc1dc797e47162 100644 --- a/packages/shared/src/mcpApp.test.ts +++ b/packages/shared/src/mcpApp.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it } from "vite-plus/test"; import { injectMcpAppCsp, injectMcpAppThemeBootstrap, + injectMcpAppCookieBootstrap, mcpAppContentSecurityPolicy, mcpAppToolCallableByApp, readMcpAppCsp, @@ -209,3 +210,61 @@ describe("mcpAppFromToolItem", () => { expect(output?.t3McpApp?.server).toBe("weather"); }); }); + +describe("MCP App cookie bootstrap", () => { + const vendor = ``; + const execute = (html: string, document: object) => { + for (const script of html.matchAll(/`; + // Saved captures begin with the CSP inserted by injectMcpAppCsp. Keep it + // ahead of executable code, including this bootstrap, without rewriting it. + const prefix = + /^(?:\uFEFF?\s*]*>\s*)?(?:)?/i.exec( + html, + )![0]; + return `${prefix}${bootstrap}${html.slice(prefix.length)}`; +} + /** The iframe `allow` attribute for the permissions an app declared. */ export function mcpAppAllowAttribute(permissions: McpAppPermissions | undefined): string { const features: Array = []; -- 2.54.0