-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtsup.mcpb.config.ts
More file actions
78 lines (76 loc) · 3.24 KB
/
Copy pathtsup.mcpb.config.ts
File metadata and controls
78 lines (76 loc) · 3.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import { builtinModules } from "node:module";
import { defineConfig } from "tsup";
/**
* tsup bundler config — produces the single-file artefact used inside
* the Claude Desktop `.mcpb` extension bundle.
*
* Why a separate config (and not just reuse `tsup.config.ts`):
*
* - **Inline ALL deps.** A `.mcpb` ships as a self-contained ZIP that
* double-clicks into Claude Desktop without any `npm install` —
* so the bundled JS must carry undici / @modelcontextprotocol/sdk
* / zod / pino / etc. directly. `noExternal: [/.*\u002f]` (`/.*\u002f/`
* matches every import path) overrides tsup's default of
* externalising every dependency.
* - **No splitting.** The runtime expects a single `server/index.js`
* entry inside the bundle (manifest's `entry_point`), and the
* single-file output also keeps the smoke-test in `release.yml`
* trivial (`node server/index.js < initialize.json`).
*
* **Node version gotcha** — `tsup.config.ts` documents that
* `splitting: false` defeats the lazy preflight in `bin.ts::main()`:
* undici 8.x crashes at import-time on Node < 22.19 with the
* cryptic `webidl.util.markAsUncloneable is not a function` instead
* of our clean error message. We mitigate by declaring
* `compatibility.runtimes.node: ">=22.19.0"` in the manifest; Claude
* Desktop refuses to install on older Node. For users who edit
* manifest or bypass that gate, the crash on first launch is the
* trade-off for the simpler smoke-test surface.
* - **No DTS / no sourcemap.** Bundle is consumed by Claude Desktop
* at runtime — types and source maps would just inflate the ZIP.
*
* `external` is limited to Node built-ins (both `fs` and `node:fs`
* forms) — anything else gets inlined.
*
* ## createRequire banner — required for CJS deps
*
* Some bundled deps (pino-pretty, undici, parts of the MCP SDK) use
* CommonJS-style `require()` internally. esbuild's ESM output handles
* static requires fine, but a few do `require(varName)` or
* `require()` calls inside CJS code paths that get inlined verbatim.
* Without a top-level `require` symbol in ESM, those paths throw
* `Fatal: Dynamic require of "X" is not supported` at runtime.
*
* The banner injects `createRequire(import.meta.url)` so any leftover
* `require()` call inside the bundle resolves against the bundle file
* itself — which is enough because all the actually-required modules
* are already inlined into the same file. Verified empirically:
* without the banner the `initialize` smoke test crashes on first
* load; with it, the bundle starts and answers JSON-RPC.
*
* Output: `dist-mcpb/index.js` (single ESM file with shebang).
*/
export default defineConfig({
entry: { index: "src/bin.ts" },
outDir: "dist-mcpb",
format: ["esm"],
target: "node22",
platform: "node",
bundle: true,
noExternal: [/.*/],
external: [...builtinModules, ...builtinModules.map((m) => `node:${m}`)],
dts: false,
sourcemap: false,
minify: false,
splitting: false,
treeshake: true,
clean: true,
banner: {
js: [
"#!/usr/bin/env node",
'import { createRequire as __mcpbCreateRequire } from "node:module";',
"const require = __mcpbCreateRequire(import.meta.url);",
].join("\n"),
},
tsconfig: "./tsconfig.build.json",
});