The deployed cloud Worker has no filesystem, so loadMcpAppsShellHtml's fs.readFile always failed and the ui://executor/shell.html resource silently served a 'Shell not built' placeholder — an inert document that never starts the MCP-Apps handshake, so every artifact widget in every MCP client spun on its loading state forever with no error anywhere. - Workers hosts (cloud, host-cloudflare) now fetch the built shell through the ASSETS binding: mcpAppsShellAsset emits a stable-named copy alongside the content-hashed one, and makeAssetsShellHtmlLoader (new @executor-js/mcp-apps-shell/worker entry) reads and verifies it. Under vite dev the document travels inline via a dev-only virtual module, since no assets are emitted yet. - The shell document carries an identity marker the loaders verify, so an SPA not-found fallback (index.html with a 200) can never be served as the shell. - The self-host image reads the same emitted asset from its SPA dist; the runtime image has no packages/ tree for the old candidates. - The silent placeholder is gone: a host that cannot produce the shell fails the resource read with an actionable error. - App builds now fail when the shell asset is missing or wrong (apps/cloud scripts/build.mjs, assert-shell-asset.mjs on both hosts).
Showing the changes to apps/cloud/scripts (1 of 23 files). Show all
Unified · Split
| @@ -9,7 +9,7 @@ · 8 unmodified lines | |||
| 9 | 9 | ||
| 10 | import { spawnSync } from "node:child_process"; | 10 | import { spawnSync } from "node:child_process"; |
| 11 | import { randomBytes } from "node:crypto"; | 11 | import { randomBytes } from "node:crypto"; |
| 12 | import { rmSync } from "node:fs"; | 12 | import { existsSync, readFileSync, rmSync } from "node:fs"; |
| 13 | 13 | ||
| 14 | if (!process.env.VITE_PUBLIC_ANALYTICS_PATH) { | 14 | if (!process.env.VITE_PUBLIC_ANALYTICS_PATH) { |
| 15 | process.env.VITE_PUBLIC_ANALYTICS_PATH = randomBytes(4).toString("hex"); | 15 | process.env.VITE_PUBLIC_ANALYTICS_PATH = randomBytes(4).toString("hex"); |
| @@ -32,3 +32,22 @@ for (const step of steps) { · 16 unmodified lines | |||
| 32 | process.exit(result.status ?? 1); | 32 | process.exit(result.status ?? 1); |
| 33 | } | 33 | } |
| 34 | } | 34 | } |
| 35 | |||
| 36 | // The deployed Worker serves the MCP-Apps shell (`ui://executor/shell.html`) | ||
| 37 | // by fetching this stable-named asset through the ASSETS binding — it has no | ||
| 38 | // filesystem to read the shell from. A build that failed to emit it would | ||
| 39 | // deploy a Worker whose artifact resource reads all fail, so make the miss a | ||
| 40 | // build failure here instead of a production incident. Same shape as the | ||
| 41 | // worker-bundler asset assertion from #1378. | ||
| 42 | const shellAsset = new URL( | ||
| 43 | "../dist/client/assets/executor-mcp-apps-shell-stable.html", | ||
| 44 | import.meta.url, | ||
| 45 | ); | ||
| 46 | if (!existsSync(shellAsset)) { | ||
| 47 | console.error(`[build] missing MCP-Apps shell asset at ${shellAsset.pathname}`); | ||
| 48 | process.exit(1); | ||
| 49 | } | ||
| 50 | if (!readFileSync(shellAsset, "utf8").includes('name="executor-mcp-apps-shell"')) { | ||
| 51 | console.error(`[build] ${shellAsset.pathname} is not the MCP-Apps shell document`); | ||
| 52 | process.exit(1); | ||
| 53 | } | ||