Skip to content

CI only, do not merge: the whole perf stack (#919 to #925) - #926

Closed
frontegg-david wants to merge 10 commits into
release/1.10.xfrom
perf/1.10.x-sdk-semver-subpaths
Closed

frontegg-david wants to merge 10 commits into
release/1.10.xfrom
perf/1.10.x-sdk-semver-subpaths

Conversation

@frontegg-david

Copy link
Copy Markdown
Contributor

Do not merge. This draft only runs CI on the whole perf stack (#919 to #925) against release/1.10.x: CI runs only on PRs into release/**, and #920 to #925 are based on the branch below them. It closes once the stack is green.

…e per character

V8 keeps the source of every loaded script for the life of the process, and
stores it with two bytes per character when one character is above U+00FF.
esbuild escapes non-ASCII characters in strings and identifiers but copies the
comments it keeps as they are, so an em dash or a box-drawing rule in a comment
doubled the memory the SDK bundle's source takes (6 MB instead of 3 MB); tsc
output (the CLI, @frontmcp/nx) keeps them in strings too.

Each publishable package's build now ends with scripts/escape-non-ascii.mjs,
which rewrites every non-ASCII character in its .js, .mjs and .cjs output as a
\uXXXX escape. A file is rewritten only when esbuild compiles the escaped text
to the same program as the original, so a raw template string or a character
after a backslash stops the build instead. CI checks the built output with
--check.
…nder require()

@frontmcp/protocol's root re-exported the MCP SDK's Client,
StreamableHTTPClientTransport, SSEClientTransport and StdioClientTransport,
so every CommonJS server loaded the SDK's client, its OAuth client, the SSE and
streamable HTTP client transports, eventsource and cross-spawn (31 files) at
startup, though a server only uses them for remote apps.

The client exports now come from a #client subpath import. Under the require
condition it resolves to lazy-client.js, whose getters load each MCP SDK module
the first time the export is read; worker, browser and ESM importers get the
same static re-exports as before (an ESM importer gets esm/client.mjs, which
strip-dist now maps for an `import` target, so the client still comes from the
MCP SDK's ESM build). The public exports don't change.
@coderabbitai

coderabbitai Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 21, 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-10-21T12:19:03.087Z
Commit: 517ef0db

…d so a coverage threshold

lazy-client.spec.ts is @frontmcp/protocol's first test file, so CI's protocol:test --coverage now applies the package's 95% threshold, and the re-export modules nobody had tested left it at 21%. exports.spec.ts checks that the Node modules hand back the MCP SDK's classes, that the browser stand-ins throw their errors, and the 2026-07-28 version helpers. index.ts is left out of coverage, as in most of the libraries here.
@frontegg-david
frontegg-david force-pushed the perf/1.10.x-sdk-semver-subpaths branch 2 times, most recently from b0a3ae3 to 1e7194d Compare October 21, 2026 11:44
…h loads none of the parser

@frontmcp/utils imported analyzeForReDoS and REDOS_THRESHOLDS from the @enclave-vm/ast root, which loads the whole AST validator and its parser (acorn, acorn-walk, astring), so every FrontMCP server loaded them at startup. @enclave-vm/ast 2.17.0 exports the analyzer alone as @enclave-vm/ast/redos (agentfront/enclave#104). @enclave-vm/core moves to 2.17.0 with it in the root and plugin-codecall, since core pins ast exactly; the tree keeps one @enclave-vm/ast.
…no source maps

V8 keeps the source of every loaded script for the life of the process, and
parses all of it at load. The SDK's CommonJS bundle was 3.07 million
characters, of which comments and indentation are about a third.

Every package's build-cjs and build-esm now set esbuild's minifyWhitespace:
comments and whitespace go, identifiers and syntax stay. The SDK's index.js
drops from 3.07 MB to 2.0 MB. The tsc-built packages (frontmcp, @frontmcp/nx,
@frontmcp/skills) stop emitting .js.map files, so no package ships source maps.

minifyWhitespace also drops the comments bundlers read. The one that changes
behavior is the webpackIgnore / @vite-ignore pair on the ESM loader's import()
of a computed specifier: without it, rspack (the CLI's lambda and vercel
bundler) replaces the import with a context that throws "Cannot find module".
That function now lives in its own module, reached through the #import-computed
subpath import and built unminified, so the comments stay. The browser builds
(dist/browser) are not minified, so the @__PURE__ annotations their per-module
tree-shaking relies on stay too.
… keep their pure annotations

esbuild's minifyWhitespace drops /* @__PURE__ */ annotations, which bundlers use to tree-shake @frontmcp/react, @frontmcp/ui and @frontmcp/plugin-webmcp out of browser apps. Servers don't load those packages for their own startup, so minifying them saved nothing this stack measures.
…oaded

runStdio() took McpServer and StdioServerTransport from
`await import('@frontmcp/protocol')`. In a CommonJS process that import
resolves the package's `import` condition, so a stdio server loaded a second,
ESM copy of @frontmcp/protocol and, through it, of the MCP SDK (types.js and
its zod schemas, the client, shared/protocol.js, zod-to-json-schema), on top of
the CommonJS copy the SDK had already required.

runStdio() now uses the statically imported @frontmcp/protocol, as the HTTP
transport already does, so a CommonJS stdio server uses one copy of the MCP
SDK. In an ESM process the import resolves the same ESM module as before.
…e first HTTP session

A stdio server loaded the MCP SDK's Node Streamable HTTP transport at startup,
and with it @hono/node-server, which reads the global Request and Response
when it loads and so makes Node load its bundled undici. Two things did it:
@frontmcp/protocol's CommonJS root required #mcp-streamable-http, and the
SDK's RecreateableStreamableHTTPServerTransport, which extends the MCP SDK
class, was defined when the SDK's root initialized the transport barrel.

- #mcp-streamable-http gets a require variant whose getters load each
  transport the first time it is read, and a types entry ahead of it.
- TransportStreamableHttpAdapter imports the recreateable transport as a type
  and loads it with loadStreamableHttpTransport(); TransportService awaits it
  where it creates a LocalTransporter, which it already did asynchronously.
- The SDK root imports the in-memory server, web-fetch handler, web-standard
  helpers and transport types from their modules instead of the barrel, which
  initialized every module transport/index.ts re-exports.

An HTTP server now loads the transport with its first session; a stdio server
never does.
…P and stateless HTTP sessions

An SSE session uses TransportSSEAdapter, which never touches the Streamable HTTP class, so an SSE-only server no longer loads it either.
semver.utils.ts imported the semver package root, which loads all 46 of its
files, at startup. It uses seven functions; importing them from their own
modules (semver/functions/*.js, semver/ranges/*.js) loads 23 files, with the
same results.
@frontegg-david
frontegg-david force-pushed the perf/1.10.x-sdk-semver-subpaths branch from 1e7194d to 017448c Compare October 21, 2026 12:04
@frontegg-david

Copy link
Copy Markdown
Contributor Author

Closing: this only ran CI on the whole stack, and #919 to #925 are merged. release/1.10.x (f9bf70c) has the same tree as this branch.

@frontegg-david
frontegg-david deleted the perf/1.10.x-sdk-semver-subpaths branch October 21, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant