The root @modelcontextprotocol/inspector package ships as one tarball with a single version number — no separate -web / -cli / -tui / -core packages. npm run build builds every client, then prepack runs before npm publish. Runtime dependencies are declared on the root package.json; client builds bundle @inspector/core and externalize npm packages resolved from the root install.
The root package.json "files" allowlist is the source of truth for the tarball. A few non-obvious entries exist because they are read at runtime or were silently dropped by npm's packlist — do not remove them without re-running npm run pack:verify:
- No source maps. The client bundlers set
sourcemap: false(clients/{cli,tui}/tsup.config.ts,clients/web/tsup.runner.config.ts); Vite and the launcher'stscalready emit none. Maps are ~half the unpacked size and aren't needed at runtime — debug vianpm run devon the source. clients/web/buildships viaclients/web/.npmignore.clients/web/.gitignorelistsbuild/, and npm's packlist honors that nested.gitignoreover the root"files"allowlist — so the prod web-server runner was silently missing from the tarball whileclients/web/distslipped through (its.gitignoreonly listsdist-ssr).clients/web/.npmignoreoverrides the.gitignorefor publishing so bothbuild/(runner) anddist/(SPA) ship. The other clients don't need this — none ship a nested.gitignore.clients/web/staticships the MCP Apps sandbox proxy.clients/web/static/sandbox_proxy.htmlis a committed source file (not a build artifact), read from disk at runtime byclients/web/server/sandbox-controller.tsas<runner dir>/../static/sandbox_proxy.html. It was missing from the root"files"allowlist entirely, so every published build failed the Apps tab with "Sandbox not loaded" (#1859) while working fine in the repo. Because the path is resolved relative toclients/web/build, the directory must ship at that exact location —pack:verifyasserts the tarball entry, the installed-on-disk path, and (since #2003) that a widget actually loads through it on the installed bin. Presence and reachability are different properties: a rename with a stale reader ships a file that is there and unusable.- A dependency that renders React is bundled, not externalized. An externalized package resolves its own
reactfrom wherever npm placed it in the consumer's tree, which is not necessarily where the bundle resolves ours — npm places a package beside a React satisfying its peer range, and those ranges are looser than ours.ink-formandink-scroll-viewdeclare">=18", so a project holding React 18 satisfies them and gets them hoisted while the Inspector's React 19 nests underneath: two React copies, and the TUI dies withTypeError: Cannot read properties of null (reading 'useState')the moment a tool test form or a scroll view mounts (#1952). Both are therefore inlined byclients/tui/tsup.config.tsand are not root dependencies: the tarball ships their code insideclients/tui/build/index.jsrather than having consumers install them. Bundling also pins their transitive deps to what this repo's install resolved (notablyink-select-input@6viaoverrides, which npm ignores for a package installed as a dependency).inkis the one exception, on cost: bundling it works but adds ~1.4 MB (react-reconcilerandyoga-layoutcome along, plus acreateRequirebanner for the inlined CJS), so it stays external — not because its">=19"peer makes it safe, which it does not. What keeps that tolerable is the rootreactrange:"^19.0.0"is deliberately open to the whole major so npm can dedupe our React with whatever React 19 a consumer pins, leaving an externalinkon the same copy the bundle uses. Narrowing that range reopens the bug for the renderer itself —clients/tui/__tests__/tsupConfig.test.tspins it toink's peer floor, and guards the rest of the split; see the TUI README. - A single version number, read from the root
package.json. The Inspector ships as one package with one version, so only the rootpackage.jsoncarries aversion— the fourclients/*/package.jsons deliberately have none. Every Node client (CLI, TUI, and the web backend) resolves the version through the sharedreadInspectorVersion()reader incore/node/version.ts, which walks up to the root manifest (always present in the tarball). No clientpackage.jsonis read at runtime, so none needs to ship. The web browser can't read the filesystem; it gets its version from the backend viaGET /api/config(see #1639).
The smoke:* scripts run against the in-repo build tree, which is not the published package. npm run pack:verify (scripts/pack-and-verify.mjs) closes that gap: it builds, npm packs the publishable tarball (asserting no source maps ship and that the runtime-required files are present), installs the tarball into a clean throwaway consumer — a fresh temp directory where it runs a real npm install <tgz> (pulls runtime deps, runs postinstall), exactly as npx @modelcontextprotocol/inspector would — and drives the installed mcp-inspector bin end to end: --help dispatch, a real --cli tools/list over stdio, a prod --web boot that must serve / from the shipped dist, and — riding that same boot — an MCP App rendered in headless Chromium through the shipped sandbox proxy, connect → open app → data-app-status="ready" (#2003). That last step shares its flow with smoke:web:app via scripts/lib/mcp-app-flow.mjs rather than copying it, since the deep-link shape is the part that rots silently; the client comes from the install while the App test server stays a repo fixture. It catches "works in --dev, breaks under npx …" path/packaging failures. It requires network access (the install pulls deps), so it is a local / release check, not part of the fast validate loop or of local:gate.
The step-by-step procedure — bump on v2/main first, merge the milestone into
main, tag origin/main with a bare x.y.z, then draft the GitHub Release —
lives in the release skill, so there is
exactly one copy of it. Invoke it as /release, or just read the file.
The short version: publishing is automated by two release-gated jobs in
.github/workflows/main.yml, both
needs: [build, coverage], so a release cannot publish with either the build
job or the coverage gate red.
The container image and everything about running it — ports, volumes, where secrets go — is in Running the Inspector in Docker.