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 | 10 | import { spawnSync } from "node:child_process"; |
| 11 | 11 | import { randomBytes } from "node:crypto"; |
| 12 | −import { rmSync } from "node:fs"; | |
| 12 | +import { existsSync, readFileSync, rmSync } from "node:fs"; | |
| 13 | 13 | |
| 14 | 14 | if (!process.env.VITE_PUBLIC_ANALYTICS_PATH) { |
| 15 | 15 | process.env.VITE_PUBLIC_ANALYTICS_PATH = randomBytes(4).toString("hex"); |
| @@ -32,3 +32,22 @@ for (const step of steps) { · 16 unmodified lines | ||
| 32 | 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 | +} | |