Fix v2 single-package publish bundle + add pack:verify - #1637
Conversation
Prepare the single `@modelcontextprotocol/inspector` tarball for publishing and add a script that verifies the real published package end to end. - Add `scripts/pack-and-verify.mjs` (`npm run pack:verify`): build, `npm pack`, install the tarball into a clean consumer, and drive the installed `mcp-inspector` bin (web/cli/tui) — catching "works in --dev, breaks under npx" packaging/path failures the in-repo smokes miss. Local/release tool (install needs network), not wired into the fast validate/ci loop. - Disable source maps in the cli/tui/web-runner tsup configs (~halves the unpacked payload; runtime doesn't need them). - Add `clients/web/.npmignore` so `clients/web/build` (prod web-server runner) ships — npm's packlist was honoring the nested `.gitignore` `build/` entry over the root "files" allowlist, dropping the runner while `dist/` slipped through. - Ship `clients/cli/package.json` and `clients/tui/package.json`: both bundles read their own package.json at runtime (CLI client identity, TUI header), so the installed CLI crashed on connect and the installed TUI crashed on launch without them. Document the packaging invariants and pack:verify in the launcher README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
CLAUDE.md included @./README.md, which did not exist. Add a root README covering project layout, setup, the @inspector/core shared package, the web "dumb components" + Storybook approach, test-servers, build, the testing/quality gate, publishing, and how AGENTS.md/CLAUDE.md govern contributions for humans and agents alike. Move the publishing/packaging documentation (single-package tarball, "files" invariants, pack:verify) from the launcher README to the root README, where whole-repo packaging belongs; the launcher README now points there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Added a root The root README covers project layout, setup, the |
The Inspector ships as one package with one version, but four client package.jsons each carried their own `version` and were read at runtime (cli.ts, tui App.tsx), and core hardcoded a third, stale value (0.20.0) that the TUI and web actually reported to servers. Consolidate onto the root package.json as the single source of truth. - Add core/node/version.ts: shared, Node-only readInspectorVersion() that walks up to the root package.json (always shipped in the tarball). 100% covered by a new integration test. - CLI and TUI read the version via the shared reader instead of their own manifest; TUI now also passes a real clientIdentity (was falling through to the stale core default). - Drop the `version` field from all four client package.jsons; the root keeps the only version. TUI header name/description become local display constants. - core default client identity: replace the stale 0.20.0 hardcode with a neutral 0.0.0 placeholder (real clients pass clientIdentity; web will source its version from GET /api/config, see #1639). - Stop shipping clients/cli/package.json and clients/tui/package.json (no longer read at runtime) — supersedes the earlier "ship them" fix; update pack:verify and the root README packaging invariants accordingly. - Add core/node/** to the web coverage include so the new module is gated. - Add the shared-code architecture diagram to the root README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Single-sourced the version from the root What changed:
Verified with |
|
@claude review |
|
Claude finished @cliffhall's task in 2m 17s —— View job Review: PR #1637 — v2 single-package publish bundle +
|
Review follow-up: `cleanupTarball` was always true (set true at init and again in the catch), so the finally guard was dead logic. Make it meaningful — retain the packed tarball on failure so a packaging problem can be inspected, and log where it is; the happy path still removes it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Thanks for the review! Responses to the four observations:
Only #2 was an actual code fix; the rest are intentional/documented. Re-requesting a review to confirm nothing else. |
|
@claude review |
|
Claude finished @cliffhall's task in 1m 31s —— View job Re-review:
|
Re-review caught that the previous fix was inert: fail() calls process.exit(1), which skips finally blocks — so `cleanupTarball = false` never took effect and, worse, the temp work dir (a full node_modules from the real install) leaked on every failed run. Move cleanup into fail() itself — the single failure-exit point, called from everywhere including deep inside the run. On failure it removes the heavy work dir and leaves the packed tarball in place for post-mortem inspection; the success path removes both inline before the OK log. Drop the dead `cleanupTarball` flag entirely. Verified both paths: success removes work dir + tarball (exit 0); a forced failure removes the work dir, retains the tarball, and exits 1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Sharp catch — you're right, my previous fix was inert. Fixed properly in 3dead1f: moved cleanup into Verified both paths this time rather than reasoning about them:
Re-requesting review. @claude review |
|
Claude finished @cliffhall's task in 1m 18s —— View job Re-review:
|
…s-finally)
Re-review found the same process.exit()-skips-finally pattern in verifyWeb: a
failure during the --web phase goes through fail() → process.exit(1), skipping
its `finally { stop() }`, so the spawned web server was orphaned — left on the
port, where a later run could fetch a stale false-200 and mask a regression.
Track the live child in a module `webChild` and have fail() SIGTERM it before
exiting, alongside the workDir cleanup; stop() clears it on the success path.
Verified: a forced web-phase failure exits 1, retains the tarball, and leaves
no listener on the port (child killed, not orphaned).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
Good catch — same Fixed in 9324f9f: track the live Verified: a forced web-phase failure exits 1, retains the tarball, and leaves no listener on the port (child killed, not orphaned) — before the fix that server would have lingered. @claude review |
|
Claude finished @cliffhall's task in 44s —— View job Re-review:
|
Refs #1636 (packaging half). Does not close it: this PR delivers the single-package packaging correctness +
pack:verify; themain.ymlrelease-job wiring is tracked separately in #1640 (lands at thev2/main→maincutover), and the standalonecorepackage remains deferred on #1636. The web version display is #1639.Prepares the single-package v2 publish bundle and adds a script that verifies the real published tarball end to end — catching "works in
--dev, breaks undernpx @modelcontextprotocol/inspector" failures before they ship.Per the decisions on #1636: one
@modelcontextprotocol/inspectorpackage with everything, a single version number, no standalonecorepackage for now, source maps dropped, andclients/web/buildfixed. Actualmain.ymlrelease-job wiring still lands at thev2/main→maincutover; this lands the packaging correctness + local verification now.What changed
scripts/pack-and-verify.mjs(npm run pack:verify) — builds,npm packs the publishable tarball, installs it into a clean throwaway consumer (realnpm install <tgz>, runspostinstall), and drives the installedmcp-inspectorbin:--helpdispatch, a real--clitools/listover stdio, and a prod--webboot that must serve/(HTTP 200, injected token) from the shippeddist. Also asserts no source maps ship and that the runtime-required files are present. Local/release tool (the install needs network), so it is intentionally not part of the fastvalidate/ciloop.clients/{cli,tui,web}tsup configs). Vite and the launchertscalready emit none. This ~halves the unpacked payload.clients/web/.npmignoresoclients/web/build(the prod web-server runner) actually ships. npm's packlist was honoringclients/web/.gitignore'sbuild/entry over the root"files"allowlist, silently dropping the runner whiledist/slipped through.clients/cli/package.jsonandclients/tui/package.json(added to root"files") — both bundles read their ownpackage.jsonat runtime (CLI client identity, TUI header). Without them the installed CLI crashed on connect and the installed TUI crashed on launch, though both worked in-repo.Two real publish-only bugs this caught
pack:verifysurfaced both while being written — each worked in every existing smoke (which run against the in-repo tree) but crashed from an installed package:App.tsxread../../package.json(→clients/tui/package.json) →ENOENTon launch.cli.tsimported../package.json(→clients/cli/package.json) →ERR_MODULE_NOT_FOUNDon connect (--helpworked;tools/listdidn't).Result
Publishable tarball: 26 files, ~1.5 MB compressed / 4.44 MB unpacked (was 6.6 MB), no source maps,
clients/web/{build,dist}+ both clientpackage.jsons present.npm run pack:verifypasses: install-clean + web/cli/tui driven end to end from the installed bin.Test plan
npm run pack:verify— green end to end.npm run ci— validate + coverage gate + smokes + Storybook.🤖 Generated with Claude Code