bun is a monorepo tool — lockfile lives at workspace root
(_ts_packages/bun.lock), not per-subpackage. Placeholder at
_ts_packages/packages/mcp-server/bun.lock was the wrong path.
Changes:
- Generated real _ts_packages/bun.lock (626 lines) via 'bun install'
(bun 1.3.13, auto-migrated from package-lock.json)
- .github/workflows/release.yml working-directory:
_ts_packages/packages/mcp-server → _ts_packages (workspace root)
- BUILD.md Lockfile section rewritten to document workspace-root
location + coexistence with package-lock.json (L2 audit finding
partially resolved — full consolidation deferred to v0.20)
release.yml build-mcp-binary job now has real lockfile to consume —
H4 'tag build fails on missing lockfile' gate still active but now
there's something actually committed to satisfy it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.6 KiB
Building a single-binary kei-mcp-server
KeiSeiKit v0.18 Phase 1 (exobrain) — ship the MCP server as a portable static binary so any machine without Node can run it off a USB drive.
Tooling
Compile via bun (bun build --compile). Bundles the Bun runtime + JS
into one static executable — no Node, no node_modules/ at runtime.
Requires bun >= 1.0. Docs (target list + flags):
[VERIFIED: https://bun.sh/docs/bundler/executables]
Supported targets
| Platform | Arch | --target= |
Output name |
|---|---|---|---|
| Linux | x64 | bun-linux-x64 |
kei-mcp-server-linux-x64 |
| Linux | arm64 | bun-linux-arm64 |
kei-mcp-server-linux-arm64 |
| macOS | x64 | bun-darwin-x64 |
kei-mcp-server-darwin-x64 |
| macOS | arm64 | bun-darwin-arm64 |
kei-mcp-server-darwin-arm64 |
| Windows | x64 | bun-windows-x64 |
kei-mcp-server-windows-x64.exe |
Local build
cd _ts_packages/packages/mcp-server
bun install
bun run build:native # host-native
bun run build:native:darwin-arm64 # explicit cross-target
Output lands in dist/. Size ~85–95 MB per binary (bundled runtime).
Release build (CI)
.github/workflows/release.yml → job build-mcp-binary runs the 5-target
matrix on tag push (v*) and attaches binaries + .sha256 sums to the
GitHub release. Runtime requirement: none (static).
Troubleshooting
- macOS Gatekeeper (“cannot be opened because Apple cannot check it for
malicious software”) — remove the quarantine attribute:
xattr -d com.apple.quarantine ./kei-mcp-server-darwin-arm64 - Windows SmartScreen / AV flags — not signed; right-click → Properties → Unblock, or add an AV exclusion for the binary path.
- Missing symbol at startup — usually a native-only dep that resolved
at runtime on Node but cannot be bundled. Re-run
bun install, thenbun build --compile ... --smolto surface the resolution error. .jsESM imports fail — the mcp-server source imports via.jssuffix (ESM canonical). Bun resolves these from the sibling.tsfile automatically; notscpre-step needed.
Lockfile
Since v0.19.1 the _ts_packages workspace ships a single bun.lock
at the workspace root (_ts_packages/bun.lock). Bun is a monorepo
tool — one lockfile covers all packages/* including mcp-server.
The release workflow runs bun install --frozen-lockfile from the
workspace root with NO fallback — a missing or out-of-date lockfile
fails the build on purpose. This is H4 supply-chain defense: every
release builds against the exact dependency tree recorded in the
committed lockfile, not whatever the npm registry serves that day.
Before every release tag:
cd _ts_packagesbun install(regeneratesbun.lockif anypackages/*/package.jsonchanged)- Commit
bun.lockif it changed - Tag the release
If you see the build fail with "lockfile out of sync" on a tag push:
you pushed the tag before committing an updated bun.lock. Fix:
generate the lockfile locally, commit, re-tag.
Coexistence with package-lock.json: the top-level CI (ci.yml)
still uses npm ci against package-lock.json for TypeScript tests.
Both lockfiles are committed. Audit finding L2 (dual-lockfile skew
risk) is tracked for a future v0.20 consolidation — pick one tool
across CI + release. Until then, re-run bun install AND
npm install whenever any packages/*/package.json changes.