Repository navigation
perf(sdk): serve in-memory, direct-client and web-standard MCP with the loaded protocol module - #934
Merged
Conversation
…he loaded protocol module
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.
Contributor
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:02:58.662Z |
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. |
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 was referenced Oct 21, 2026
This was referenced Nov 2, 2026
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 1 of 5, based on
release/1.10.x. Merge in order.What and why
#923 made
runStdio()use the statically imported@frontmcp/protocol. Three other places still took classes and schemas fromawait importWithRequireFallback(() => import('@frontmcp/protocol'), ...):createInMemoryServer():McpServer,InMemoryTransport,McpError,ErrorCodeandElicitResultSchema. It servesconnect(),create()'s clients and every other direct client.ResourceUpdatedNotificationSchemaandElicitRequestSchema.runWebStandardMcp()(thehandleWebFetchstage ofhttp:request, behindcreateFetchHandler()and the Worker builds):McpServerandWebStandardStreamableHTTPServerTransport.The SDK's CommonJS build keeps that
import()as a native dynamic import, which resolves@frontmcp/protocol'simportcondition. So a CommonJS process loaded the package a second time as ESM, and through it a second copy of the MCP SDK: its ESMtypes.jswith every protocol schema,shared/protocol.js, the client and its transports, and the ESM builds of zod andzod-to-json-schema. That second copy was on the path to the first answer.All three now use the statically imported
@frontmcp/protocol, which the SDK already requires at startup. A CommonJS process uses one copy of@frontmcp/protocoland of the MCP SDK, the CommonJS one; the benchmark'sconnect()script loads 311 modules instead of 513, and none of them from anesmdirectory. An ESM process resolves the same ESM module as before. The browser and Worker builds keep@frontmcp/protocolexternal, as before.What changes besides startup: in a CommonJS process these classes and schemas now come from the MCP SDK's CommonJS build instead of its ESM build (same package version), so an
McpErrorthey raise is an instance of the same class the rest of a CommonJS FrontMCP server uses.The HTTP and stdio servers of the benchmark don't run this code: their loaded modules are the same before and after.
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). Two CommonJS scripts, run with
nodeandNODE_ENV=productionagainstnpm packed builds of this branch and ofrelease/1.10.x(the same code as the published 1.10.9-rc.3), installed into scratch copies of the benchmark's FrontMCP implementation. Both serve the benchmark'saddtool:connect():connect(config), thenclient.callTool('add', ...).createFetchHandler():FrontMcpInstance.createFetchHandler(config), theninitialize,notifications/initializedandtools/callas WebRequests.Each script reports, from inside the process: the time from process start to
@frontmcp/sdkbeing loaded, the time from there to thetools/callresult, the total, andprocess.memoryUsage()700 ms after the result (no forced GC). 12 rounds, the four (branch, script) pairs in random order in each round; medians. MB is 10^6 bytes. The 1-minute load average was 5.5 to 7.7.release/1.10.xconnect():connect()to connectedconnect(): process start to thetools/callresultconnect(): heap usedconnect(): RSSconnect(): modules loadedcreateFetchHandler(): SDK loaded to thetools/callresponsecreateFetchHandler(): process start to thetools/callresponsecreateFetchHandler(): heap usedcreateFetchHandler(): RSScreateFetchHandler(): modules loadedLoading the SDK takes the same time on both (162.3 and 157.9 ms for
connect(), 155.2 and 158.3 ms forcreateFetchHandler()); the difference is all after it.Tests
nx run sdk:test --coverage: 7,133 tests in 555 suites pass, among them the direct-client,connect(),create(), in-memory server and web fetch handler suites. The direct-client andconnect()specs mockClientthroughjest.mock('@frontmcp/protocol')with the actual module for the rest, which the static import picks up the same way.nx run sdk:lint,nx run sdk:typecheckand Prettier on the changed files pass.module.registerHookspreload: the two scripts above load 311 and 330 modules instead of 513 and 527, and no.mjsfile or file under anesmdirectory. The benchmark's HTTP and stdio servers load the same 450 and 326 modules as before.Summary by CodeRabbit