Skip to content

ci: derive the npm dist-tag from the version instead of defaulting to latest - #1834

Merged
cliffhall merged 1 commit into
mainfrom
ci/publish-dist-tag
Jul 28, 2026
Merged

ci: derive the npm dist-tag from the version instead of defaulting to latest#1834
cliffhall merged 1 commit into
mainfrom
ci/publish-dist-tag

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Blocking prerequisite for #1818. Found while preparing the RC step, which could not have worked as written.

The bug

npm publish defaults to --tag latest regardless of semver prerelease status, and the publish step passed no --tag:

run: npm publish --access public --provenance

So cutting the 2.0.0-rc.1 release that #1818 calls for would have published the release candidate to latest — every npx @modelcontextprotocol/inspector in the world would resolve to an RC.

That is the same class of failure #1816 fixed for v1 (a missing --tag silently claiming latest), except here it would have been triggered deliberately, by following the runbook.

There was also simply no mechanism to pass a dist-tag through a release-triggered publish, so the RC step was not achievable at all before this.

The fix

VERSION="$(node -p "require('./package.json').version")"
case "$VERSION" in
  *-*) NPM_TAG=next ;;
  *)   NPM_TAG=latest ;;
esac
npm publish --access public --provenance --tag "$NPM_TAG"

A hyphen after the patch component is a prerelease per semver; build metadata uses + and correctly is not. Verified against real inputs:

Version Tag
2.0.0 latest
2.0.1 latest
10.20.30 latest
2.0.0-rc.1 next
2.0.0-beta.3 next
2.0.0+build.5 latest

Why shell and not semver

semver resolves in this repo only as a transitive dependency. Relying on it in CI means a publish step that breaks whenever some unrelated package drops it from its tree — not a dependency worth taking for a one-line predicate.

Notes

The step also logs the chosen tag before publishing, so the release run shows which dist-tag was claimed rather than leaving it to be inferred afterwards.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD

… latest

`npm publish` defaults to `--tag latest` regardless of semver prerelease
status, and the publish step passed no `--tag`. So cutting the `2.0.0-rc.1`
release that #1818 calls for would have published the release candidate to
`latest` — pointing every `npx @modelcontextprotocol/inspector` at an RC. That
is the same class of failure #1816 fixed for v1, except triggered deliberately
by following the runbook.

Derives the tag from the version: a hyphen after the patch component means a
prerelease (`2.0.0-rc.1` → `next`), anything else is a release (`2.0.0` →
`latest`). Build metadata (`2.0.0+build.5`) is correctly not a prerelease.

Implemented as a shell `case` rather than with `semver`, which is only a
transitive dependency here and should not be relied on in CI.

Also makes the RC step of #1818 possible at all — there was previously no way
to pass a dist-tag through a release-triggered publish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Jul 28, 2026
@cliffhall
cliffhall merged commit 1450029 into main Jul 28, 2026
6 checks passed
@cliffhall
cliffhall deleted the ci/publish-dist-tag branch July 28, 2026 04:16
@cliffhall cliffhall mentioned this pull request Jul 28, 2026
cliffhall added a commit that referenced this pull request Jul 28, 2026
Release candidate for the 2.0.0 go-live (#1818, §6). Publishes under the `next`
dist-tag via the derivation added in #1834 — verified: `2.0.0-rc.1` contains a
hyphen after the patch component, so it resolves to `next`, leaving `latest` on
1.0.1 untouched.

The RC exists to exercise what only exists against the live registry: the
`next`-tag publish path itself (newly written in #1834), provenance/OIDC
minting, the `files` allowlist as npm actually packs it, and the postinstall
cascade's early-exit under a real dependency install on a clean machine.


Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cliffhall added a commit that referenced this pull request Jul 28, 2026
…TOKEN (#1836)

* fix(ci): publish via OIDC trusted publishing, not a non-existent NPM_TOKEN

The 2.0.0-rc.1 publish failed with `npm error code ENEEDAUTH`. Two causes, both
in the v2 publish job, which had never actually executed before — every prior
release was cut from the v1 workflow.

1. `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` pointed at a secret that does
   not exist (the repo has no secrets at repo or environment level — publishing
   moved to OIDC trusted publishing). `setup-node` therefore wrote an *empty*
   `_authToken` into its generated `.npmrc`, and npm failed ENEEDAUTH before
   OIDC was ever attempted. Removed, with a comment saying why it must not be
   "restored".

2. The job never upgraded the npm CLI. Trusted publishing requires npm
   >= 11.5.1 and Node 22 bundles 10.x, so this would have failed even with the
   token line gone. The v1 workflow has this step; v2 was missing it.

The RC did its job: it caught both against the real registry, and consumed
nothing — the run failed before `npm publish` wrote anything, so no version was
burned and the dist-tags are untouched.

Also confirms #1834 works: the log shows `Publishing 2.0.0-rc.1 under dist-tag
'next'` before the auth failure.

README corrected — it documented `NPM_TOKEN` as part of the publish setup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD

* docs: restructure the test-servers section into navigable subsections

The section had grown into eight undifferentiated paragraphs, the longest 1,965
characters, each describing one or two showcase configs. The content is good
reference material — the problem was purely that you could not find anything in
it or tell where one config's description ended and the next began.

Restructured, not rewritten. No facts removed:

- A summary table of all nine showcase configs, with what each demonstrates and
  its tracking issue, so the set is visible at a glance.
- One `####` subsection per config family, so each is linkable and skimmable.
- Enumerations that were buried in prose are now tables: the six MRTR presets,
  the four `trigger_*` spec-error tools and their status/code pairs.
- Step sequences (advertised extensions) and era contrasts (logging,
  subscriptions, tasks) are now bullets rather than run-on sentences.
- Two caveats that were parenthetical asides — browser `Mcp-Param-*` skipping,
  and `collect_elicitation` erroring on the modern leg — are now blockquotes,
  since both are things you hit and then have to go hunting for.

Longest line drops 1,965 → 620 characters. Verified every previously-documented
config and all technical identifiers survive.

Noted while auditing: `demo.json`, `oauth-step-up-demo.json`,
`url-elicitation-form.json` and `xaa-ema-http.json` exist in
`test-servers/configs/` but have never been documented here. Left alone —
that is a pre-existing gap, not a regression from this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cliffhall cliffhall mentioned this pull request Jul 28, 2026
cliffhall added a commit that referenced this pull request Jul 28, 2026
The v2 go-live release (#1818, phase 5 of #1804).

This is byte-identical to 2.0.0-rc.3 apart from dropping the prerelease suffix,
which is what makes rc.3's validation meaningful: the tree that ships is the
tree that was validated against the live registry.

`2.0.0` has no hyphen, so the derivation added in #1834 resolves it to `latest`
— this is the release that takes the tag from 1.0.1.

Validated across three release candidates:

- rc.1 caught two publish-path defects invisible to every local check —
  `NODE_AUTH_TOKEN` pointing at a non-existent secret (shadowing OIDC trusted
  publishing) and a missing npm CLI upgrade (trusted publishing needs
  >= 11.5.1; Node 22 bundles 10.x). Both fixed in #1836.
- rc.2 validated the dependency sweep (#1837) that cleared every prod-scope
  advisory using lockfile-only changes.
- rc.3 validated the vite 8.1.5 bump (#1841) closing three high-severity
  dev-server file-read advisories.

Each RC was verified with a cold `node:22` container install driving the
published tarball end to end — install, `--help`, and a real `--cli tools/list`
against a live stdio server. `latest` remained on 1.0.1 throughout, proving the
`--tag` derivation.

Zero open Dependabot alerts at time of release.


Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cliffhall added a commit that referenced this pull request Aug 2, 2026
…#1904)

main's copy of .github/workflows/main.yml carried three release fixes
v2/main never took -- #1831 (least-privilege default GITHUB_TOKEN
scope), #1834 (derive the npm dist-tag from the version) and #1836
(publish via npm OIDC trusted publishing).

Diffing the file both directions shows v2/main has NOTHING main lacks:
its only unique content is the superseded publish step,

    run: npm publish --access public --provenance
    env:
      NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

which is exactly what #1836 replaced. Every other line already matched.
So this is a verbatim copy of main's file rather than a hunk-by-hunk
forward-port -- fewer ways to get it wrong, and it leaves the two
branches byte-identical (`git diff origin/main -- .github/workflows`
is now empty).

Not a back-merge: only this one file is taken, so none of main's
pre-swap v1 lineage enters v2/main's ancestry (see #1868).


Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
pull Bot pushed a commit to Bang2985/inspector that referenced this pull request Aug 5, 2026
Resolves the eight conflicts from the first v2/main -> main milestone
merge on a branch cut from main, so v2/main's ancestry stays clean --
resolving them on the PR head would have back-merged main into v2/main,
which is exactly what modelcontextprotocol#1868 was closed to avoid.

All eight took v2/main's side, each verifiably the newer content:

- README.md          stale Mcp-Param-* callout; v2/main's is corrected
                     by modelcontextprotocol#1847 (the Inspector builds the headers itself)
- SECURITY.md        add/add (modelcontextprotocol#1843 on main, modelcontextprotocol#1867 on v2/main); differ
                     by one line, the CONTRIBUTORS.md -> CONTRIBUTING.md
                     rename from modelcontextprotocol#1884
- clients/web/package.json   vitest ^4.1.0 -> ^4.1.10, the modelcontextprotocol#1899 fix
- the five package-lock.json files, then regenerated from a clean
  install rather than hand-merged

main's release-workflow commits (modelcontextprotocol#1831 least-privilege token, modelcontextprotocol#1834
dist-tag derivation, modelcontextprotocol#1836 OIDC trusted publishing) auto-merged and are
preserved -- .github/workflows/main.yml is the only file where the
merged tree differs from v2/main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant