Skip to content

fix(utils,auth,protocol): a browser Web Worker gets the browser variants; Node variants go to workerd - #1038

Merged
alexmercerpo merged 5 commits into
fix/g03-plugin-browser-storesfrom
fix/g01-web-worker-conditions
Nov 3, 2026
Merged

alexmercerpo merged 5 commits into
fix/g03-plugin-browser-storesfrom
fix/g01-web-worker-conditions

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1037

What and why

Bug fix. Fixes #1031.

webpack's target: 'webworker' resolves packages with the worker and browser conditions. @frontmcp/utils mapped worker to its Node variants, both in its imports (#crypto-provider, #async-context, #event-emitter, #env, #runtime-context, #path) and, since #1030, in its root export, where it selects the single-file ESM bundle. @frontmcp/auth's root export did the same, and so did @frontmcp/protocol's #mcp-streamable-http, #mcp-server and #server-types (which imports http). So a browser Web Worker got node:crypto, AsyncLocalStorage, node:events, node:path and process.env. On the base, webpack's target: 'webworker' build of create() + one tool stops on 13 unresolved Node built-ins. With those left empty, the worker throws while it loads.

What each runtime passes

I checked each runtime in its bundler's source and built with it where I could.

Bundler or runtime Conditions it adds Before After
webpack 5.105, target: 'webworker' worker, browser Node variants browser variants
webpack, worker entry (new Worker(new URL(...))) in a target: 'web' build worker only for the entry request. The worker's imports resolve like the page's. browser browser
Parcel (@parcel/node-resolver-core 3.7), web and service workers worker, browser Node browser
esbuild, platform: 'browser', conditions: ['worker'] worker, browser Node browser
Rollup 4.67 + node-resolve 16, browser: true, exportConditions: ['worker'] worker, browser Node browser
Rollup + node-resolve, browser: true browser browser browser
Rollup + node-resolve without browser: true none (default, module, import) Node, as a Node build Node (unchanged)
Vite 8, page or worker module, browser browser browser
wrangler 4.100 (and every release since 2.9, so all of 3.x), including Pages Functions workerd, worker, browser Node Node (same files)
@cloudflare/vite-plugin 1.67 workerd, worker, module, browser Node Node
@cloudflare/vitest-pool-workers 0.23 workerd, worker, module, browser Node Node
OpenNext for Cloudflare 1.20 workerd (esbuild, platform: 'node') Node Node
Vercel Edge: Next.js 16.4 (webpack and Turbopack), @vercel/node 29 edge-light, browser browser browser
Netlify Edge (@netlify/edge-bundler 16.2) node (esbuild, platform: 'node') Node Node
Fastly Compute (@fastly/js-compute 4.0) fastly, browser browser browser
Deno, Bun, Node, AWS Lambda deno / bun / node Node Node

webpack, wrangler, Vite and esbuild were built with from the repo's node_modules. Rollup was built with from a scratch install. For Parcel, Next.js, Turbopack, @vercel/node, Netlify, Fastly, OpenNext, @cloudflare/vite-plugin, @cloudflare/vitest-pool-workers and wrangler 2.x and 3.0, I read the published source. Miniflare runs what it is given and doesn't bundle.

No frontmcp build target passes worker. --target cloudflare leaves bundling to wrangler; vercel and lambda bundle with rspack target: 'node'; browser uses esbuild with browser; the SEA and mcpb builds use esbuild platform: 'node'.

Two notes in the issue don't hold for these versions:

  • Vercel Edge passes edge-light and browser, not worker. It got the browser variants before this change and still does.
  • In a target: 'web' build, webpack's worker entry applies worker to the entry request only. Its imports resolve like the page's, so only target: 'webworker' (and Parcel) hit the bug.

Rollup without browser: true builds for Node, so the worker key never reached it. There's nothing to fix in the packages for that case: the catalog now tells Rollup users to pass browser: true.

What changes

The Node variants are keyed on workerd, the condition only Cloudflare's bundlers pass, instead of worker:

  • @frontmcp/utils: the six imports and the root export.
  • @frontmcp/auth: the root export.
  • @frontmcp/protocol: #mcp-streamable-http, #mcp-server and #server-types.

The worker keys that already point at the browser stubs or at the shared file stay, because they're right for any worker: protocol's #stdio-client, #stdio-server and #client, and the SDK's #sse-transport, #express-host and #protocol-client. The SDK's root export has no worker key. A browser Web Worker now resolves every split the way a page does.

Cloudflare keeps exactly the files it resolved before. I ran wrangler deploy --dry-run --metafile (wrangler 4.100) on a Worker serving createFetchHandler + one tool, built from the packages before and after the change. Both runs bundle the same 1,652 inputs into a byte-identical worker.js (SHA-1 99ff8beaf8365db6921abc7bf82f26ba4d0b2461). The same dry run on the frontmcp build --target cloudflare output of demo-e2e-cloudflare's fixture also bundles the same 2,424 inputs into a byte-identical index.js. nx test demo-e2e-cloudflare, which runs frontmcp build --target cloudflare and then wrangler dev on workerd, passes.

A Worker bundled by hand with worker and browser but without workerd now gets the browser variants. Those variants read no environment variables and have no Web-standard MCP transport. The skipped managed-edge-autoupdate e2e bundled that way, and it now passes wrangler's three conditions. The Cloudflare reference tells custom bundlers to pass workerd.

Not in this PR

On the base and on this branch, a webpack 5 build of the SDK for a page (target: 'web') stops on 6 node: URIs that webpack can't read without configuration:

  • node:cluster, node:net and node:path from server/security/unix-socket-guard;
  • node:http and node:stream from shared/unix-socket-fetch;
  • node:os from shared/shared-files.

The first three come from a static chain, index → provider/create-container → duty/duty.lifecycle (#1014) → server/security/unix-socket-guard. The other three come through the import('../shared/index.js') in FrontMcpInstance.runShared (#1019), which webpack follows too. The per-module browser build keeps these node: imports. Vite replaces them with an empty module and the bundle-size e2e marks them external, but webpack 5 refuses the node: scheme. That build failure isn't specific to Web Workers. After this change a target: 'webworker' build fails on the same 6 URIs, down from 13. The e2e's webpack build leaves Node built-ins empty, as a webpack app has to today. This deserves its own issue.

Bundle size

nx run-many -t build -p utils auth protocol sdk plugin-webmcp --skip-nx-cache, then nx test demo-e2e-bundle-size:

Base (#1030) This PR Budget
create() + one tool, esbuild, minified 1,294,971 1,294,971 1,295,507 (unchanged)
create() + WebMcpPlugin + one tool 1,302,155 1,302,155 1,302,379 (unchanged)
Vite initial chunk, create() + one tool 761,771 761,771 761,903 (unchanged)

A page resolves browser, which this change doesn't touch, so the page bundles don't move.

User-facing change

  • What changed: @frontmcp/utils, @frontmcp/auth and @frontmcp/protocol give their Node variants to the workerd condition instead of worker. A browser Web Worker now gets the browser variants, as a page does. This covers webpack's target: 'webworker', Parcel's workers, and esbuild or Rollup given worker alongside browser. Cloudflare Workers built by wrangler or @cloudflare/vite-plugin resolve the same files as before.
  • Before → after:
    • Under webpack target: 'webworker', create() failed with process is not defined or an unresolved Node built-in. It now starts and serves tools.
    • A Worker bundled by hand with worker + browser and no workerd used to get the Node variants and now gets the browser ones. It has to pass workerd, as wrangler does.
  • frontmcp.dev pages affected (if known): the browser deployment page (Web Workers), the Cloudflare Workers deployment page (custom bundlers and the workerd condition).
  • Skills catalog entries updated:
    • frontmcp-deployment/references/build-for-browser.md: Web Workers get the browser builds, Rollup needs browser: true, esbuild needs platform: 'browser', plus a troubleshooting row.
    • frontmcp-deployment/references/deploy-to-cloudflare.md: wrangler's conditions, custom bundlers must pass workerd, plus a troubleshooting row.

Tests

  • apps/e2e/demo-e2e-bundle-size/e2e/browser-web-worker.e2e.spec.ts (new). It bundles create() + one tool as a browser Web Worker twice: with webpack target: 'webworker', and with esbuild platform: 'browser' + conditions: ['worker']. Each bundle is checked for the Node variants of utils, auth and protocol, then run in a worker-like context with no process, Buffer, global or setImmediate, where the worker posts its tool result.
    • On the base, the webpack bundle holds 11 Node variants and throws at load. The esbuild bundle throws AsyncLocalStorage is not a constructor.
    • With the fix, both pass.
    • The helper's Node built-in stub now matches names with digits, such as node:http2.
  • scripts/worker-export-conditions.test.mjs. For every imports and exports entry of every package under libs/ and plugins/, it checks that a browser Web Worker resolves the file a page resolves, for import and require, with and without module. It also resolves every split and the utils and auth root exports with the condition sets of Cloudflare (workerd, worker, browser), a browser Web Worker (worker, browser), Vercel Edge (edge-light, browser), a browser and Node.
    • Cloudflare keeps the Node variants and the ESM bundle. Every other set gets what a browser or Node got before.
    • On the base, 5 of 9 failed (every browser Web Worker case). Now 9 pass.
  • scripts/strip-dist-from-pkg.test.mjs. Its subpath-import fixture maps the Node variant with workerd, as the packages now do.
  • Results:
    • nx test demo-e2e-bundle-size: 10 suites, 44 tests passed, with the sizes above.
    • nx test demo-e2e-cloudflare: 2 suites, 16 tests passed. One suite is skipped as on the base.
    • node --test scripts/*.test.mjs: 88 passed.
    • node scripts/check-worker-isolate-safety.mjs: clean.
  • Unit tests: nx test utils 2,117 passed, auth 2,855, protocol 9, skills 249.
  • Lint and format:
    • nx run-many -t lint -p utils auth protocol skills: no errors.
    • tsc --noEmit -p apps/e2e/demo-e2e-bundle-size/tsconfig.e2e.json: no errors.
    • Prettier clean.
  • By hand: the wrangler dry run above, webpack target: 'web' with and without a worker entry, and Rollup with browser: true, without it, and with browser: true + exportConditions: ['worker'], each before and after the change.

Summary by CodeRabbit

  • Bug Fixes

    • Improved package resolution for browser Web Workers and Cloudflare Workers, helping each environment use compatible builds.
    • Added coverage for bundling and running Web Worker builds with webpack and esbuild.
  • Documentation

    • Updated browser-build and Cloudflare deployment guidance with resolver settings and troubleshooting for custom bundles.

webpack's target: 'webworker', Parcel's workers, and esbuild or Rollup
given the `worker` condition resolve `worker` and `browser`. @frontmcp/utils
mapped `worker` to its Node variants in its imports (#crypto-provider,
#async-context, #event-emitter, #env, #runtime-context, #path) and its root
export, @frontmcp/auth in its root export, and @frontmcp/protocol in
#mcp-streamable-http, #mcp-server and #server-types, so the SDK failed in a
browser Web Worker with `process is not defined` or a missing Node built-in.

The Node variants now take the `workerd` condition, which wrangler and
@cloudflare/vite-plugin pass with `worker` and `browser`, so a Cloudflare
Worker resolves the same files as before (a wrangler dry run bundles the
same 1,652 inputs into a byte-identical worker.js) and a browser Web Worker
gets the browser variants, as a page does. The `worker` keys that already
pointed at stubs stay.

Fixes #1031
…e files it gives a page

The guard listed the splits by hand, so it missed the SDK's #protocol-client and would not catch a new split
that maps `worker` to a Node file. It now resolves every imports and exports entry of every workspace package
with and without `worker`, for import and require.
… up to 1.10

@frontmcp/auth's root export had no `worker` key on release/1.10.x; it came with the browser builds.
@frontegg-david
frontegg-david marked this pull request as ready for review November 3, 2026 10:15
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

Package export conditions now use workerd for Node variants, while browser Web Workers resolve browser variants. The changes update Cloudflare resolution checks and deployment guidance, and add webpack and esbuild end-to-end coverage for bundling and running a Web Worker fixture.

Changes: Worker Runtime Conditions and Browser Coverage

Layer / File(s) Summary
Separate Workerd and browser conditions
libs/auth/package.json, libs/protocol/package.json, libs/utils/package.json, apps/e2e/demo-e2e-cloudflare/e2e/managed-edge-autoupdate.e2e.spec.ts, scripts/strip-dist-from-pkg.js, scripts/strip-dist-from-pkg.test.mjs, libs/skills/catalog/frontmcp-deployment/references/build-for-browser.md, libs/skills/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md
Package mappings use workerd for Node variants. The Cloudflare test configuration, package-rewrite test, and deployment guidance reflect the updated condition.
Check runtime condition resolution
scripts/worker-export-conditions.test.mjs
The tests check expected implementations across Cloudflare, browser Web Workers, Vercel Edge, browsers, and Node. They also compare workspace package resolution between browser Web Workers and pages.
Bundle and run the Web Worker fixture
apps/e2e/demo-e2e-bundle-size/e2e/browser-web-worker.e2e.spec.ts, apps/e2e/demo-e2e-bundle-size/e2e/helpers/browser-bundle.ts, apps/e2e/demo-e2e-bundle-size/e2e/helpers/run-in-web-worker.mjs, apps/e2e/demo-e2e-bundle-size/fixture/web-worker-create-tool.mjs
Webpack and esbuild bundle the fixture for a Web Worker. The test checks bundle inputs and runs the bundle to verify the tool result.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: alexmercerpo

Merge Risk: Minimal · up to 7fac0

Browser workers receive browser mappings while Cloudflare retains its existing mappings. No merge-blocking risk was established.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title names the three packages (utils, auth, protocol) and states the main behavior change: a browser Web Worker gets browser variants, and Node variants move to the workerd condition. The changes…
Description check Passed The description follows the repository template. It includes 'What and why' with the bug, the issue link (#1031), and the cause. It includes a 'User-facing change' section with what changed, before/af…
Linked Issues check Passed Issue #1031 requires browser Web Workers to resolve browser variants and Cloudflare Workers to retain their current variants. The package maps in @frontmcp/utils, @frontmcp/auth, and `@frontmcp/pr…
Out of Scope Changes check Passed The changes remain within issue #1031. Package-map updates implement the condition fix. The export-condition tests, Web Worker end-to-end fixture, bundler helpers, Cloudflare condition update, documen…
Docstring Coverage Passed Docstring coverage is 66.67% which is sufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
Full details: Title check

Explanation

The title names the three packages (utils, auth, protocol) and states the main behavior change: a browser Web Worker gets browser variants, and Node variants move to the workerd condition. The changeset matches this. The title is long (101 characters) and uses a semicolon-joined clause, but it is specific and on-topic.

Full details: Description check

Explanation

The description follows the repository template. It includes 'What and why' with the bug, the issue link (#1031), and the cause. It includes a 'User-facing change' section with what changed, before/after, affected frontmcp.dev pages, and skills catalog entries. It includes a detailed 'Tests' section with the tests run and manual checks. The template's placeholder comments are gone, and the content fills each required section.

Full details: Linked Issues check

Explanation

Issue #1031 requires browser Web Workers to resolve browser variants and Cloudflare Workers to retain their current variants. The package maps in @frontmcp/utils, @frontmcp/auth, and @frontmcp/protocol move only the Node mappings from worker to workerd. The new condition tests check browser-worker, browser, Node, Vercel Edge, and Cloudflare resolution. The new webpack and esbuild end-to-end test runs create() and a tool without Node globals. These changes satisfy the coding requirements for #1031.

Full details: Out of Scope Changes check

Explanation

The changes remain within issue #1031. Package-map updates implement the condition fix. The export-condition tests, Web Worker end-to-end fixture, bundler helpers, Cloudflare condition update, documentation, and strip-dist fixture update verify or document the same runtime behavior. No unrelated implementation change is demonstrated.

Full details: Docstring Coverage

Explanation

Docstring coverage is 66.67% which is sufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 3 functions across 8 files. (5 skipped: 5 unsupported.)

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 39fc03d and 7fac0d6.

Files selected for processing: 13
  • apps/e2e/demo-e2e-bundle-size/e2e/browser-web-worker.e2e.spec.ts
  • apps/e2e/demo-e2e-bundle-size/e2e/helpers/browser-bundle.ts
  • apps/e2e/demo-e2e-bundle-size/e2e/helpers/run-in-web-worker.mjs
  • apps/e2e/demo-e2e-bundle-size/fixture/web-worker-create-tool.mjs
  • apps/e2e/demo-e2e-cloudflare/e2e/managed-edge-autoupdate.e2e.spec.ts
  • libs/auth/package.json
  • libs/protocol/package.json
  • libs/skills/catalog/frontmcp-deployment/references/build-for-browser.md
  • libs/skills/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md
  • libs/utils/package.json
  • scripts/strip-dist-from-pkg.js
  • scripts/strip-dist-from-pkg.test.mjs
  • scripts/worker-export-conditions.test.mjs

Included review availability: This review used your included allowance. Your plan provides up to 5 included reviews per hour; 0 remain after this review.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-03T10:29:57.963Z
Commit: 988608d6

@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

@frontmcp/utils resolves its Node variants in a webpack Web Worker, so the SDK fails to start there

2 participants