Skip to content

Serve the MCP-Apps shell from static assets on Workers hosts - #1502

Merged
RhysSullivan merged 1 commit into
mainfrom
fix-shell-not-built-fallback
Jul 30, 2026
Merged

Serve the MCP-Apps shell from static assets on Workers hosts#1502
RhysSullivan merged 1 commit into
mainfrom
fix-shell-not-built-fallback

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

Problem

The cloud MCP host served a Shell not built placeholder as the ui://executor/shell.html resource: loadMcpAppsShellHtml reads the built shell with fs.readFile, which can never succeed on a deployed Worker, and the loader silently fell back to an inert placeholder (and cached it for the isolate's lifetime). The placeholder never starts the MCP-Apps handshake, so every artifact widget in every remote MCP client hung on its loading state with no error anywhere in the chain. Confirmed against production with a direct resources/read.

The self-host image had the same failure by a different path: its runtime bundle carries no packages/ tree, so every read candidate missed there too.

Fix

  • mcpAppsShellAsset now emits a second, stable-named copy of the built shell (/assets/executor-mcp-apps-shell-stable.html) alongside the content-hashed one.
  • New @executor-js/mcp-apps-shell/worker entry: makeAssetsShellHtmlLoader fetches that asset through the ASSETS binding (same pattern as the worker-bundler artifact, Serve the worker-bundler artifact from static assets #1378). cloud and host-cloudflare DOs use it; host-cloudflare gains the ASSETS binding in wrangler.jsonc. Under vite dev the document travels inline through a dev-only virtual module, built on first use.
  • The shell template carries an identity <meta> marker that loaders verify, so an SPA not-found fallback (index.html with a 200) can never be served as the shell.
  • The self-host server reads the same emitted asset from its SPA dist.
  • The silent fallback is gone. A host that cannot produce the shell fails the resource read with an error naming the missing file and the fix, instead of hanging every client.
  • Builds block on the asset: apps/cloud/scripts/build.mjs and assert-shell-asset.mjs on both self-host targets fail the build if the stable asset is missing or is not the shell document; the shell build itself fails if the marker is lost.

Verification

  • packages/hosts/mcp-apps-shell full suite (82 tests, incl. the Chromium browser suite) green; new unit tests for the assets loader (404, SPA-fallback 200, dev thunk, caching).
  • packages/hosts/mcp suite green (167 tests).
  • host-cloudflare suite green.
  • Production builds of cloud, host-cloudflare, and host-selfhost all emit the stable asset and pass the new assertions; the cloud server bundle resolves the dev-html virtual module to undefined as intended.
  • e2e scenarios/artifacts.test.ts against the cloud target: 4/4 passed.

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).
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 30, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
executor-cloud 1e50ff3 Jul 30 2026, 01:39 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 30, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 1e50ff3 Commit Preview URL

Branch Preview URL
Jul 30 2026, 01:38 AM

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@pkg-pr-new

pkg-pr-new Bot commented Jul 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1502

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1502

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1502

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1502

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1502

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1502

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1502

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1502

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1502

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1502

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1502

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1502

executor

npm i https://pkg.pr.new/executor@1502

commit: 1e50ff3

@RhysSullivan
RhysSullivan marked this pull request as ready for review July 30, 2026 01:57
@RhysSullivan
RhysSullivan merged commit 25270b1 into main Jul 30, 2026
20 of 21 checks passed
RhysSullivan added a commit that referenced this pull request Jul 30, 2026
* Deliver MCP responses larger than the DO storage value cap

The DO transport persisted every outbound message for reconnect replay
BEFORE writing the live SSE frame, and storage.put of a value over the
128 KiB DO cap throws — so an oversize response (the ~5MB
ui://executor/shell.html resource) was neither stored nor delivered and
the client hung on keepalives forever. Found live in prod after #1502
made the shell resource actually serve its bytes.

- storeEvent skips persistence for messages over a 120 KiB guard,
  logging mcp_event_store_skipped_oversize and returning undefined; the
  caller delivers the live frame without a replay id.
- sendOnStream treats any storeEvent failure as best-effort: log, then
  still write the live SSE frame.
- New e2e (cloud/mcp-oversize-response.test.ts) drives resources/read of
  the shell over real workerd and fails on a hang.
- Event-store unit tests updated for the skip contract.

* Fix e2e typecheck: Effect.die instead of the unavailable dieMessage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant