From 76e4153d346b14ef194222bbeef240cf8a16dc99 Mon Sep 17 00:00:00 2001 From: Ysopwnable <161784452+N0zoM1z0@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:01:01 +0800 Subject: [PATCH] perf(mcp): reduce Windows startup module resolution (#1814) perf(mcp): bundle internal modules to reduce Windows startup I/O --- package-lock.json | 23 +---- package.json | 11 ++- scripts/bundle-mcp-runtime.d.mts | 1 + scripts/bundle-mcp-runtime.mjs | 104 ++++++++++++++++++++ scripts/prepack.mjs | 1 + scripts/rea.mjs | 8 +- tests/boundary/cli/dispatcher.test.ts | 3 +- tests/boundary/process/mcpBundle.test.ts | 118 +++++++++++++++++++++++ turbo.json | 1 + 9 files changed, 240 insertions(+), 30 deletions(-) create mode 100644 scripts/bundle-mcp-runtime.d.mts create mode 100644 scripts/bundle-mcp-runtime.mjs create mode 100644 tests/boundary/process/mcpBundle.test.ts diff --git a/package-lock.json b/package-lock.json index 132da9196..aaed5cd61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "lint-staged": "16.4.0", "oxfmt": "0.72.0", "oxlint": "1.87.0", + "rolldown": "1.2.12", "turbo": "2.11.7", "typescript": "7.0.2", "typescript-termux": "npm:typescript@5.9.3", @@ -1128,7 +1129,6 @@ "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/oxc-project" } @@ -2117,7 +2117,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2135,7 +2134,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2153,7 +2151,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2171,7 +2168,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2189,7 +2185,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2207,7 +2202,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2228,7 +2222,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2249,7 +2242,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2270,7 +2262,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2291,7 +2282,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2312,7 +2302,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2333,7 +2322,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2351,7 +2339,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2369,7 +2356,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2387,7 +2373,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2397,8 +2382,7 @@ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.64.1", @@ -6723,11 +6707,10 @@ }, "node_modules/rolldown": { "version": "1.2.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "resolved": "https://mirrors.cloud.tencent.com/npm/rolldown/-/rolldown-1.2.12.tgz", "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/types": "=0.152.0", "@rolldown/pluginutils": "^1.0.0" diff --git a/package.json b/package.json index 3d8d6fdfa..673b079ef 100644 --- a/package.json +++ b/package.json @@ -249,8 +249,8 @@ "verify:evm:interface": "npm run build:cached && node scripts/verify-evm-interface.mjs", "verify:recorded:crash": "npm run build:cached && node scripts/verify-recorded-crash.mjs", "mcp-catalog:check:built": "node scripts/generate-mcp-tool-catalog.mjs --check", - "compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json", - "compile:termux": "node scripts/clean-build-output.mjs && node --max-old-space-size=3072 node_modules/typescript-termux/lib/tsc.js -p tsconfig.build.json", + "compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/bundle-mcp-runtime.mjs", + "compile:termux": "node scripts/clean-build-output.mjs && node --max-old-space-size=3072 node_modules/typescript-termux/lib/tsc.js -p tsconfig.build.json && node scripts/bundle-mcp-runtime.mjs", "artifacts:mcp-catalog": "node scripts/generate-mcp-tool-catalog.mjs", "artifacts:skills": "node scripts/generate-skill-metadata.mjs", "artifacts:product-catalog": "node scripts/generate-product-catalog.mjs", @@ -285,6 +285,7 @@ "canonicalize": "3.0.0", "cross-spawn": "7.0.6", "diff": "9.0.0", + "evmole": "0.9.3", "har-schema": "2.0.0", "ignore": "7.0.6", "incur": "0.6.1", @@ -301,9 +302,8 @@ "stream-json": "3.7.0", "write-file-atomic": "7.0.0", "ws": "8.21.0", - "zod": "4.4.3", - "evmole": "0.9.3", - "yaml": "2.9.0" + "yaml": "2.9.0", + "zod": "4.4.3" }, "overrides": { "vitepress": { @@ -324,6 +324,7 @@ "lint-staged": "16.4.0", "oxfmt": "0.72.0", "oxlint": "1.87.0", + "rolldown": "1.2.12", "turbo": "2.11.7", "typescript": "7.0.2", "typescript-termux": "npm:typescript@5.9.3", diff --git a/scripts/bundle-mcp-runtime.d.mts b/scripts/bundle-mcp-runtime.d.mts new file mode 100644 index 000000000..7fff6f125 --- /dev/null +++ b/scripts/bundle-mcp-runtime.d.mts @@ -0,0 +1 @@ +export const bundleMcpRuntime: (root: string) => Promise; diff --git a/scripts/bundle-mcp-runtime.mjs b/scripts/bundle-mcp-runtime.mjs new file mode 100644 index 000000000..2750883f5 --- /dev/null +++ b/scripts/bundle-mcp-runtime.mjs @@ -0,0 +1,104 @@ +import { parse } from "@babel/parser"; +import { readFile } from "node:fs/promises"; +import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +/** + * Reduce filesystem resolution during MCP startup. Keep dependencies external, + * dynamic imports lazy, and the original compiled files for CLI/worker entrypoints. + */ +export const bundleMcpRuntime = async (root) => { + const compiledRoot = join(root, "dist"); + const { rolldown, RUNTIME_MODULE_ID } = await import("rolldown"); + const { imports = {} } = JSON.parse( + await readFile(join(root, "package.json"), "utf8"), + ); + const bundle = await rolldown({ + input: { main: join(compiledRoot, "main.js") }, + platform: "node", + treeshake: false, + external: (source) => !source.startsWith(".") && !isAbsolute(source), + plugins: [ + { + name: "preserve-compiled-module-locations", + transform(code, id) { + if (id === RUNTIME_MODULE_ID) return null; + if (!code.includes("import.meta")) return null; + const sourcePath = relative(compiledRoot, id); + if ( + isAbsolute(sourcePath) || + sourcePath === ".." || + sourcePath.startsWith(`..${sep}`) + ) + throw new Error(`MCP bundle input is outside dist: ${id}`); + // All chunks are directly under dist/mcp. Preserve the original + // module URL, including URL escaping, for bridges and createRequire. + const moduleUrl = `new URL(${JSON.stringify(`../${sourcePath.split(sep).map(encodeURIComponent).join("/")}`)}, import.meta.url).href`; + const edits = []; + const visit = (node) => { + if (node === null || typeof node !== "object") return; + const isMeta = (value) => + value?.type === "MetaProperty" && + value.meta.name === "import" && + value.property.name === "meta"; + if ( + node.type === "CallExpression" && + node.callee.type === "MemberExpression" && + !node.callee.computed && + isMeta(node.callee.object) && + node.callee.property.name === "resolve" && + node.arguments.length === 1 && + node.arguments[0].type === "StringLiteral" && + Object.hasOwn(imports, node.arguments[0].value) + ) + // Package imports resolve in the same package scope after bundling. + return; + if ( + node.type === "MemberExpression" && + !node.computed && + isMeta(node.object) && + node.property.name === "url" + ) { + edits.push({ start: node.start, end: node.end }); + return; + } + if (isMeta(node)) + throw new Error( + `Unsupported import.meta use in MCP bundle: ${id}`, + ); + for (const value of Object.values(node)) { + if (Array.isArray(value)) value.forEach(visit); + else visit(value); + } + }; + visit(parse(code, { sourceType: "module" }).program); + for (const { start, end } of edits.sort((a, b) => b.start - a.start)) + code = code.slice(0, start) + moduleUrl + code.slice(end); + return { code, map: null }; + }, + }, + ], + }); + try { + await bundle.write({ + dir: join(compiledRoot, "mcp"), + format: "esm", + entryFileNames: "[name].js", + chunkFileNames: "[name]-[hash].js", + codeSplitting: true, + strictExecutionOrder: true, + minify: false, + sourcemap: false, + }); + } finally { + await bundle.close(); + } +}; + +if ( + process.argv[1] !== undefined && + pathToFileURL(resolve(process.argv[1])).href === import.meta.url +) + await bundleMcpRuntime( + resolve(dirname(fileURLToPath(import.meta.url)), ".."), + ); diff --git a/scripts/prepack.mjs b/scripts/prepack.mjs index c085449eb..3b9f9adf1 100644 --- a/scripts/prepack.mjs +++ b/scripts/prepack.mjs @@ -46,6 +46,7 @@ if (await pathExists(join(root, "src"))) { } else { for (const path of [ "dist/main.js", + "dist/mcp/main.js", "dist/mcpDoctor.js", "dist/cli.js", "dist/cliOutput.js", diff --git a/scripts/rea.mjs b/scripts/rea.mjs index 769fa2fe0..57272d987 100755 --- a/scripts/rea.mjs +++ b/scripts/rea.mjs @@ -5,7 +5,7 @@ import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; // Route production MCP before importing Incur. Incur owns registration helpers -// such as `mcp add`, while only dist/main.js may serve the stdio tool catalog. +// such as `mcp add`, while the MCP runtime serves the stdio tool catalog. const args = process.argv.slice(2); const { default: packageJson } = await import("../package.json", { with: { type: "json" }, @@ -31,9 +31,9 @@ if ( } const runtimeFiles = isMcpMode - ? ["dist/main.js"] + ? ["dist/mcp/main.js"] : isMcpDoctorMode - ? ["dist/main.js", "dist/mcpDoctor.js"] + ? ["dist/mcp/main.js", "dist/mcpDoctor.js"] : ["dist/cli.js", "dist/cliOutput.js", "dist/cli/streamedJsonOutput.js"]; if (!(await compiledRuntimeExists(runtimeFiles))) { @@ -42,7 +42,7 @@ if (!(await compiledRuntimeExists(runtimeFiles))) { ); process.exitCode = 1; } else if (isMcpMode) { - const { runEntrypoint } = await import("../dist/main.js"); + const { runEntrypoint } = await import("../dist/mcp/main.js"); await runEntrypoint(); } else if (isMcpDoctorMode) { const { runProductionMcpDoctorCli } = await import("../dist/mcpDoctor.js"); diff --git a/tests/boundary/cli/dispatcher.test.ts b/tests/boundary/cli/dispatcher.test.ts index 8de9028cf..f002fcd51 100644 --- a/tests/boundary/cli/dispatcher.test.ts +++ b/tests/boundary/cli/dispatcher.test.ts @@ -12,6 +12,7 @@ describe("executable dispatcher", () => { const scripts = await workspace.mkdir("scripts"); await workspace.mkdir("dist"); await workspace.mkdir("dist/cli"); + await workspace.mkdir("dist/mcp"); await Promise.all([ copyFile("scripts/rea.mjs", join(scripts, "rea.mjs")), workspace.write( @@ -19,7 +20,7 @@ describe("executable dispatcher", () => { JSON.stringify({ type: "module", version: "1.0.0" }), ), workspace.write( - "dist/main.js", + "dist/mcp/main.js", 'export const runEntrypoint = () => process.stdout.write(JSON.stringify({ adapter: "mcp" }));\n', ), workspace.write( diff --git a/tests/boundary/process/mcpBundle.test.ts b/tests/boundary/process/mcpBundle.test.ts new file mode 100644 index 000000000..3895b2ce8 --- /dev/null +++ b/tests/boundary/process/mcpBundle.test.ts @@ -0,0 +1,118 @@ +import { readFile, readdir } from "node:fs/promises"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; + +import { describe, expect } from "vitest"; + +import { bundleMcpRuntime } from "../../../scripts/bundle-mcp-runtime.mjs"; +import { workspaceCliTest } from "../../support/cli/workspaceCliFixture.js"; + +describe("MCP runtime bundle", () => { + workspaceCliTest( + "preserves module locations, lazy side effects, package resolution and worker entrypoints", + async ({ processes, workspace }) => { + await workspace.mkdir("dist/provider"); + await workspace.mkdir("dist/composition"); + await workspace.mkdir("bridge"); + await workspace.mkdir("node_modules/external-probe"); + const files = { + "package.json": JSON.stringify({ + type: "module", + imports: { + "#javascript-analysis-worker": + "./dist/composition/javascriptAnalysisWorkerProcess.js", + }, + }), + "bridge/resource.txt": "bridge resource\n", + "node_modules/external-probe/package.json": JSON.stringify({ + name: "external-probe", + main: "index.cjs", + }), + "node_modules/external-probe/index.cjs": 'module.exports = "external";', + "dist/provider/probe λ.js": ` +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +globalThis.effects ??= []; +globalThis.effects.push('provider'); +export const facts = { + url: import.meta.url, + resource: readFileSync(new URL('../../bridge/resource.txt', import.meta.url), 'utf8'), + external: createRequire(import.meta.url)('external-probe'), + worker: import.meta.resolve('#javascript-analysis-worker') +};`, + "dist/lazy.js": ` +globalThis.effects.push('lazy'); +export const lazyUrl = import.meta.url;`, + "dist/composition/javascriptAnalysisWorkerProcess.js": + 'process.stdout.write("worker-entry");', + "dist/main.js": ` +import { facts } from './provider/probe λ.js'; +import { execFileSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +globalThis.effects.push('main'); +export const run = async () => { + const beforeLazy = [...globalThis.effects]; + const { lazyUrl } = await import('./lazy.js'); + return { + ...facts, beforeLazy, afterLazy: globalThis.effects, lazyUrl, + workerOutput: execFileSync(process.execPath, [fileURLToPath(facts.worker)], { encoding: 'utf8' }) + }; +};`, + }; + for (const [path, contents] of Object.entries(files)) + await workspace.write(path, contents); + const invoke = async (entry: string) => { + const moduleUrl = pathToFileURL(join(workspace.root, entry)).href; + const result = await processes.run( + process.execPath, + [ + "--input-type=module", + "--eval", + `const { run } = await import(${JSON.stringify(moduleUrl)}); process.stdout.write(JSON.stringify(await run()));`, + ], + { cwd: workspace.root }, + ); + expect(result.exitCode).toBe(0); + return JSON.parse(result.stdout) as unknown; + }; + const original = await invoke("dist/main.js"); + await bundleMcpRuntime(workspace.root); + const bundled = await invoke("dist/mcp/main.js"); + expect(bundled).toEqual(original); + expect(bundled).toMatchObject({ + resource: "bridge resource\n", + external: "external", + beforeLazy: ["provider", "main"], + afterLazy: ["provider", "main", "lazy"], + workerOutput: "worker-entry", + }); + for (const [path, contents] of Object.entries(files)) + expect(await readFile(join(workspace.root, path), "utf8")).toBe( + contents, + ); + expect( + (await readdir(join(workspace.root, "dist/mcp"))).length, + ).toBeGreaterThan(1); + }, + ); + + workspaceCliTest( + "rejects module-relative resolution and unknown import metadata during the build", + async ({ workspace }) => { + await workspace.mkdir("dist"); + await workspace.write("package.json", '{"type":"module"}'); + for (const expression of [ + "import.meta.resolve('./worker.js')", + "import.meta.env", + ]) { + await workspace.write( + "dist/main.js", + `export const value = ${expression};`, + ); + await expect(bundleMcpRuntime(workspace.root)).rejects.toThrow( + "Unsupported import.meta use in MCP bundle", + ); + } + }, + ); +}); diff --git a/turbo.json b/turbo.json index 7c0314b24..03ce42b73 100644 --- a/turbo.json +++ b/turbo.json @@ -10,6 +10,7 @@ "package-lock.json", "scripts/check-dependency-install.mjs", "scripts/clean-build-output.mjs", + "scripts/bundle-mcp-runtime.mjs", "scripts/generate-package-metadata.mjs", "src/**/*.ts", "!src/**/*.test.ts",