You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dedicated Rsbuild audit, deeper than the combined survey in #566. Findings already filed there are not restated: #566 §1 P3 (MCP Apps overrideBrowserslist), §5 (Workbench filenameHash/cache headers, .mapfiles entry, browserslist) and its "Already compliant" list are out of scope here and referenced by section where a new finding builds on them.
Audited:main @ d88cc10 (fix: Node 26 transform flag; … (#554)), in a detached worktree, pnpm install --frozen-lockfile && pnpm build, Node v22.23.2, pnpm 11.23.0. Nothing was committed; the worktree was removed.
Versions (installed → latest):@rsbuild/core 2.2.1 → 2.2.3 (bump already implied by #566 §1 P1, not re-filed), @rsbuild/plugin-react 2.1.0 → 2.1.0, @rspack/core 2.2.1 (and 2.1.10 via @rslib/core 0.23.2, per #566) → 2.2.2, @rspress/core 2.0.21 → 2.0.21, rsbuild-plugin-rsc 0.1.1 → 0.1.1. @rsdoctor/rspack-plugin 1.6.3 was installed only as audit scratch in the worktree; main does not depend on it. Full table in §6.
What was actually built and measured (all scratch output under /tmp):
examples/mcp-app view through the real composeMcpAppsRsbuildConfig/compileMcpApps (same code path as agent-bundle build), plus Rsdoctor (brief JSON) and Rspack stats composition; the CLI itself against the example and against a deliberately broken copy.
Two throwaway fixtures through compileMcpApps: a .ts entry importing a .tsx component, and the same entry as .tsx; seven tools.rsbuild hatch variants.
Workbench production build through createWorkbenchConfig (3 cold runs, 3 warm runs with performance.buildCache, 3 rsbuild build CLI runs), dev-server first compile (3 runs), Rsdoctor brief JSON on the production build.
Docsite: two full rspress build runs (cold, then warm with Rspress's default performance.buildCache populated) through an untracked wrapper config that dumps the resolved Rsbuild and Rspack config.
Skills applied: rsbuild-best-practices, rsbuild-v2-upgrade, rspack-best-practices, rspack-split-chunks, rspack-debugging, rspack-tracing, rsdoctor-analysis; option semantics verified against the installed @rsbuild/core@2.2.1 typings (dist/types/config.d.ts) and source (dist/626.js), not memory.
This is what every plugin author's app ships through, so it is ordered first.
P1 · packages/agent-bundle/src/build/mcp-apps.ts:49,189 · rsbuild-best-practices: register @rsbuild/plugin-react for the build, not per file — the automatic JSX runtime is a project-level SWC setting (swcReactOptions.runtime) · usesReactSyntax decides from the entry extension alone. A .ts entry that imports a .tsx component (the natural layout: views/status.ts + views/StatusPanel.tsx) gets no React plugin, so Rsbuild's bare SWC transform lowers the component's JSX to the classic factory. Verified with a fixture through the real compileMcpApps: the emitted HTML contains return React.createElement("strong",{className:"w"},t) with a free React identifier, the build succeeds with no warning, and the app throws ReferenceError: React is not defined the moment the host renders it; the identical fixture with a .tsx entry compiles to (0,e.jsx)(...) via react/jsx-runtime. Same trap for .js → .jsx. rslib.ts:623-628 already documents this exact failure for Rslib surfaces and applies pluginReact({ fastRefresh: false }) unconditionally. · Apply pluginReact() to every app environment unconditionally (it is inert for JSX-free code: splitChunks is already false, fast refresh is dev-only), or keep the switch but decide on the module graph (stats.modules containing \.[jt]sx$) and fail with a diagnostic when JSX was compiled without the plugin. Add a unit test: .ts entry importing a .tsx module, assert !/[^.\w]React\.createElement/.test(html). · Risk: none; both fixture outputs are ~190 kB either way.
P1 · packages/agent-bundle/src/build/mcp-apps.ts:196 (logLevel: 'silent'), :281-298 · rspack-debugging: read stats.errors/stats.warnings and surface them with file and line; rsbuild-best-practices: never swallow compiler diagnostics · Rsbuild 2.2.1's build() (@rsbuild/core/dist/626.js, RSPACK_BUILD_ERROR) rejects with a bare Error('Rspack build failed.') when buildState.hasErrors; the per-file errors exist only in the logger output, which 'silent' mutes. Nothing on this path reads stats.errors or stats.warnings (collectBundledOutputEvidence requests neither; there is no ignoreWarnings, so warnings are not filtered — they are simply never looked at). Verified with the real CLI on a copy of examples/mcp-app: a syntax error in views/status-panel.ts, an unresolved import, and a tsconfig.json whose extends target is missing all print exactly [{"code":"AB5000","message":"Rspack build failed.","severity":"error"}] — no file, line, or message. With logLevel: 'error' the same build prints × Tsconfig not found /tmp/tsconfig.json / × [html-rspack-plugin]: Child compilation failed. Every Rslib surface uses logLevel: 'error' (cli-bins.ts:201, entries.ts:517, package-build.ts:327, rslib.ts:759), so MCP Apps is the only compile path in the product that is fully mute, and it is the one plugin authors iterate on most. · Keep 'silent' (one structured channel) but add a profile plugin: api.onAfterEnvironmentCompile(({ environment, stats }) => …) fires with stats even when stats.hasErrors(); collect stats.toJson({ all: false, errors: true, warnings: true, moduleTrace: true }), and throw a DiagnosticError with a dedicated code (a new AB47xx, "MCP App view failed to compile", registered in docs/diagnostics.md so the hosts/diagnostics pages regenerate) whose message lists app · file:line · message per error; emit warnings as severity: 'warning' diagnostics. · Risk: low — the Workbench and CLI already render diagnostics; the only behaviour change is a specific code instead of the AB5000 catch-all.
P2 · packages/agent-bundle/src/build/mcp-apps.ts:196 + default performance.printFileSize · rsdoctor-analysis: know the composition before shipping; rsbuild-best-practices: watch output size · Because logging is silent, Rsbuild's file-size table never prints and the CLI summary is only Built <name> to <dir>; the manifest carries sizes but nothing shows them. Measured: the 3.2 kB examples/mcp-app view compiles to a 437 kB HTML (104 kB gzip). Composition (Rspack stats, pre-minify): zod 1.42 MB across 190 modules (v3 types.js 128 kB and v4 core — @modelcontextprotocol/sdk@1.30's zod-compat pulls both), @modelcontextprotocol/sdk 263 kB (types.js 72 kB, shared/protocol.js 51 kB), zod-to-json-schema 98 kB, @modelcontextprotocol/ext-apps 65 kB, author code 3.2 kB. This floor is inherent to @modelcontextprotocol/ext-apps@1.7.5 (peer @modelcontextprotocol/sdk ^1.29, zod) — no Rsbuild knob removes it — but authors are never told, and a "why is my 2 kB view 440 kB" report is predictable. · Print per-app inlined size and gzip size in the build summary (the evidence pass already touches every emitted asset), and state the expected floor in website/docs/en/guide/authoring/mcp.mdx (+ zh) with the reason. · Risk: none.
P2 · packages/agent-bundle/src/build/mcp-apps.ts:190-193 · Rsbuild html config docs (html.title default 'Rsbuild App', html.mountId default 'root', built-in template in @rsbuild/core/dist/626.js) · When an app declares no template, the emitted document is Rsbuild's built-in shell. Verified output: <!DOCTYPE html><html><head><title>Rsbuild App</title><meta charset="utf-8"><meta name="viewport" …></head><body><div id="root"></div><script defer>…. Every template-less MCP App therefore ships into hosts titled "Rsbuild App", with no lang on <html>, and mounts at #root — a convention documented nowhere in website/docs/en/guide/authoring/mcp.mdx, so an author who renders into #app gets a blank view with a green build. · Set html.title to the app name (or meta.name), html.mountId explicitly, html.tags/template lang, and document the mount id and title on the authoring page (+ zh); alternatively ship a framework default template. · Risk: none.
P2 · packages/agent-bundle/src/build/mcp-apps.ts:216-219 (enforceInvariants alias agent-bundle/meta$) · Rsbuild resolve.aliasStrategy docs (default 'prefer-tsconfig': tsconfig.jsonpaths win over resolve.alias) · The framework's identity module is delivered by an alias to a virtual module, but the profile leaves aliasStrategy at its default, so the author's tsconfig can shadow it. Verified: a fixture tsconfig.json with paths: { "agent-bundle/meta": ["./stub-meta.ts"] } builds green and the HTML contains the stub's string instead of the framework identity — the app reports a wrong name/version inside the host with no diagnostic. paths entries for agent-bundle/* are a plausible editor-resolution workaround for authors. · Pin resolve.aliasStrategy: 'prefer-alias' in enforceInvariants (and assert it in assertResolvedViewConfig), or fail validation when the author's paths cover agent-bundle/meta. The same check applies to the Rslib surfaces' framework aliases (rslib.ts), out of scope here. · Risk: none for compliant projects.
P3 · packages/agent-bundle/src/build/mcp-apps.ts:189 + @rsbuild/plugin-react/dist/index.js:75-128 · Rsbuild resolve.dedupe docs; rspack-split-chunks: with splitChunks: false the plugin's react cache group never applies · pluginReact() does not set resolve.dedupe, and the profile does not either, so a plugin author whose tree has a nested react/react-dom (monorepo, ext-apps peer skew) can ship two React copies inside one self-contained HTML — "Invalid hook call" at runtime. Rsdoctor on examples/mcp-app shows one copy, so this is latent, not observed. · When the React plugin is applied, set resolve.dedupe: ['react', 'react-dom', 'scheduler'] on that environment. · Risk: none.
P3 · packages/agent-bundle/src/build/mcp-apps.ts:190-193 · Rsbuild html.templateParameters docs (default exposes compilation, rspackConfig, htmlPlugin, assetPrefix to <%= %>) · Author templates are handed the full html-rspack-plugin parameter set; nothing documents which parameters are stable, and a template that interpolates compilation or rspackConfig can dump absolute paths into the shipped HTML. · Pass a minimal templateParameters allow-list (e.g. app name, meta) and document it. · Risk: rare; sharp when hit.
P3 · packages/agent-bundle/src/build/mcp-apps.ts:91-93 · rspack-debugging: an assertion that fails must name the artefact that failed it · assertSelfContainedViews throws "Rsbuild emitted files beyond the stable self-contained MCP App HTML output." without listing the files. Verified: tools.rsbuild = { output: { sourceMap: { js: 'source-map' } } } and tools.rsbuild = { mode: 'development' } both hit it (the extra file is the .map), while { js: 'inline-source-map' } passes and grows the HTML to 1.35 MB. · Include the unexpected relative paths in the message and a hint that only inline source maps stay self-contained. · Risk: none.
P3 · packages/agent-bundle/src/build/mcp-apps.ts:51-83 · rsbuild-best-practices: pin production invariants and verify them on the resolved config (inspectConfig) · assertResolvedViewConfig pins cleanDistPath, filenameHash, inlineScripts, inlineStyles, dataUriLimit, splitChunks, asyncChunks and output.path, but not mode, output.minify or performance.buildCache. The profile's mode: 'production' (#566 "compliant") is therefore a default, not an invariant: tools.rsbuild = { mode: 'development' } only fails today as a side effect of the .map file (previous finding); with an inline source map it would ship process.env.NODE_ENV === 'development' React and the react-refresh runtime into hosts, because pluginReact() is called bare (:189) while rslib.ts:628 and the framework-plugins.ts:16-18 comment say fastRefresh: false. performance.buildCache is likewise unpinned (Rslib pins it false at rslib.ts:618-622 for concurrent-build safety; several agent-bundle build processes can share one project's node_modules/.cache). · Assert mode === 'production' and performance.buildCache === false in the same loop, pass pluginReact({ fastRefresh: false }), and comment beside the assertion which of minify/sourceMap the hatch may change. · Risk: none.
2. MCP Apps dev loop in the Workbench
P1 · packages/agent-bundle/src/dev/artifacts/artifact-service.ts:80-85 → dev/coordinator.ts:491,578 → packages/workbench/src/overview-model.ts:100-103 · rspack-debugging: the first thing a failing build must give you is the compiler's own error; rsbuild-best-practices "Debugging" · This is the dev-loop face of §1 P1. When a view fails to compile under agent-bundle dev, the coordinator wraps the bare Error('Rspack build failed.') as AB7100 · "Unable to compile the build: Rspack build failed.", which is what the Overview Diagnostics table and the build.failed Dev Log entry show; the previous good epoch stays pinned (correct), but the author has no file, line or message anywhere in the Workbench. Rslib surfaces are no better inside the Workbench: their logLevel: 'error' output goes to the agent-bundle process stderr, which dev-log-producers.ts:154-163 does not capture (only MCP session mcp.stderr is). AB7100 is emitted at artifact-service.ts:81,144 but is not listed in docs/diagnostics.md, so the generated diagnostics reference page does not document the code authors actually see. · Once §1 P1 attaches the Rspack errors to a structured diagnostic, propagate it unchanged through artifact-service.ts:75-79 (it already forwards DiagnosticError.diagnostics) so the Overview shows app · file:line · message; add AB7100 to docs/diagnostics.md. · Risk: none.
P2 · packages/agent-bundle/src/build/mcp-apps.ts:196-207 + website/docs/en/guide/authoring/mcp.mdx, website/docs/en/guide/development/workbench.mdx:179-180 · rsbuild-best-practices "CLI": rsbuild (dev mode, source maps, readable output) for local development, rsbuild build for production · agent-bundle dev compiles MCP App views through the identical production profile (mode: 'production', minified, output.sourceMap: false, silent) — verified path coordinator.ts:540 → artifact-service.ts:208 → build.ts:402 → mcp-apps.ts:268. No Rsbuild dev server or createDevServer exists anywhere under packages/agent-bundle/src (only the Workbench's own in workbench-server.ts:546), which is a deliberate design (workbench.mdx:179-180), but it means the preview sandbox shows minified code with no source maps and the only escape hatch — tools.rsbuild = { output: { sourceMap: { js: 'inline-source-map' } } }, verified working, HTML 437 kB → 1.35 MB — is documented nowhere; mode: 'development' and external maps fail opaquely (§1 P3). · Either add a dev-only overlay in compileMcpApps (inline source maps + minify: false when mode === 'development' is passed from the coordinator — the self-contained assertion still holds for inline maps), or document the working hatch and DEBUG=rsbuild in mcp.mdx + zh and in the Workbench MCP Apps page. · Risk: inline maps triple the preview HTML (dev only); external .map files must stay forbidden for resource packaging.
P3 · packages/agent-bundle/src/dev/mcp-apps/mcp-app-preview-service.ts:322, dev/mcp-session/mcp-session.ts:138-146 · rsbuild-best-practices: dev loop = edit → rebuild → the view refreshes · After a successful rebuild artifact.available publishes a new epoch, but an open MCP session stays pinned to its epoch and the preview HTML was loaded once via bridge.loadResource(); nothing re-loads the resource into the sandbox iframe. The author must close and re-open Open App preview (or start a new session) to see the rebuilt view — the loop is edit → rebuild → notice nothing changed → re-open. The rebuild trigger itself is fine (ProjectWatcher watches the whole project root, watcher.ts:50-54, so imported components and templates invalidate, not only the entry). · Offer an opt-in "reload preview on new epoch" (the Workbench already receives artifact.available over SSE) or state the re-open step next to the preview button and in workbench.mdx. · Risk: an automatic swap must stay opt-in so epoch evidence guarantees hold.
P1 · packages/workbench/src/mcp/mcp-route-client.ts:592-595, packages/agent-bundle/src/dev/foreground-server.ts:746, 804-808, 821-827 vs packages/workbench/rsbuild.config.ts:40 and website/docs/en/guide/development/workbench.mdx:170-178 · rsbuild-best-practices "CLI: use rsbuild (dev server) for local development"; Rsbuild server.proxy docs (changeOrigin rewrites Host only, never Origin) · The documented contributor HMR loop — AGENT_BUNDLE_WORKBENCH_API_PROXY=http://127.0.0.1:3100 pnpm --filter agent-bundle-workbench dev, proxying /api — cannot complete a session. The browser sits on the Rsbuild origin (http://localhost:3000); /api/project/session answers origin: this.url (http://127.0.0.1:3100) and the client throws AB8003 "Foreground session bootstrap origin does not match this browser." because location.origin !== body.origin. Independently, every mutation POST carries Origin: http://localhost:3000, which #assertMutationSession rejects with AB8003 (origin !== this.url); changeOrigin: true (Rsbuild default, asserted in tests/rsbuild-workbench.test.ts:43) does not help since http-proxy rewrites Host, not Origin. Read-only GETs and the SSE stream work, so the loop looks alive until the first MCP page or mutation. Verified by reading both sides; not run. · Make the foreground server dev-proxy aware: accept an explicitly configured proxy origin (e.g. AGENT_BUNDLE_WORKBENCH_DEV_ORIGIN) in #assertMutationSession/#assertSessionBootstrapOrigin and echo it in the bootstrap origin, or serve the Rsbuild dev middleware from the foreground origin (rsbuild.createDevServer() + server.middlewareMode). Add a test that drives the proxied path end to end, and fix workbench.mdx (+ zh) once it works. · Risk: relaxing only the client-side check still leaves protected POSTs failing; the allowed origin must be an explicit opt-in, never derived from the request.
P2 · packages/workbench/tests/mcp-app-frame.test.ts:130-151 and 7 siblings (lifecycles-page.browser.test.tsx:47, mcp-json-input.test.ts:47, mcp-app-preview-browser.test.ts:75, mcp-page-app-browser.test.ts:149, evals-real.e2e.test.ts:91, comparisons-page-client-scope-browser.test.ts:66, runtime-consent-dialog.test.ts:58) · rsbuild-best-practices "Configuration: one rsbuild.config.ts/defineConfig, reuse it"; AGENTS.md "look for the helper before writing it" · Eight browser fixtures each re-own createRsbuild + bare pluginReact() + workbenchBrowserAliases instead of createWorkbenchConfig, and they already diverge: distPath.assets vs production static, no mode pin (rsbuild.config.ts:49 pins it), extensionAlias only in mcp-app-frame.test.ts:146. Four more (discovery-atoms-disposal.test.ts:185, runtime-document-atoms-disposal.test.ts:135, route-editor-atoms-disposal.test.ts:133, runtime-inspector.test.ts:85) call createWorkbenchConfig() and mutate it, inheriting the notices output.copy and — if AGENT_BUNDLE_WORKBENCH_API_PROXY happens to be set in the shell — the proxy. · One tests/support/workbench-fixture-config.ts (mode: 'production', pluginReact(), aliases, temp distPath/entry, optional extensionAlias) used by all twelve. · Risk: none beyond a mechanical refactor.
P2 · packages/workbench/rsbuild.config.ts:40 + website/docs/en/guide/development/workbench.mdx:176, website/docs/en/contributing/index.mdx · Rsbuild ServerConfig (config.d.ts:317-390: port 3000, host localhost, strictPort false, open false) · The HMR docs give the env + one command; nothing says which URL opens (Rsbuild picks 3000, or the next free port silently — strictPort false), that only /api is proxied, or that MCP App / runtime iframes come from separate loopback origins the proxy does not cover. contributing/index.mdx has no Workbench text at all. · Document the default URL and the proxy scope once P1 above works; consider server.strictPort: true for the contributor config so a port collision fails loudly instead of moving the UI. · Risk: none.
P3 · packages/agent-bundle/src/dev/workbench-assets.ts:42 + packages/workbench/rsbuild.config.ts:20-21 · Rsbuild output.copy / asset management · contentTypeFor falls back to application/octet-stream for extensionless paths, so the two files output.copy ships — THIRD_PARTY_NOTICES and src/mcp/APP-RENDERER-LICENSE — are served as binary downloads by #serveAsset (foreground-server.ts:953). Nothing in packages/workbench/src links them (they are the packaging contract in packages/agent-bundle/NOTICE:11 and tests/dev-workbench-packaging.test.ts:46), so this is cosmetic. · Map extensionless basenames to text/plain; charset=utf-8. · Risk: none.
4. Docsite builder (website/rspress.config.ts, no builderConfig)
P2 · .github/workflows/docs.yml:48-61 + Rspress initRsbuild.js:197-215 · Rsbuild performance.buildCache docs (persistent cache only pays off when the cache directory survives between builds) · Rspress enables performance.buildCache by default (node_modules/.cache, ≈89 MB after one build here), but the Docs job restores only the pnpm store, so every CI build pays cold compile plus the cache write. Measured locally: cold 196.6 s vs warm 188.8 s (−4 %) — the wall time is TypeDoc + SSG of 1906 pages, not Rspack, so restoring the cache via actions/cache would buy little and cost a ~90 MB artefact per key. · Set RSPRESS_PERSISTENT_CACHE=false in docs.yml (skips the write) unless a measured actions/cache restore shows a real gain; do not cache blindly. · Risk: none (the cache is dead weight in CI today).
No other builderConfig change is justified. With no builderConfig (website/rspress.config.ts:54-194), the resolved web environment is mode: 'production', target: web, assetPrefix: '/agent-bundle/' (from base), filenameHash: true, JS sourceMap: false (0 .map files in doc_build), legalComments: 'linked' (three .LICENSE.txt, ~2 kB), polyfill: 'off', dataUriLimit 4 KiB, printFileSize.compressed, security.sri off (same-origin Pages), no tools.rspack beyond Rspress's own, source.define only Rspress SSR/LLMS flags. Route chunks already get <link rel="preload"> from Rspress's RouteChunkAssetsPlugin; blanket performance.prefetch over ~1900 async chunks would hurt. OG/description meta come from Rspress SSG (rspress.config.ts:57-61), so theme-color etc. belong in Rspress head, not html.meta. website/plugins/*.ts use Rspress config hooks only, no builderConfig/modifyRsbuildConfig (mirror-api-locale.ts:87-114, generated-reference.ts:911-921).
5. Other @rsbuild/core / createRsbuild users
P2 · examples/rsc-agent-runtime/rsbuild.config.ts:203-206 + examples/rsc-agent-runtime/src/dev/rsbuild-runtime-session.ts:563-568 · rspack-debugging: surface stats.errors text; Rsbuild onAfterDevCompile hook docs · Same shape as §1 P1 on the third compile path in the product: on stats.hasErrors() the observer fails the attempt with a fixed Error('RSC runtime compile reported errors.') and the session emits AB8206 "RSC runtime source build failed."; stats.toJson({ errors: true }) is never read, warnings never inspected (hasWarnings unused), logLevel left at 'info' so the real message lands only on the process console. The README advertises a Workbench compile-error capture for this example. · Attach the stats errors (file:line:message) to the AB8206 diagnostic and set logLevel: 'error'. · Risk: none.
P2 · examples/rsc-agent-runtime/rsbuild.config.ts:362-403 vs packages/agent-bundle/src/build/mcp-apps.ts:198-214 · rsbuild-best-practices asset/bundle guidance; rspack-split-chunks: web default preset still extracts lib-react under pluginReact · The app environment claims self-contained HTML with inlineScripts/inlineStyles + dynamicImportMode: 'eager', but unlike composeMcpAppsRsbuildConfig it sets neither splitChunks: false, dataUriLimit: Number.MAX_SAFE_INTEGER, asyncChunks: false nor an assertion like assertResolvedViewConfig; the default 4 KiB dataUriLimit means any asset >4 KiB becomes a sibling file, and legalComments: 'linked' already emits lib-react.js.LICENSE.txt beside the HTML (a host test expects it). · Reuse the mcp-apps invariant set (or its assertion) for app, and legalComments: 'inline' if siblings are unwanted. · Risk: none; today's HTML-only checks pass.
P3 · examples/rsc-agent-runtime/rsbuild.config.ts:288, 299, 368-369 · Rsbuild mode docs; @rsbuild/plugin-reactfastRefresh default true · mode: 'production' is pinned even when options.mode === 'development' (rsbuild-runtime-session.ts:845), so the long-lived agent-bundle dev provider ships production React (no dev warnings) and Fast Refresh can never engage even though pluginReact() is called without { fastRefresh: false } (Rslib and the framework-plugins.ts:16-18 comment say refresh is off). · Either let mode follow options.mode for the web envs, or pass pluginReact({ fastRefresh: false }) and document that "dev" is production-compiled with onAppReload only. · Risk: none.
P3 · examples/rsc-agent-runtime/rsbuild.config.ts:290, 373-387 · Rsbuild dev.writeToDisk, output.cleanDistPath, output.filenameHash docs · Development forces dev.writeToDisk: true with cleanDistPath: false on all envs, but filenameHash: false is applied only in the production app branch — so a long dev session accumulates stale hashed app assets on disk and in staged checkpoints. · Apply filenameHash: false to development app too. · Risk: none.
P3 · examples/rsc-agent-runtime/rsbuild.config.ts:148-149 · Rsbuild output.cleanDistPath docs · emitRuntimeManifest does rm(dirname(environments.rsc.distPath)) — i.e. dist/ — wiping sibling widget/app output to compensate for cleanDistPath: false; apply: 'build' so it never races writeToDisk. · rm each environment's own distPath. · Risk: none.
P3 · examples/rsc-agent-runtime/rsbuild.config.ts:341-360, 362-403 · rsbuild-best-practices "keep browserslist aligned" (output.overrideBrowserslist) · Neither widget nor app pins targets — the same gap #566 §1 P3 files for mcp-apps.ts, on a second surface. · Pin the same list the framework adopts. · Risk: none.
P3 · packages/create-agent-bundle/templates/** · (informational) · Templates ship no Rsbuild config, plugins or scripts; mcp-server/tsconfig.json:15 sets jsx: react-jsx for route .tsx compiled by the package build. No MCP App views exist in templates, so nothing to change until one is added — at which point §1 P1 (.ts entry + .tsx component) is the trap to avoid.
6. Rsbuild 2.x currency
P2 · packages/rsc-runtime/package.json:94 · rsbuild-v2-upgrade step 4: bump every Rsbuild/Rspack package in lockstep (taze major --include /rsbuild/) · The exact devDependency pin "@rspack/core": "2.2.1" mirrors the ^2.2.0-0 peer at :78; nothing under packages/rsc-runtime/ imports @rspack/core. @rsbuild/core@2.2.3 (the #566 §1 P1 target) depends on @rspack/core ~2.2.2, so landing the bump without moving this line splits the tree into @rspack/core 2.2.1 + 2.2.2 (two native @rspack/binding-* downloads) and lets @rspack/plugin-react-refresh@2.0.2 peer-resolve against whichever copy pnpm picks. pnpm why @rspack/core already shows two versions today (2.1.10 via @rslib/core@0.23.2, 2.2.1 via @rsbuild/core@2.2.1 and this pin); #566 collapses the 2.1.x copy only if :94 moves too. · Bump :94 to 2.2.2 in the same PR, or drop the devDependency and rely on the peer. · Risk: none (no import).
P3 · packages/agent-bundle/src/core/types.ts:259 + packages/agent-bundle/src/config/validate.ts:1835-1882 · rsbuild-v2-upgrade "Migrate performance.chunkSplit → splitChunks" · The tools.rsbuild hatch is typed as @rsbuild/core@2.2.1's EnvironmentConfig, which still admits the two @deprecated keys performance.chunkSplit (config.d.ts:584) and output.sourceMap.extract.js (config.d.ts:816); validateTools (AB4720–AB4724) checks shape and plugin collisions only. Because both framework profiles set splitChunks: false (mcp-apps.ts:210, rslib.ts:632), a consumer chunkSplit hits Rsbuild's "Both performance.chunkSplit and splitChunks are set" branch (dist/626.js:9425) and is discarded — via logger.warn, below the 'silent'/'error' levels the framework builds with, so the author sees nothing. · Reject both keys in validateTools with an AB472x diagnostic pointing at the v2 replacement, and mention it on the tools-hatch docs page (en + zh). · Risk: none for compliant configs.
P3 · pnpm-workspace.yaml:24 · pnpm docs "minimumReleaseAge / minimumReleaseAgeExclude" · minimumReleaseAgeExclude: ['@rsbuild/core@2.2.1'] was added in #20 because 2.2.1 was <24 h old at the time; pnpm 11's built-in release-age window has long passed for 2.2.1, and both @rsbuild/core@2.2.3 and @rslib/core@1.0.0 (published 2026-09-03) are already outside it, so the entry neither helps nor blocks the #566 bump — it is dead config that will name a version no longer in the lockfile. · Delete the line in the bump PR, or set minimumReleaseAgeExcludePrune: true (pnpm ≥11.22; repo is on 11.23.0). · Risk: none.
No deprecated Rsbuild 2.x option is in use anywhere. The only @deprecated keys in the installed typings are performance.chunkSplit, output.sourceMap.extract.js and dev.setupMiddlewares; git grep across packages/agent-bundle/src, packages/workbench, examples, rstest.*.ts, website/rspress.config.ts and website/plugins finds none of them nor any v1 leftovers (source.alias, dev.startUrl, tools.htmlPlugin, output.enableLatestDecorators, source.transformImport, dev.progressBar, output.distPath.server, proxy context/onProxy*, onDevCompileDone, htmlWebpackPlugin template params) outside a correct migration comment at rslib.ts:629. Every hook and JS-API call in use is current (onAfterEnvironmentCompile, onBeforeDevCompile, onAfterDevCompile, startDevServer({ getPortSilently }), loadEnv, mergeRsbuildConfig). Peer ranges are satisfied by 2.2.1 and by 2.2.3: @rsbuild/plugin-react@2.1.0 (^2.0.0, latest), rsbuild-plugin-rsc@0.1.1 (^2.0.0), rsbuild-plugin-publint@1.0.0, @rstest/core@0.11.10 (~2.2.0; latest 0.11.12 wants ~2.2.2), @rspress/core@2.0.21 (^2.1.13). Not re-filed: the two-@rsbuild/core tree (2.1.13 via @rslib/core@0.23.2, 2.2.1 elsewhere) is #566 §1 P1.
MCP Apps — composeToolsLayers order profile → tools.rsbuild → tools.rspack → invariants (compose-layers.ts:32-36); enforceInvariants runs last and pins asyncChunks: false, the meta alias and VirtualModulesPlugin (mcp-apps.ts:212-238); one Rsbuild environment per app (:188-195); server.publicDir: false (:209); filenameHash: false + distPath.html: 'mcp-apps' → stable [name].html, names validated kebab-case (validate.ts:513) and duplicate names must share identity (mcp-apps.ts:136-145); createRsbuild({ cwd: projectRoot }) so a config in a subdirectory still resolves from the project root (mcp-apps.ts:268, build.ts:402, normalize.ts:852-854); result.close() on success (:296-298) and Rsbuild closes the compiler itself before rejecting; defaults output.charset utf8, polyfill: 'off', injectStyles: false, output.module: false; source.include default transpiles TS/JSX in dependencies but not plain JS; source.tsconfigPath auto-set only when <root>/tsconfig.json exists; provenance ignores the virtual module (:289-291, meta.ts:27-31); Rsdoctor: zero duplicate packages in the example app; security.nonce/sri correctly unset — the sandbox CSP allows 'unsafe-inline' (mcp-app-sandbox.ts:22-32) and there are no external srcs to hash; the tools.rsbuild hatch honours removeConsole, overrideBrowserslist and source.define (verified).
MCP Apps dev loop — no Rsbuild dev server is used for app preview, by design (workbench.mdx:179-180); ProjectWatcher watches the project root so entry, imported modules and templates all invalidate (watcher.ts:50-54, coordinator.ts:331-335); a failed rebuild keeps the last good epoch (coordinator.ts:578); collectBundledOutputEvidence replaces planned sourceInputs with the real module graph after each compile (mcp-apps.ts:283-304, provenance.ts:181-248).
Workbench — defineConfig + exported createWorkbenchConfig, proxy only when the env is set (rsbuild.config.ts:13, 40, 54; scripts/dev.mjs:3); changeOrigin: true is the Rsbuild 2.2.1 default and asserted in tests/rsbuild-workbench.test.ts:43; no path rewrite or ws: true needed — every typed client route is under /api/…, transport is fetch + EventSource, no WebSocket; Rsbuild's compress middleware skips text/event-stream, so SSE survives the proxy; non-/api traffic is intentional (hash routing main.tsx:342, blob: downloads, absolute sandbox iframe origins, data: media); hash routing only, #serveAsset maps / → index.html, no SPA fallback needed, HEAD supported (foreground-server.ts:216, 947); #serveAsset sets only content-type, no CSP conflict with favicon="data:," / defer scripts; index.html owns lang, charset, viewport, title, favicon and the app never writes document.title; pluginReact() defaults (fast refresh in dev only, no React Compiler) are right for a SPA; 11 plain .css files, zero .module.css, no PostCSS/Tailwind, nothing relying on Lightning CSS-specific transforms; Rsdoctor: zero duplicate packages (one react, react-dom, effect, zod), 25 chunks; output.copy targets match the packaging contract (NOTICE:11, dev-workbench-packaging.test.ts:46-47); zero console.* in packages/workbench/src, so performance.removeConsole has nothing to do.
Docsite — see §4: every resolved Rsbuild default matches the skills for a same-origin GitHub Pages site; base → assetPrefix/publicPath/agent-bundle/; no .map published; legalComments: 'linked' kept for licence compliance; route preloads come from Rspress, not performance.preload.
rsc-agent-runtime — per-env tools.rspack.name for MultiStats matching (rsbuild.config.ts:318-320, 359, 399-400); splitChunks: false on the node env matches Rsbuild 2.2's server default (multi-entry chunks: 'all', same rationale as rslib.ts:629-632); widget entries all on client-anchor.ts per the rsbuild-plugin-rsc environments contract; app hmr: false/liveReload: false with a custom reload plugin; emitRuntimeManifest is apply: 'build' so it never races writeToDisk; documented hooks only; server.port: 0/host: '127.0.0.1'/printUrls: false + getPortSilently + server.close(); all four integration tests reuse createRscRuntimeRsbuildConfig; create-agent-bundle templates ship no Rsbuild config or deps; services/mcp-run.ts:199-203 uses loadEnv only.
Currency — no deprecated 2.x option in use; all peers satisfied by 2.2.1 and by 2.2.3; @rsbuild/plugin-react, @rspress/*, rsbuild-plugin-rsc, rsbuild-plugin-publint at latest.
Not assessed / caveats
Timings were taken on a shared machine with other agents running; medians of three are reported, single runs are noted. The Workbench "warm" number measures performance.buildCache, which the repo does not enable — it sizes the option, it does not describe today's pnpm build.
The contributor-HMR origin finding (§3 P1) was established by reading both sides (client check at mcp-route-client.ts:592-595, server checks at foreground-server.ts:804-827, browsers always send Origin on POST, http-proxy changeOrigin rewrites Host only); it was not reproduced live.
The .ts → .tsx React finding (§1 P1) and the aliasStrategy finding (§1 P2) were reproduced with throwaway fixtures through the real compileMcpApps; the error-surfacing finding was reproduced with the real CLI against a copy of examples/mcp-app.
Docsite memory: the Docs job sets no NODE_OPTIONS; peak RSS 8.36 GB fits public ubuntu-latest (16 GB) with headroom, but the same build would OOM on a private-repo 8 GB runner. No Rsbuild knob applies; it is SSG/runner sizing.
The audit worktree carried uncommitted scratch (@rsdoctor/rspack-plugin added to the root package.json/lockfile for Rsdoctor runs; an untracked website/rspress.audit.config.ts); main has neither, and nothing was committed or pushed. The worktree has been removed.
Dedicated Rsbuild audit, deeper than the combined survey in #566. Findings already filed there are not restated: #566 §1 P3 (MCP Apps
overrideBrowserslist), §5 (WorkbenchfilenameHash/cache headers,.mapfilesentry, browserslist) and its "Already compliant" list are out of scope here and referenced by section where a new finding builds on them.Audited:
main@ d88cc10 (fix: Node 26 transform flag; … (#554)), in a detached worktree,pnpm install --frozen-lockfile && pnpm build, Node v22.23.2, pnpm 11.23.0. Nothing was committed; the worktree was removed.Versions (installed → latest):
@rsbuild/core2.2.1 → 2.2.3 (bump already implied by #566 §1 P1, not re-filed),@rsbuild/plugin-react2.1.0 → 2.1.0,@rspack/core2.2.1 (and 2.1.10 via@rslib/core0.23.2, per #566) → 2.2.2,@rspress/core2.0.21 → 2.0.21,rsbuild-plugin-rsc0.1.1 → 0.1.1.@rsdoctor/rspack-plugin1.6.3 was installed only as audit scratch in the worktree;maindoes not depend on it. Full table in §6.What was actually built and measured (all scratch output under
/tmp):examples/mcp-appview through the realcomposeMcpAppsRsbuildConfig/compileMcpApps(same code path asagent-bundle build), plus Rsdoctor (brief JSON) and Rspack stats composition; the CLI itself against the example and against a deliberately broken copy.compileMcpApps: a.tsentry importing a.tsxcomponent, and the same entry as.tsx; seventools.rsbuildhatch variants.createWorkbenchConfig(3 cold runs, 3 warm runs withperformance.buildCache, 3rsbuild buildCLI runs), dev-server first compile (3 runs), Rsdoctor brief JSON on the production build.rspress buildruns (cold, then warm with Rspress's defaultperformance.buildCachepopulated) through an untracked wrapper config that dumps the resolved Rsbuild and Rspack config.Skills applied:
rsbuild-best-practices,rsbuild-v2-upgrade,rspack-best-practices,rspack-split-chunks,rspack-debugging,rspack-tracing,rsdoctor-analysis; option semantics verified against the installed@rsbuild/core@2.2.1typings (dist/types/config.d.ts) and source (dist/626.js), not memory.1. MCP Apps compiler path (
packages/agent-bundle/src/build/mcp-apps.ts)This is what every plugin author's app ships through, so it is ordered first.
P1 ·
packages/agent-bundle/src/build/mcp-apps.ts:49,189· rsbuild-best-practices: register@rsbuild/plugin-reactfor the build, not per file — the automatic JSX runtime is a project-level SWC setting (swcReactOptions.runtime) ·usesReactSyntaxdecides from the entry extension alone. A.tsentry that imports a.tsxcomponent (the natural layout:views/status.ts+views/StatusPanel.tsx) gets no React plugin, so Rsbuild's bare SWC transform lowers the component's JSX to the classic factory. Verified with a fixture through the realcompileMcpApps: the emitted HTML containsreturn React.createElement("strong",{className:"w"},t)with a freeReactidentifier, the build succeeds with no warning, and the app throwsReferenceError: React is not definedthe moment the host renders it; the identical fixture with a.tsxentry compiles to(0,e.jsx)(...)viareact/jsx-runtime. Same trap for.js→.jsx.rslib.ts:623-628already documents this exact failure for Rslib surfaces and appliespluginReact({ fastRefresh: false })unconditionally. · ApplypluginReact()to every app environment unconditionally (it is inert for JSX-free code:splitChunksis alreadyfalse, fast refresh is dev-only), or keep the switch but decide on the module graph (stats.modulescontaining\.[jt]sx$) and fail with a diagnostic when JSX was compiled without the plugin. Add a unit test:.tsentry importing a.tsxmodule, assert!/[^.\w]React\.createElement/.test(html). · Risk: none; both fixture outputs are ~190 kB either way.P1 ·
packages/agent-bundle/src/build/mcp-apps.ts:196(logLevel: 'silent'),:281-298· rspack-debugging: readstats.errors/stats.warningsand surface them with file and line; rsbuild-best-practices: never swallow compiler diagnostics · Rsbuild 2.2.1'sbuild()(@rsbuild/core/dist/626.js,RSPACK_BUILD_ERROR) rejects with a bareError('Rspack build failed.')whenbuildState.hasErrors; the per-file errors exist only in the logger output, which'silent'mutes. Nothing on this path readsstats.errorsorstats.warnings(collectBundledOutputEvidencerequests neither; there is noignoreWarnings, so warnings are not filtered — they are simply never looked at). Verified with the real CLI on a copy ofexamples/mcp-app: a syntax error inviews/status-panel.ts, an unresolved import, and atsconfig.jsonwhoseextendstarget is missing all print exactly[{"code":"AB5000","message":"Rspack build failed.","severity":"error"}]— no file, line, or message. WithlogLevel: 'error'the same build prints× Tsconfig not found /tmp/tsconfig.json/× [html-rspack-plugin]: Child compilation failed. Every Rslib surface useslogLevel: 'error'(cli-bins.ts:201,entries.ts:517,package-build.ts:327,rslib.ts:759), so MCP Apps is the only compile path in the product that is fully mute, and it is the one plugin authors iterate on most. · Keep'silent'(one structured channel) but add a profile plugin:api.onAfterEnvironmentCompile(({ environment, stats }) => …)fires with stats even whenstats.hasErrors(); collectstats.toJson({ all: false, errors: true, warnings: true, moduleTrace: true }), and throw aDiagnosticErrorwith a dedicated code (a newAB47xx, "MCP App view failed to compile", registered indocs/diagnostics.mdso the hosts/diagnostics pages regenerate) whose message listsapp · file:line · messageper error; emit warnings asseverity: 'warning'diagnostics. · Risk: low — the Workbench and CLI already render diagnostics; the only behaviour change is a specific code instead of theAB5000catch-all.P2 ·
packages/agent-bundle/src/build/mcp-apps.ts:196+ defaultperformance.printFileSize· rsdoctor-analysis: know the composition before shipping; rsbuild-best-practices: watch output size · Because logging is silent, Rsbuild's file-size table never prints and the CLI summary is onlyBuilt <name> to <dir>; the manifest carries sizes but nothing shows them. Measured: the 3.2 kBexamples/mcp-appview compiles to a 437 kB HTML (104 kB gzip). Composition (Rspack stats, pre-minify):zod1.42 MB across 190 modules (v3types.js128 kB and v4 core —@modelcontextprotocol/sdk@1.30'szod-compatpulls both),@modelcontextprotocol/sdk263 kB (types.js72 kB,shared/protocol.js51 kB),zod-to-json-schema98 kB,@modelcontextprotocol/ext-apps65 kB, author code 3.2 kB. This floor is inherent to@modelcontextprotocol/ext-apps@1.7.5(peer@modelcontextprotocol/sdk ^1.29,zod) — no Rsbuild knob removes it — but authors are never told, and a "why is my 2 kB view 440 kB" report is predictable. · Print per-app inlined size and gzip size in the build summary (the evidence pass already touches every emitted asset), and state the expected floor inwebsite/docs/en/guide/authoring/mcp.mdx(+zh) with the reason. · Risk: none.P2 ·
packages/agent-bundle/src/build/mcp-apps.ts:190-193· Rsbuildhtmlconfig docs (html.titledefault'Rsbuild App',html.mountIddefault'root', built-in template in@rsbuild/core/dist/626.js) · When an app declares notemplate, the emitted document is Rsbuild's built-in shell. Verified output:<!DOCTYPE html><html><head><title>Rsbuild App</title><meta charset="utf-8"><meta name="viewport" …></head><body><div id="root"></div><script defer>…. Every template-less MCP App therefore ships into hosts titled "Rsbuild App", with nolangon<html>, and mounts at#root— a convention documented nowhere inwebsite/docs/en/guide/authoring/mcp.mdx, so an author who renders into#appgets a blank view with a green build. · Sethtml.titleto the app name (ormeta.name),html.mountIdexplicitly,html.tags/templatelang, and document the mount id and title on the authoring page (+zh); alternatively ship a framework default template. · Risk: none.P2 ·
packages/agent-bundle/src/build/mcp-apps.ts:216-219(enforceInvariantsaliasagent-bundle/meta$) · Rsbuildresolve.aliasStrategydocs (default'prefer-tsconfig':tsconfig.jsonpathswin overresolve.alias) · The framework's identity module is delivered by an alias to a virtual module, but the profile leavesaliasStrategyat its default, so the author's tsconfig can shadow it. Verified: a fixturetsconfig.jsonwithpaths: { "agent-bundle/meta": ["./stub-meta.ts"] }builds green and the HTML contains the stub's string instead of the framework identity — the app reports a wrong name/version inside the host with no diagnostic.pathsentries foragent-bundle/*are a plausible editor-resolution workaround for authors. · Pinresolve.aliasStrategy: 'prefer-alias'inenforceInvariants(and assert it inassertResolvedViewConfig), or fail validation when the author'spathscoveragent-bundle/meta. The same check applies to the Rslib surfaces' framework aliases (rslib.ts), out of scope here. · Risk: none for compliant projects.P3 ·
packages/agent-bundle/src/build/mcp-apps.ts:189+@rsbuild/plugin-react/dist/index.js:75-128· Rsbuildresolve.dedupedocs; rspack-split-chunks: withsplitChunks: falsethe plugin'sreactcache group never applies ·pluginReact()does not setresolve.dedupe, and the profile does not either, so a plugin author whose tree has a nestedreact/react-dom(monorepo,ext-appspeer skew) can ship two React copies inside one self-contained HTML — "Invalid hook call" at runtime. Rsdoctor onexamples/mcp-appshows one copy, so this is latent, not observed. · When the React plugin is applied, setresolve.dedupe: ['react', 'react-dom', 'scheduler']on that environment. · Risk: none.P3 ·
packages/agent-bundle/src/build/mcp-apps.ts:190-193· Rsbuildhtml.templateParametersdocs (default exposescompilation,rspackConfig,htmlPlugin,assetPrefixto<%= %>) · Author templates are handed the full html-rspack-plugin parameter set; nothing documents which parameters are stable, and a template that interpolatescompilationorrspackConfigcan dump absolute paths into the shipped HTML. · Pass a minimaltemplateParametersallow-list (e.g. app name,meta) and document it. · Risk: rare; sharp when hit.P3 ·
packages/agent-bundle/src/build/mcp-apps.ts:91-93· rspack-debugging: an assertion that fails must name the artefact that failed it ·assertSelfContainedViewsthrows "Rsbuild emitted files beyond the stable self-contained MCP App HTML output." without listing the files. Verified:tools.rsbuild = { output: { sourceMap: { js: 'source-map' } } }andtools.rsbuild = { mode: 'development' }both hit it (the extra file is the.map), while{ js: 'inline-source-map' }passes and grows the HTML to 1.35 MB. · Include the unexpected relative paths in the message and a hint that only inline source maps stay self-contained. · Risk: none.P3 ·
packages/agent-bundle/src/build/mcp-apps.ts:51-83· rsbuild-best-practices: pin production invariants and verify them on the resolved config (inspectConfig) ·assertResolvedViewConfigpinscleanDistPath,filenameHash,inlineScripts,inlineStyles,dataUriLimit,splitChunks,asyncChunksandoutput.path, but notmode,output.minifyorperformance.buildCache. The profile'smode: 'production'(#566 "compliant") is therefore a default, not an invariant:tools.rsbuild = { mode: 'development' }only fails today as a side effect of the.mapfile (previous finding); with an inline source map it would shipprocess.env.NODE_ENV === 'development'React and the react-refresh runtime into hosts, becausepluginReact()is called bare (:189) whilerslib.ts:628and theframework-plugins.ts:16-18comment sayfastRefresh: false.performance.buildCacheis likewise unpinned (Rslib pins itfalseatrslib.ts:618-622for concurrent-build safety; severalagent-bundle buildprocesses can share one project'snode_modules/.cache). · Assertmode === 'production'andperformance.buildCache === falsein the same loop, passpluginReact({ fastRefresh: false }), and comment beside the assertion which ofminify/sourceMapthe hatch may change. · Risk: none.2. MCP Apps dev loop in the Workbench
P1 ·
packages/agent-bundle/src/dev/artifacts/artifact-service.ts:80-85→dev/coordinator.ts:491,578→packages/workbench/src/overview-model.ts:100-103· rspack-debugging: the first thing a failing build must give you is the compiler's own error; rsbuild-best-practices "Debugging" · This is the dev-loop face of §1 P1. When a view fails to compile underagent-bundle dev, the coordinator wraps the bareError('Rspack build failed.')asAB7100· "Unable to compile the build: Rspack build failed.", which is what the Overview Diagnostics table and thebuild.failedDev Log entry show; the previous good epoch stays pinned (correct), but the author has no file, line or message anywhere in the Workbench. Rslib surfaces are no better inside the Workbench: theirlogLevel: 'error'output goes to theagent-bundleprocess stderr, whichdev-log-producers.ts:154-163does not capture (only MCP sessionmcp.stderris).AB7100is emitted atartifact-service.ts:81,144but is not listed indocs/diagnostics.md, so the generated diagnostics reference page does not document the code authors actually see. · Once §1 P1 attaches the Rspack errors to a structured diagnostic, propagate it unchanged throughartifact-service.ts:75-79(it already forwardsDiagnosticError.diagnostics) so the Overview showsapp · file:line · message; addAB7100todocs/diagnostics.md. · Risk: none.P2 ·
packages/agent-bundle/src/build/mcp-apps.ts:196-207+website/docs/en/guide/authoring/mcp.mdx,website/docs/en/guide/development/workbench.mdx:179-180· rsbuild-best-practices "CLI":rsbuild(dev mode, source maps, readable output) for local development,rsbuild buildfor production ·agent-bundle devcompiles MCP App views through the identical production profile (mode: 'production', minified,output.sourceMap: false, silent) — verified pathcoordinator.ts:540 → artifact-service.ts:208 → build.ts:402 → mcp-apps.ts:268. No Rsbuild dev server orcreateDevServerexists anywhere underpackages/agent-bundle/src(only the Workbench's own inworkbench-server.ts:546), which is a deliberate design (workbench.mdx:179-180), but it means the preview sandbox shows minified code with no source maps and the only escape hatch —tools.rsbuild = { output: { sourceMap: { js: 'inline-source-map' } } }, verified working, HTML 437 kB → 1.35 MB — is documented nowhere;mode: 'development'and external maps fail opaquely (§1 P3). · Either add a dev-only overlay incompileMcpApps(inline source maps +minify: falsewhenmode === 'development'is passed from the coordinator — the self-contained assertion still holds for inline maps), or document the working hatch andDEBUG=rsbuildinmcp.mdx+zhand in the Workbench MCP Apps page. · Risk: inline maps triple the preview HTML (dev only); external.mapfiles must stay forbidden for resource packaging.P3 ·
packages/agent-bundle/src/dev/mcp-apps/mcp-app-preview-service.ts:322,dev/mcp-session/mcp-session.ts:138-146· rsbuild-best-practices: dev loop = edit → rebuild → the view refreshes · After a successful rebuildartifact.availablepublishes a new epoch, but an open MCP session stays pinned to its epoch and the preview HTML was loaded once viabridge.loadResource(); nothing re-loads the resource into the sandbox iframe. The author must close and re-open Open App preview (or start a new session) to see the rebuilt view — the loop is edit → rebuild → notice nothing changed → re-open. The rebuild trigger itself is fine (ProjectWatcherwatches the whole project root,watcher.ts:50-54, so imported components and templates invalidate, not only the entry). · Offer an opt-in "reload preview on new epoch" (the Workbench already receivesartifact.availableover SSE) or state the re-open step next to the preview button and inworkbench.mdx. · Risk: an automatic swap must stay opt-in so epoch evidence guarantees hold.3. Workbench (
packages/workbench/rsbuild.config.ts)P1 ·
packages/workbench/src/mcp/mcp-route-client.ts:592-595,packages/agent-bundle/src/dev/foreground-server.ts:746, 804-808, 821-827vspackages/workbench/rsbuild.config.ts:40andwebsite/docs/en/guide/development/workbench.mdx:170-178· rsbuild-best-practices "CLI: usersbuild(dev server) for local development"; Rsbuildserver.proxydocs (changeOriginrewritesHostonly, neverOrigin) · The documented contributor HMR loop —AGENT_BUNDLE_WORKBENCH_API_PROXY=http://127.0.0.1:3100 pnpm --filter agent-bundle-workbench dev, proxying/api— cannot complete a session. The browser sits on the Rsbuild origin (http://localhost:3000);/api/project/sessionanswersorigin: this.url(http://127.0.0.1:3100) and the client throwsAB8003"Foreground session bootstrap origin does not match this browser." becauselocation.origin !== body.origin. Independently, every mutation POST carriesOrigin: http://localhost:3000, which#assertMutationSessionrejects withAB8003(origin !== this.url);changeOrigin: true(Rsbuild default, asserted intests/rsbuild-workbench.test.ts:43) does not help since http-proxy rewritesHost, notOrigin. Read-only GETs and the SSE stream work, so the loop looks alive until the first MCP page or mutation. Verified by reading both sides; not run. · Make the foreground server dev-proxy aware: accept an explicitly configured proxy origin (e.g.AGENT_BUNDLE_WORKBENCH_DEV_ORIGIN) in#assertMutationSession/#assertSessionBootstrapOriginand echo it in the bootstraporigin, or serve the Rsbuild dev middleware from the foreground origin (rsbuild.createDevServer()+server.middlewareMode). Add a test that drives the proxied path end to end, and fixworkbench.mdx(+zh) once it works. · Risk: relaxing only the client-side check still leaves protected POSTs failing; the allowed origin must be an explicit opt-in, never derived from the request.P2 ·
packages/workbench/tests/mcp-app-frame.test.ts:130-151and 7 siblings (lifecycles-page.browser.test.tsx:47,mcp-json-input.test.ts:47,mcp-app-preview-browser.test.ts:75,mcp-page-app-browser.test.ts:149,evals-real.e2e.test.ts:91,comparisons-page-client-scope-browser.test.ts:66,runtime-consent-dialog.test.ts:58) · rsbuild-best-practices "Configuration: onersbuild.config.ts/defineConfig, reuse it"; AGENTS.md "look for the helper before writing it" · Eight browser fixtures each re-owncreateRsbuild+ barepluginReact()+workbenchBrowserAliasesinstead ofcreateWorkbenchConfig, and they already diverge:distPath.assetsvs productionstatic, nomodepin (rsbuild.config.ts:49pins it),extensionAliasonly inmcp-app-frame.test.ts:146. Four more (discovery-atoms-disposal.test.ts:185,runtime-document-atoms-disposal.test.ts:135,route-editor-atoms-disposal.test.ts:133,runtime-inspector.test.ts:85) callcreateWorkbenchConfig()and mutate it, inheriting the noticesoutput.copyand — ifAGENT_BUNDLE_WORKBENCH_API_PROXYhappens to be set in the shell — the proxy. · Onetests/support/workbench-fixture-config.ts(mode: 'production',pluginReact(), aliases, tempdistPath/entry, optionalextensionAlias) used by all twelve. · Risk: none beyond a mechanical refactor.P2 ·
packages/workbench/rsbuild.config.ts:40+website/docs/en/guide/development/workbench.mdx:176,website/docs/en/contributing/index.mdx· RsbuildServerConfig(config.d.ts:317-390:port3000,hostlocalhost,strictPortfalse,openfalse) · The HMR docs give the env + one command; nothing says which URL opens (Rsbuild picks 3000, or the next free port silently —strictPortfalse), that only/apiis proxied, or that MCP App / runtime iframes come from separate loopback origins the proxy does not cover.contributing/index.mdxhas no Workbench text at all. · Document the default URL and the proxy scope once P1 above works; considerserver.strictPort: truefor the contributor config so a port collision fails loudly instead of moving the UI. · Risk: none.P3 ·
packages/agent-bundle/src/dev/workbench-assets.ts:42+packages/workbench/rsbuild.config.ts:20-21· Rsbuildoutput.copy/ asset management ·contentTypeForfalls back toapplication/octet-streamfor extensionless paths, so the two filesoutput.copyships —THIRD_PARTY_NOTICESandsrc/mcp/APP-RENDERER-LICENSE— are served as binary downloads by#serveAsset(foreground-server.ts:953). Nothing inpackages/workbench/srclinks them (they are the packaging contract inpackages/agent-bundle/NOTICE:11andtests/dev-workbench-packaging.test.ts:46), so this is cosmetic. · Map extensionless basenames totext/plain; charset=utf-8. · Risk: none.4. Docsite builder (
website/rspress.config.ts, nobuilderConfig)P2 ·
.github/workflows/docs.yml:48-61+ RspressinitRsbuild.js:197-215· Rsbuildperformance.buildCachedocs (persistent cache only pays off when the cache directory survives between builds) · Rspress enablesperformance.buildCacheby default (node_modules/.cache, ≈89 MB after one build here), but the Docs job restores only the pnpm store, so every CI build pays cold compile plus the cache write. Measured locally: cold 196.6 s vs warm 188.8 s (−4 %) — the wall time is TypeDoc + SSG of 1906 pages, not Rspack, so restoring the cache viaactions/cachewould buy little and cost a ~90 MB artefact per key. · SetRSPRESS_PERSISTENT_CACHE=falseindocs.yml(skips the write) unless a measuredactions/cacherestore shows a real gain; do not cache blindly. · Risk: none (the cache is dead weight in CI today).No other
builderConfigchange is justified. With nobuilderConfig(website/rspress.config.ts:54-194), the resolved web environment ismode: 'production',target: web,assetPrefix: '/agent-bundle/'(frombase),filenameHash: true, JSsourceMap: false(0.mapfiles indoc_build),legalComments: 'linked'(three.LICENSE.txt, ~2 kB),polyfill: 'off',dataUriLimit4 KiB,printFileSize.compressed,security.srioff (same-origin Pages), notools.rspackbeyond Rspress's own,source.defineonly Rspress SSR/LLMS flags. Route chunks already get<link rel="preload">from Rspress'sRouteChunkAssetsPlugin; blanketperformance.prefetchover ~1900 async chunks would hurt. OG/description meta come from Rspress SSG (rspress.config.ts:57-61), sotheme-coloretc. belong in Rspresshead, nothtml.meta.website/plugins/*.tsuse Rspressconfighooks only, nobuilderConfig/modifyRsbuildConfig(mirror-api-locale.ts:87-114,generated-reference.ts:911-921).5. Other
@rsbuild/core/createRsbuildusersP2 ·
examples/rsc-agent-runtime/rsbuild.config.ts:203-206+examples/rsc-agent-runtime/src/dev/rsbuild-runtime-session.ts:563-568· rspack-debugging: surfacestats.errorstext; RsbuildonAfterDevCompilehook docs · Same shape as §1 P1 on the third compile path in the product: onstats.hasErrors()the observer fails the attempt with a fixedError('RSC runtime compile reported errors.')and the session emitsAB8206"RSC runtime source build failed.";stats.toJson({ errors: true })is never read, warnings never inspected (hasWarningsunused),logLevelleft at'info'so the real message lands only on the process console. The README advertises a Workbench compile-error capture for this example. · Attach the stats errors (file:line:message) to theAB8206diagnostic and setlogLevel: 'error'. · Risk: none.P2 ·
examples/rsc-agent-runtime/rsbuild.config.ts:362-403vspackages/agent-bundle/src/build/mcp-apps.ts:198-214· rsbuild-best-practices asset/bundle guidance; rspack-split-chunks: web default preset still extractslib-reactunderpluginReact· Theappenvironment claims self-contained HTML withinlineScripts/inlineStyles+dynamicImportMode: 'eager', but unlikecomposeMcpAppsRsbuildConfigit sets neithersplitChunks: false,dataUriLimit: Number.MAX_SAFE_INTEGER,asyncChunks: falsenor an assertion likeassertResolvedViewConfig; the default 4 KiBdataUriLimitmeans any asset >4 KiB becomes a sibling file, andlegalComments: 'linked'already emitslib-react.js.LICENSE.txtbeside the HTML (a host test expects it). · Reuse the mcp-apps invariant set (or its assertion) forapp, andlegalComments: 'inline'if siblings are unwanted. · Risk: none; today's HTML-only checks pass.P3 ·
examples/rsc-agent-runtime/rsbuild.config.ts:288, 299, 368-369· Rsbuildmodedocs;@rsbuild/plugin-reactfastRefreshdefaulttrue·mode: 'production'is pinned even whenoptions.mode === 'development'(rsbuild-runtime-session.ts:845), so the long-livedagent-bundle devprovider ships production React (no dev warnings) and Fast Refresh can never engage even thoughpluginReact()is called without{ fastRefresh: false }(Rslib and theframework-plugins.ts:16-18comment say refresh is off). · Either letmodefollowoptions.modefor the web envs, or passpluginReact({ fastRefresh: false })and document that "dev" is production-compiled withonAppReloadonly. · Risk: none.P3 ·
examples/rsc-agent-runtime/rsbuild.config.ts:290, 373-387· Rsbuilddev.writeToDisk,output.cleanDistPath,output.filenameHashdocs · Development forcesdev.writeToDisk: truewithcleanDistPath: falseon all envs, butfilenameHash: falseis applied only in the productionappbranch — so a long dev session accumulates stale hashedappassets on disk and in staged checkpoints. · ApplyfilenameHash: falseto developmentapptoo. · Risk: none.P3 ·
examples/rsc-agent-runtime/rsbuild.config.ts:148-149· Rsbuildoutput.cleanDistPathdocs ·emitRuntimeManifestdoesrm(dirname(environments.rsc.distPath))— i.e.dist/— wiping siblingwidget/appoutput to compensate forcleanDistPath: false;apply: 'build'so it never raceswriteToDisk. ·rmeach environment's owndistPath. · Risk: none.P3 ·
examples/rsc-agent-runtime/rsbuild.config.ts:341-360, 362-403· rsbuild-best-practices "keep browserslist aligned" (output.overrideBrowserslist) · Neitherwidgetnorapppins targets — the same gap #566 §1 P3 files formcp-apps.ts, on a second surface. · Pin the same list the framework adopts. · Risk: none.P3 ·
packages/create-agent-bundle/templates/**· (informational) · Templates ship no Rsbuild config, plugins or scripts;mcp-server/tsconfig.json:15setsjsx: react-jsxfor route.tsxcompiled by the package build. No MCP App views exist in templates, so nothing to change until one is added — at which point §1 P1 (.tsentry +.tsxcomponent) is the trap to avoid.6. Rsbuild 2.x currency
P2 ·
packages/rsc-runtime/package.json:94· rsbuild-v2-upgrade step 4: bump every Rsbuild/Rspack package in lockstep (taze major --include /rsbuild/) · The exact devDependency pin"@rspack/core": "2.2.1"mirrors the^2.2.0-0peer at:78; nothing underpackages/rsc-runtime/imports@rspack/core.@rsbuild/core@2.2.3(the #566 §1 P1 target) depends on@rspack/core ~2.2.2, so landing the bump without moving this line splits the tree into@rspack/core2.2.1 + 2.2.2 (two native@rspack/binding-*downloads) and lets@rspack/plugin-react-refresh@2.0.2peer-resolve against whichever copy pnpm picks.pnpm why @rspack/corealready shows two versions today (2.1.10 via@rslib/core@0.23.2, 2.2.1 via@rsbuild/core@2.2.1and this pin); #566 collapses the 2.1.x copy only if:94moves too. · Bump:94to2.2.2in the same PR, or drop the devDependency and rely on the peer. · Risk: none (no import).P3 ·
packages/agent-bundle/src/core/types.ts:259+packages/agent-bundle/src/config/validate.ts:1835-1882· rsbuild-v2-upgrade "Migrateperformance.chunkSplit→splitChunks" · Thetools.rsbuildhatch is typed as@rsbuild/core@2.2.1'sEnvironmentConfig, which still admits the two@deprecatedkeysperformance.chunkSplit(config.d.ts:584) andoutput.sourceMap.extract.js(config.d.ts:816);validateTools(AB4720–AB4724) checks shape and plugin collisions only. Because both framework profiles setsplitChunks: false(mcp-apps.ts:210,rslib.ts:632), a consumerchunkSplithits Rsbuild's "Bothperformance.chunkSplitandsplitChunksare set" branch (dist/626.js:9425) and is discarded — vialogger.warn, below the'silent'/'error'levels the framework builds with, so the author sees nothing. · Reject both keys invalidateToolswith an AB472x diagnostic pointing at the v2 replacement, and mention it on the tools-hatch docs page (en + zh). · Risk: none for compliant configs.P3 ·
pnpm-workspace.yaml:24· pnpm docs "minimumReleaseAge / minimumReleaseAgeExclude" ·minimumReleaseAgeExclude: ['@rsbuild/core@2.2.1']was added in #20 because 2.2.1 was <24 h old at the time; pnpm 11's built-in release-age window has long passed for 2.2.1, and both@rsbuild/core@2.2.3and@rslib/core@1.0.0(published 2026-09-03) are already outside it, so the entry neither helps nor blocks the #566 bump — it is dead config that will name a version no longer in the lockfile. · Delete the line in the bump PR, or setminimumReleaseAgeExcludePrune: true(pnpm ≥11.22; repo is on 11.23.0). · Risk: none.No deprecated Rsbuild 2.x option is in use anywhere. The only
@deprecatedkeys in the installed typings areperformance.chunkSplit,output.sourceMap.extract.jsanddev.setupMiddlewares;git grepacrosspackages/agent-bundle/src,packages/workbench,examples,rstest.*.ts,website/rspress.config.tsandwebsite/pluginsfinds none of them nor any v1 leftovers (source.alias,dev.startUrl,tools.htmlPlugin,output.enableLatestDecorators,source.transformImport,dev.progressBar,output.distPath.server, proxycontext/onProxy*,onDevCompileDone,htmlWebpackPlugintemplate params) outside a correct migration comment atrslib.ts:629. Every hook and JS-API call in use is current (onAfterEnvironmentCompile,onBeforeDevCompile,onAfterDevCompile,startDevServer({ getPortSilently }),loadEnv,mergeRsbuildConfig). Peer ranges are satisfied by 2.2.1 and by 2.2.3:@rsbuild/plugin-react@2.1.0(^2.0.0, latest),rsbuild-plugin-rsc@0.1.1(^2.0.0),rsbuild-plugin-publint@1.0.0,@rstest/core@0.11.10(~2.2.0; latest 0.11.12 wants~2.2.2),@rspress/core@2.0.21(^2.1.13). Not re-filed: the two-@rsbuild/coretree (2.1.13 via@rslib/core@0.23.2, 2.2.1 elsewhere) is #566 §1 P1.@rsbuild/core@rslib/core@0.23.2)package.json:68,packages/agent-bundle/package.json:101,packages/workbench/package.json:30,examples/rsc-agent-runtime/package.json:28@rsbuild/plugin-react@rspack/core@rsbuild/core@2.1.13)packages/rsc-runtime/package.json:94(exact)@rslib/core@rstest/core,adapter-rslib,browser,browser-react,playwright@rslint/corepackage.json:72,packages/agent-bundle/package.json:104@rspress/core+plugin-{llms,sitemap,twoslash,typedoc}website/package.json:18-22rsbuild-plugin-rscexamples/rsc-agent-runtime/package.json:36rsbuild-plugin-publintpackage.json:84@rsdoctor/rspack-pluginmain— installed only as audit scratch in the worktreeMeasurements
MCP Apps —
examples/mcp-appview (views/status-panel.ts+ template)compileMcpApps, 1 view, warm process)agent-bundle buildfor the whole example 6.0 s wallmcp-apps/status.html, 437.3 kB, 104.4 kB gzipviews/status-panel.ts) + 0.3 kB generatedagent-bundle/metazod1423 kB / 190 modules (v3 + v4),@modelcontextprotocol/sdk263 kB / 10,zod-to-json-schema98 kB / 63,@modelcontextprotocol/ext-apps65 kB / 2zod/v3/types.js128 kB,zod/v4/core/schemas.js96 kB,zod/v4/core/compile.js85 kB,@modelcontextprotocol/sdk/dist/esm/types.js72 kB,…/shared/protocol.js51 kB,ext-apps/dist/src/app.js33 kB.tsentry →.tsxcomponentReact.createElementwith freeReact(broken at runtime).tsxentry →.tsxcomponentreact/jsx-runtime)tools.rsbuildhatch probesinline-source-mapOK (1.35 MB),source-mapFAIL (opaque),mode: 'development'FAIL (opaque),minify: falseOK (575 kB),removeConsoleOK,overrideBrowserslistOK,source.defineOKWorkbench — production build, Rsdoctor (
rsdoctor-data.json, brief mode)index.html, 2.LICENSE.txt,THIRD_PARTY_NOTICES,src/mcp/APP-RENDERER-LICENSE)static/css/index.cssstatic/js/463.js634 kB (176 kB gz, default vendors group),static/js/index.js549 kB (142 kB gz, app code),static/js/lib-react.js185 kB (58 kB gz)async/743.js237 kB (markdown stack), four Shiki grammars at 171–177 kB each (typescript,jsx,tsx,javascript),async/763.js138 kBreact, onereact-dom, oneeffect, onezod)@shikijs/langs1005 kB (async), Workbench source 542 kB,zod332 kB (134 kB in the initial vendors chunk),react-dom174 kB,@modelcontextprotocol/client@2.0.0157 kB,effect119 kB + 62 kB,@modelcontextprotocol/sdk@1.30.031 kB,@modelcontextprotocol/ext-apps28 kB463.jscomposition@modelcontextprotocol/client157 kB + 40 kB,zod134 kB,effect119 kB + 62 kB,@modelcontextprotocol/sdk31 kB,ext-apps28 kB, micromark/hast/property-information ≈ 70 kB@shikijs/langs/dist/typescript.mjs177 kB,jsx.mjs174 kB,tsx.mjs171 kB,javascript.mjs171 kB,react-dom-client.production.js170 kB,@modelcontextprotocol/client/dist/src-*.mjs94 kB,src/mcp/mcp-page.tsx45 kB,effect/dist/internal/effect.js43 kBWorkbench — timings (3 runs each, medians; same machine, shared with other agents)
pnpm builddoes)rsbuild buildCLI wall incl. Node + pnpm start-up: 2.24 / 2.26 / 2.72 s → 2.26 s)performance.buildCachepopulated (not enabled in the repo; measured to size the option)startDevServer,onDevCompileDone)Docsite —
rspress build(Rspress 2.0.21 on Rsbuild 2.2.1; TypeDoc + twoslash + SSG of 1906 pages)performance.buildCachepopulated)doc_buildstatic/js14.75 MB / 1916 files; CSS 80 kBstatic/js/index.<hash>.jslib-react189 kB,lib-router35 kB; largest async route chunk 286 kBmode: production,target: ['web','es2017'],assetPrefix: '/agent-bundle/',filenameHash: true,sourceMap: false(JS),legalComments: 'linked',polyfill: 'off',minifyon,dataUriLimit4096,html.inject: 'head',scriptLoading: 'defer',performance.buildCacheon (Rspress default,buildDependencies= RspressinitRsbuild.js+ the config),printFileSize.compressed, Lightning CSS targetschrome ≥107, edge ≥107, firefox ≥104, safari ≥16, chunk splitchunks: 'all'withlib-react/lib-router/stylescache groupsAlready compliant (do not re-audit)
In addition to #566's list:
composeToolsLayersorder profile →tools.rsbuild→tools.rspack→ invariants (compose-layers.ts:32-36);enforceInvariantsruns last and pinsasyncChunks: false, the meta alias andVirtualModulesPlugin(mcp-apps.ts:212-238); one Rsbuild environment per app (:188-195);server.publicDir: false(:209);filenameHash: false+distPath.html: 'mcp-apps'→ stable[name].html, names validated kebab-case (validate.ts:513) and duplicate names must share identity (mcp-apps.ts:136-145);createRsbuild({ cwd: projectRoot })so a config in a subdirectory still resolves from the project root (mcp-apps.ts:268,build.ts:402,normalize.ts:852-854);result.close()on success (:296-298) and Rsbuild closes the compiler itself before rejecting; defaultsoutput.charsetutf8,polyfill: 'off',injectStyles: false,output.module: false;source.includedefault transpiles TS/JSX in dependencies but not plain JS;source.tsconfigPathauto-set only when<root>/tsconfig.jsonexists; provenance ignores the virtual module (:289-291,meta.ts:27-31); Rsdoctor: zero duplicate packages in the example app;security.nonce/sricorrectly unset — the sandbox CSP allows'unsafe-inline'(mcp-app-sandbox.ts:22-32) and there are no externalsrcs to hash; thetools.rsbuildhatch honoursremoveConsole,overrideBrowserslistandsource.define(verified).workbench.mdx:179-180);ProjectWatcherwatches the project root so entry, imported modules and templates all invalidate (watcher.ts:50-54,coordinator.ts:331-335); a failed rebuild keeps the last good epoch (coordinator.ts:578);collectBundledOutputEvidencereplaces plannedsourceInputswith the real module graph after each compile (mcp-apps.ts:283-304,provenance.ts:181-248).defineConfig+ exportedcreateWorkbenchConfig, proxy only when the env is set (rsbuild.config.ts:13, 40, 54;scripts/dev.mjs:3);changeOrigin: trueis the Rsbuild 2.2.1 default and asserted intests/rsbuild-workbench.test.ts:43; no path rewrite orws: trueneeded — every typed client route is under/api/…, transport is fetch +EventSource, no WebSocket; Rsbuild's compress middleware skipstext/event-stream, so SSE survives the proxy; non-/apitraffic is intentional (hash routingmain.tsx:342,blob:downloads, absolute sandbox iframe origins,data:media); hash routing only,#serveAssetmaps/→index.html, no SPA fallback needed,HEADsupported (foreground-server.ts:216, 947);#serveAssetsets onlycontent-type, no CSP conflict withfavicon="data:,"/deferscripts;index.htmlownslang, charset, viewport, title, favicon and the app never writesdocument.title;pluginReact()defaults (fast refresh in dev only, no React Compiler) are right for a SPA; 11 plain.cssfiles, zero.module.css, no PostCSS/Tailwind, nothing relying on Lightning CSS-specific transforms; Rsdoctor: zero duplicate packages (onereact,react-dom,effect,zod), 25 chunks;output.copytargets match the packaging contract (NOTICE:11,dev-workbench-packaging.test.ts:46-47); zeroconsole.*inpackages/workbench/src, soperformance.removeConsolehas nothing to do.base→assetPrefix/publicPath/agent-bundle/; no.mappublished;legalComments: 'linked'kept for licence compliance; route preloads come from Rspress, notperformance.preload.tools.rspack.namefor MultiStats matching (rsbuild.config.ts:318-320, 359, 399-400);splitChunks: falseon the node env matches Rsbuild 2.2's server default (multi-entrychunks: 'all', same rationale asrslib.ts:629-632); widget entries all onclient-anchor.tsper thersbuild-plugin-rscenvironments contract; apphmr: false/liveReload: falsewith a custom reload plugin;emitRuntimeManifestisapply: 'build'so it never raceswriteToDisk; documented hooks only;server.port: 0/host: '127.0.0.1'/printUrls: false+getPortSilently+server.close(); all four integration tests reusecreateRscRuntimeRsbuildConfig;create-agent-bundletemplates ship no Rsbuild config or deps;services/mcp-run.ts:199-203usesloadEnvonly.@rsbuild/plugin-react,@rspress/*,rsbuild-plugin-rsc,rsbuild-plugin-publintat latest.Not assessed / caveats
performance.buildCache, which the repo does not enable — it sizes the option, it does not describe today'spnpm build.mcp-route-client.ts:592-595, server checks atforeground-server.ts:804-827, browsers always sendOriginon POST, http-proxychangeOriginrewritesHostonly); it was not reproduced live..ts→.tsxReact finding (§1 P1) and thealiasStrategyfinding (§1 P2) were reproduced with throwaway fixtures through the realcompileMcpApps; the error-surfacing finding was reproduced with the real CLI against a copy ofexamples/mcp-app.NODE_OPTIONS; peak RSS 8.36 GB fits publicubuntu-latest(16 GB) with headroom, but the same build would OOM on a private-repo 8 GB runner. No Rsbuild knob applies; it is SSG/runner sizing.rslib.ts,entries.ts,cli-bins.ts,package-build.ts) beyond theirlogLevelfor comparison — Rstack skills audit: build compiler, package builds, tests, docsite #566 §1/§2; Rspress content (sidebar size, page count, TypeDoc) — Rstack skills audit: build compiler, package builds, tests, docsite #566 §4; the Rstest runtime-playground configs — Rstack skills audit: build compiler, package builds, tests, docsite #566 §3 deletes them; whetherperformance.printFileSizestill walks assets underlogLevel: 'silent'(CPU cost not measured); whether thealiasStrategyshadowing also affects the Rslib surfaces' framework aliases.@rsdoctor/rspack-pluginadded to the rootpackage.json/lockfile for Rsdoctor runs; an untrackedwebsite/rspress.audit.config.ts);mainhas neither, and nothing was committed or pushed. The worktree has been removed.