Repository navigation
perf(sdk): load js-yaml, semver and the uipack renderer on first use under require() - #935
Merged
alexmercerpo merged 1 commit intoOct 21, 2026
Conversation
…under require() A server loaded js-yaml (25 files), semver (23) and the @frontmcp/uipack root at startup, for code most servers never run: SKILL.md and YAML config parsing, the ESM loader's version ranges, and the auth-UI pages. They now come through three subpath imports, #js-yaml, #semver and #uipack-render, whose `require` variant reads each function from its package the first time it is used, like @frontmcp/protocol's #client. The CommonJS build keeps them external and ships both variants; the ESM and browser builds resolve them at build time to the static variant, so those bundles import the packages as before.
Contributor
Author
|
@coderabbitai full review |
Contributor
Performance Test ResultsStatus: ✅ All tests passed Summary
Total: 101 tests across 21 projects 📊 View full report in workflow run Generated at: 2026-10-21T15:32:48.723Z |
Contributor
✅ Action performedFull review finished. |
Contributor
alexmercerpo
approved these changes
Oct 21, 2026
Contributor
Cherry-pick CreatedA cherry-pick PR to Please review and merge if this change should also be in If the cherry-pick is not needed, close the PR. |
This was referenced Oct 21, 2026
alexmercerpo
pushed a commit
that referenced
this pull request
Oct 21, 2026
* perf(sdk): serve in-memory, direct-client and web-standard MCP with the loaded protocol module (#934) createInMemoryServer(), the direct client's notification and elicitation handlers, and runWebStandardMcp() took their classes and schemas from `await import('@frontmcp/protocol')`. In a CommonJS process that import resolves the package's `import` condition, so connect(), create() clients and createFetchHandler() loaded a second, ESM copy of @frontmcp/protocol and, through it, of the MCP SDK, on top of the CommonJS copy the SDK had already required. They now use the statically imported @frontmcp/protocol, as runStdio() does since #923. In an ESM process the import resolves the same ESM module as before. (cherry picked from commit 5b54191) * perf(sdk): load js-yaml, semver and the uipack renderer on first use under require() (#935) A server loaded js-yaml (25 files), semver (23) and the @frontmcp/uipack root at startup, for code most servers never run: SKILL.md and YAML config parsing, the ESM loader's version ranges, and the auth-UI pages. They now come through three subpath imports, #js-yaml, #semver and #uipack-render, whose `require` variant reads each function from its package the first time it is used, like @frontmcp/protocol's #client. The CommonJS build keeps them external and ships both variants; the ESM and browser builds resolve them at build time to the static variant, so those bundles import the packages as before. (cherry picked from commit b7ee831) * perf(sdk): load the SSE transport with the first SSE session (#937) A stdio server initializes the scope's TransportService, and with it TransportSSEAdapter, which defined RecreateableSSEServerTransport at startup. Its base class reads POST bodies with raw-body and content-type, so a stdio server loaded raw-body, content-type, iconv-lite, http-errors and their dependencies (16 files) that it never uses. TransportSSEAdapter now imports the transport as a type and loads it with loadSseTransport(), which TransportService awaits where it creates the LocalTransporter of an SSE session, as it does for the Streamable HTTP transport since #924. (cherry picked from commit f13b8af)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack 2 of 4, based on #934 (
perf/1.10.x-protocol-static-imports). Merge in order.What and why
Every server loaded three packages at startup for code most servers never run:
SKILL.mdfront matter and YAML config files (skill-md-parser.ts,sep-2640.builders.ts, the config plugin'sconfig-loader.ts).esm-loader/semver.utils.ts).@frontmcp/uipackroot, for the custom auth-UI pages (auth-ui.render.ts). The OAuth flows import that module, so it loaded with them, in public mode too.They now come through three subpath imports in
libs/sdk/package.json:#js-yaml,#semverand#uipack-render. Each has two variants inlibs/sdk/src/third-party/, as@frontmcp/protocol's#clientdoes:requirevariant (lazy-*.ts) defines each function as a getter that requires its package the first time it is read.The CommonJS build keeps the three specifiers external and builds both variants as entry points, so a CommonJS process resolves the lazy one at run time. The ESM and browser builds resolve them at build time to the static variant, so those bundles import js-yaml, semver and
@frontmcp/uipackas they did before.tsconfig.base.json,jest.imports-mapper.jsand the module-boundaries rule list the three specifiers like the existing ones.The benchmark's HTTP server loads 404 modules instead of 450, and its stdio server 280 instead of 326. A server that parses a
SKILL.mdfile, usesTool.esm()/App.esm()with a version range, or serves an auth-UI page loads the package when it does that, as before.Results
Apple M4 Pro (14 cores, 48 GiB), macOS 15.6.1, Node.js 24.18.1, on AC power, with the machine's normal background load (an IDE and other jobs). The server is the benchmark's FrontMCP implementation (
frontmcp build --target node), run withnodeand no flags, againstnpm packed builds of this branch and its base, installed into scratch copies of the implementation and built withnpm run build, measured in one session in randomized, interleaved rounds with the benchmark's own harness functions. Cold start is the time from spawningnodeto the firsttools/callresponse,initializeincluded: one discarded start per server, then 8 rounds of 4 starts per transport in random order. The 100-tool startup is spawn to the first fulltools/list, 5 rounds of 2 starts, and its RSS is read 1 s after that. Throughput is the benchmark's closed-looptools/callat concurrency 1, 16 and 64 (3 s warmup, 10 s measured, 5 rounds); RSS after startup is read 1 s after the first call, RSS after load after the last level, median of the 5 rounds. Heap used isprocess.memoryUsage().heapUsed700 ms after start (after the first call, no forced GC), median of 10 interleaved starts per transport in a separate run right after. MB is 10^6 bytes. The 1-minute load average was 3.9 to 7.6 during the cold-start and 100-tool runs, and 6.9 to 9.6 during the heap runs. During the throughput rounds an outside job pushed it as high as 47, and two of the five rounds of each branch ran slower; the medians below are of all five.perf/1.10.x-protocol-static-importstools/list, p50Per round, the median cold start moved by -7.8 ms over stdio (from -13.8 to +0.9 ms) and by -4.0 ms over HTTP (from -13.4 to +18.9 ms). The change doesn't touch the request path: throughput and RSS after load are within their run-to-run spread (c=64: 7,706 to 8,855 rps for the base, 5,238 to 9,012 for this PR; RSS after load 433 to 475 MB and 421 to 483 MB).
Tests
requirevariant (src/third-party/__tests__/lazy-*.spec.ts), modeled on@frontmcp/protocol'slazy-client.spec.ts: requiring the module doesn't load the package, reading a function loads it once, and the exports are the same functions the static variant exports.nx run sdk:test --coverage: 7,139 tests in 558 suites pass, among them theSKILL.mdparser, the config loader, the ESM loader and the auth-UI specs, which run through the static variant (jest.imports-mapper.js).nx run sdk:lint,nx run sdk:typecheck,node --test scripts/*.test.mjs(84 pass, among them the browser-build and worker-condition guards) and Prettier on the changed files pass.dist/package.jsonmaps each specifier'srequirecondition to./third-party/lazy-*.jsand its other conditions to the static file;dist/index.jsrequires the three specifiers, anddist/esm/index.mjsanddist/browser/index.cjsimport js-yaml directly, as before.module.registerHookspreload: the benchmark's HTTP server loads 404 modules instead of 450, its stdio server 280 instead of 326; what goes is js-yaml, semver and the@frontmcp/uipackroot, and three small files from@frontmcp/sdk/third-partycome in.Summary by CodeRabbit
New Features
Tests