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 = [];