You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Produce (and then execute) the plan for going live with v2 — making v2 the main branch and the latest npm release, deprecating the v1 line while keeping it alive for security fixes only, and tightening the contribution model to issues-from-external-contributors.
This issue is for writing and agreeing the plan. Each phase below should become its own tracked issue once the plan is signed off; nothing here should be executed ad hoc, since several steps are irreversible (npm publishes, bulk issue closure).
Phase issues
Filed on board #28 under the V2 Go Live column. Execute in order; ⚠️ marks the irreversible ones.
push: (all branches) + release: [published]; publish job asserts release tag == root version, runs pack:verify, then npm publish --access public --provenance
Original ground truth, as verified 2026-07-27 (the state this plan was written against)
Thing
State at planning time
npm @modelcontextprotocol/inspector
latest = 1.0.0; no other dist-tags
origin/main root package.json
version 1.0.0, tag 1.0.0 at branch tip, engines.node >=22.7.5
v2/main root package.json
version 2.0.0 already, engines.node >=22.19.0
v1 publish
publish-all = npm publish --workspaces --access public && npm publish --access public — no --tag
v1 also publishes 3 sub-packages
inspector-client, inspector-server, inspector-cli, all at latest = 1.0.0
v1 workflow triggers
push: branches: [main] + release: [published]
Tag rulesets
none existed
main branch protection
via rulesets targeting ~DEFAULT_BRANCH — so v1/main inherited nothing
Both branches' workflows live at .github/workflows/main.yml, and the release's target commit selects which one runs — that's the mechanism the whole plan leans on.
Proven since: trusted publishing works from a non-default branch (the 1.0.1 publish from v1/main, §4). npm matched on repo + workflow filename + release environment, with no branch or ref pinning — so the workflow_dispatch fallback below is not needed.
The plan as proposed
Branch main → v1/main so the v1 line continues to exist and can conceivably take security fixes.
On v1/main, merge a PR adding a prominent deprecated notice (README + runtime banner) plus the CI-trigger fix (§2), then publish 1.0.1 from v1/main — which doubles as the trusted-publishing rehearsal (§4). Apply the npm-side deprecation handles (§3) after it's live.
PR against main that replaces the entire tree with v2.
Cut release 2.0.0 and publish — after a re-evaluation of the v2 publishing pipeline for confidence.
Close all open issues and PRs against v1 (anything not labeled v2) with a note that v1 is deprecated and takes only required security fixes.
Close off external PRs so only contributors with write access can open them. External contributors file issues; we triage, label the ones with merit, and implement them ourselves to hold the quality gates and architectural integrity.
Update the publishing pipeline so v1 updates can be published from v1/main, avoiding a future emergency branch-swap to ship a v1 security fix.
Opinion — extra steps, and four things that will bite
§1–§4 are blockers or near-blockers for step 7 specifically; §5 onward are sequencing and hygiene.
🔴 1. A v1 security release will silently steal the latest tag
This is the most important finding, and it makes step 7 mandatory, not optional. publish-all has no --tag, so npm assigns latest to whatever it publishes. Once 2.0.0 is out, publishing a 1.0.2 security fix from v1/main would move latest back to 1.0.2 — every npx @modelcontextprotocol/inspector in the world silently reverts to deprecated v1.
Fixes, all needed:
Change v1/main's publish-all to publish under --tag v1-latest (both the root and the three workspace packages). ⚠️Not v1 — npm rejects any dist-tag that parses as a SemVer range, and v1 parses as 1.x (npm error Tag name must not be a valid SemVer range: v1). Using v1 does not publish to the wrong tag; it fails the publish outright, at release time. Corrected in fix: v1 dist-tag must be v1-latest — npm rejects v1 #1829.
Order the go-live publishes so latest lands correctly: publish 1.0.1 first (while latest is still the v1 line), then 2.0.0. If 1.0.1 slips to after 2.0.0, it must go out as --tag v1-latest.
After 2.0.0 is live, add the escape hatch: npm dist-tag add @modelcontextprotocol/inspector@1.0.1 v1-latest, so npm i @modelcontextprotocol/inspector@v1-latest keeps working and the deprecation message has somewhere to point. Applied to all four names, not just the root — the sub-packages' latest is equally stranded, so @v1-latest should resolve consistently whichever name someone installed.
🔴 2. v1/main gets no CI at all, so step 7 is half-done
v1's workflow triggers on push: branches: [main]. The moment v1/main exists, pushes to it match nothing — no build, no tests, no gate on the branch we're reserving for security fixes. Step 7 must also add v1/main to that trigger (and to the pull_request gate), otherwise a security fix would be published from an unvalidated branch.
Whether v1 can be published from v1/mainat all under trusted publishing is a separate and more serious question — see §4.
🔴 3. The three v1 sub-packages need deprecating too
v1 publishes inspector-client, inspector-server, and inspector-cli alongside the root package. v2 publishes only the root package as a single tarball — so those three become permanently orphaned at 1.0.0 with a live latest tag. Step 2's npm deprecation must cover all four names, or people keep installing sub-packages that will never be updated again.
On the npm deprecation mechanics: use a version range, not the whole package —
npm deprecate "@modelcontextprotocol/inspector@<2.0.0" "v1 is deprecated; upgrade to v2 (npm i @modelcontextprotocol/inspector@latest). v1 receives security fixes only, published under the 'v1' tag."
npm deprecate pkg@"*" would warn on v2 installs too. For the three sub-packages the range is <2.0.0 as well. ⚠️ An earlier draft said <=1.0.0, written when 1.0.0 was their newest version — phase 2 published 1.0.1 to all three, so <=1.0.0 would leave the version latest resolves to (the one everyone installs) un-deprecated. <2.0.0 is safe for them since no 2.x will ever exist: v2 ships only the root package. Note deprecation is retroactive-but-mutable — it can be lifted with an empty message, so it's the one reversible step here.
🟡 4. Can v1 publish from v1/main at all, given trusted publishing? — probably yes, but prove it
The concern raised: we use npm trusted publishing (OIDC), and the belief is that a publish must come from main.ymlon the default branch. If that were true, step 7 would be impossible as written and a v1 security fix really would require swapping v1/main into main — the exact scenario step 7 exists to prevent.
Per npm's documentation, the trusted publisher config does not pin a branch or ref. The fields are:
Field
Required
Our value
Organization or user
✅
modelcontextprotocol
Repository
✅
inspector
Workflow filename (filename only, not a path)
✅
main.yml
Environment name
optional
release
Allowed actions
✅
npm publish
There is no branch or ref field — authorization is repo + workflow filename (+ environment), and npm's own documented example is triggered from a tag push rather than a branch. So a release cut from a v1/main commit, running v1/main's .github/workflows/main.yml under environment: release, should satisfy the same trusted publisher, because both branches use the identical workflow filename main.yml. That coincidence is what makes step 7 viable at all.
Two consequences worth pulling out:
Verify this empirically — do not ship on my reading of the docs. npm explicitly does not validate a trusted publisher config when you save it, and every field is a case-sensitive exact match, so a mismatch surfaces only as a failed publish. The 1.0.1 publish is the rehearsal — cut it from v1/main (plan step 2) rather than burning a throwaway 1.0.1-rc.0. Same evidence, one fewer publish; see the rehearsal notes below.
It cuts the other way, security-wise. Because npm matches on filename alone, v1/main's main.yml is already authorized to publish the root package name — including to latest. Anyone who can push to v1/main can publish the v2 package. That's a concrete argument for protecting v1/main with the same rules as main (§10), not leaving it as an unguarded parking branch.
Fallback if the rehearsal fails — a dispatchable publish job on the default branch. This satisfies the "must run from main.yml on the default branch" constraint literally, and is worth considering even if the rehearsal passes, since it centralizes the release button on one branch:
# on main (the default branch), in main.ymlon:
workflow_dispatch:
inputs:
ref: { description: 'Ref to build and publish', default: 'v1/main' }npm_tag: { description: 'dist-tag', default: 'v1-latest' }
…with the job checking out inputs.ref, building it, and running npm publish --tag ${{ inputs.npm_tag }}. The OIDC identity then comes from main.yml on main — unambiguously matching the trusted publisher — while the published code comes from v1/main.
Keep the publish job inline in main.yml: npm validates the calling workflow's filename, so factoring it out into a reusable workflow_call workflow breaks OIDC (and fails looking like a config mismatch).
One caveat, only if we take this fallback route: --provenance builds its attestation from the running workflow's OIDC claims (which reference main), while the packed tree came from a different ref. Confirm npm accepts that rather than rejecting it as a source mismatch. If it doesn't, decide consciously whether a v1 security patch ships without provenance (acceptable) or via the branch-swap of last resort (much worse) — and write that decision down. This does not apply to the 1.0.1 rehearsal: there the workflow runs from the same ref it publishes.
Rehearsing via the real 1.0.1 publish
Verified against origin/main's workflow: v1 already uses OIDC (id-token: write, environment: release, no NPM_TOKEN) with NPM_CONFIG_PROVENANCE: "true", and publish is if: github.event_name == 'release' with needs: build — build has no branch condition, so a release cut from a v1/main commit runs the whole workflow and reaches publish. Five things to get right:
Settings → Environments → release — verified 2026-07-27: Deployment branches and tags is "No restriction". So a 1.0.1 tag on v1/main reaches the publish job; this is not a blocker. (If it had been restricted to selected refs, the job would have been blocked before npm was ever contacted, failing in a way that looks nothing like an OIDC rejection.) If required reviewers are configured, the run pauses for approval rather than failing — we have someone with approval rights, so that's a pause, not a risk. Note the flip side: with no restriction, v1/main's main.yml can publish the root package name to latest from the moment the branch exists, which is why §10's branch protection on v1/main is a requirement rather than hygiene.
Do not add --tag v1-latest yet. 1.0.1 must take latest, which is the whole point of shipping the deprecation notice to npx users. Commit the --tag v1-latest change to publish-allimmediately after the release, as its own commit — that gap is exactly where a forgotten flag later steals latest back from 2.0.0 (§1).
Bump all four package.jsons to 1.0.1, or the check-version step fails the build.
Partial-publish hazard:publish-all is npm publish --workspaces && npm publish. If the workspaces succeed and the root fails, 1.0.1 lands for the three sub-packages with the root still at 1.0.0. Recoverable (bump to 1.0.2, go again), not clean.
What this doesn't prove: the --tag v1-latest publish path, and a publish in the post-2.0.0 world where latest is genuinely at risk. The first real v1 security fix is still the first true exercise of that.
Also confirm npm ≥ 11.5.1 is in use (v1's workflow already has a step for this, since Node 22 bundles npm 10.x).
5. How to actually do the tree replacement (step 3)
main and v2/main have diverged enormously; a plain git merge is an unresolvable conflict field. I'd recommend a true merge that takes v2's tree wholesale, not a reset:
git checkout -b chore/v2-golive main
git merge --no-commit -s ours v2/main # record v2/main as a parent, keep no tree yet
git rm -rf .&& git checkout v2/main -- .# tree becomes exactly v2/main's
git commit
This preserves main's history (no force-push to the default branch, PR review trail intact) and makes v2/main a merge parent — so main is a true superset and v2/main can afterwards be fast-forwarded to main or retired cleanly. A git reset --hard v2/main + force-push would look simpler but discards main's history and requires disabling branch protection on the default branch. Verify after merge: git diff main v2/main is empty.
6. Re-validating the publishing pipeline before 2.0.0 (step 4)
The strongest available confidence check, and it's already built: npm run pack:verify builds, packs, installs the real tarball into a clean throwaway consumer, and drives the installed bin (--help, a real --cli tools/list over stdio, a prod --web boot from the shipped dist). The publish job already runs it as a gate.
Beyond that, I'd add one thing the repo can't self-test: publish a 2.0.0-rc.1 under --tag next to the real registry and install it via npx @modelcontextprotocol/inspector@next on a clean machine. That is the only way to validate the parts that only exist against live npm — provenance/OIDC minting, the files allowlist as npm actually packs it, the postinstall cascade's early-exit under a real dependency install, and the Docker job. A wasted prerelease version number is cheap next to a broken 2.0.0.
Also confirm before cutting: the release tag is v2.0.0 or 2.0.0 (the assert tolerates the leading v) targeting the post-merge main commit, and the Dockerlatest tag moves to 2.0.0 while a 1-pinned tag is preserved for v1 users.
7. Rollback plan (there needs to be one in writing)
npm unpublish is effectively unavailable (72h window, and disallowed once others depend on it). So the rollback for a broken 2.0.0 is dist-tag surgery, not unpublish: npm dist-tag add @modelcontextprotocol/inspector@1.0.1 latest, then npm deprecate the bad 2.0.0, then ship 2.0.1. Write this down before publishing, and make sure whoever cuts the release has the npm rights to do it.
8. Bulk-closing v1 issues and PRs (step 5) — do it auditably
Script it, but add a label (e.g. closed-v1-deprecated) rather than only a comment, so the set stays findable later. A comment alone is very hard to query.
Don't blanket-close by "not labeled v2" without a review pass: some open v1 issues may describe real bugs that still exist in v2, or security items we actually want. Triage into port to v2 (relabel v2, add to board Add tab and approval flow for server -> client sampling #28) vs. close as deprecated before running the script.
Expect GitHub secondary rate limits on a few hundred closures — throttle, don't hammer.
Consider whether to lock those threads. I'd say no — leave comments open so someone can say "this still reproduces in v2."
9. Restricting PRs to write-access contributors (step 6)
Settings → Features → Pull requests → "Collaborators only". The PR tab stays visible and anyone can read and comment, but only users with write access can open new ones. Issues are unaffected — exactly the model step 6 describes. One click, reversible.
Before relying on it, confirm what "collaborator" resolves to here — it means write/maintain/admin on the repo, which may include org-wide base write, not just the maintainer team:
gh api repos/modelcontextprotocol/inspector/collaborators --jq '.[] | select(.permissions.push) | .login'
The setting gives the contributor no explanation — they just find no "Create pull request" button. So CONTRIBUTORS.md (issue #1517) and issue templates still need to land, and land first.
Also decide the security-report path explicitly while we're here: SECURITY.md should carry a supported-versions table (v2 = supported, v1 = security fixes only, everything below 1.0.0 = unsupported) and private reporting should be enabled.
Branch protection / rulesets on the new v1/main (protect it, or the "it can take security fixes" story is fiction), and tag protection for 1.x tags.
Closes #N starts working. Per AGENTS.md, closing keywords only auto-close on the default branch — v2 PRs targeting v2/main never auto-closed. After the swap, PRs into maindo auto-close their issues. Update AGENTS.md, and stop the manual close-and-move-to-Done ritual.
Docs churn: AGENTS.md and every README reference v2/main as the base branch and describe main as legacy. All of that inverts. The board conventions section needs a full pass.
Dependabot / CodeQL / any workflow or external integration pinned to a branch name.
External permalinks: links into blob/main/... will silently resolve to the v2 tree. Anything that must keep pointing at v1 should be re-pinned to the 1.0.1 tag or v1/main.
Node engine bump (>=22.7.5 → >=22.19.0) is a real, if mild, breaking change for 2.0.0 release notes.
Sequencing / freeze: freeze merges to v2/main during the swap, and pick a low-traffic window. Create v1/main → deprecation notice + CI-trigger fix on it → publish 1.0.1 from v1/main (the rehearsal) → add --tag v1-latest to publish-all → merge v2 tree to main → verify CI green on main → publish 2.0.0 → then the cleanup steps (5, 6, 7), which are all reversible.
Announcement: GitHub release notes, the MCP community channels, and the docs site landing together.
Deliverable
An agreed, written go-live runbook (ordered, with the irreversible steps flagged and a rollback line for each), broken into per-phase tracked issues on board #28.
Summary
Produce (and then execute) the plan for going live with v2 — making v2 the
mainbranch and thelatestnpm release, deprecating the v1 line while keeping it alive for security fixes only, and tightening the contribution model to issues-from-external-contributors.This issue is for writing and agreeing the plan. Each phase below should become its own tracked issue once the plan is signed off; nothing here should be executed ad hoc, since several steps are irreversible (npm publishes, bulk issue closure).
Phase issues
Filed on board #28 under the V2 Go Live column. Execute in order;⚠️ marks the irreversible ones.
v1/main, protect it, give it CI (§2, §10)v1/main— the OIDC rehearsal (§4)--tag v1-latestonpublish-all,v1-latestdist-tag,npm deprecateall four names (§1, §3)main's tree with v2 (§5)nextfirst, rollback plan written first (§6, §7)CONTRIBUTORS.md(#1517),SECURITY.md(§9)This umbrella issue stays open until all nine are Done.
Ground truth (re-verified 2026-07-28, after phases 1–3)
@modelcontextprotocol/inspectorlatest= 1.0.1,v1-latest= 1.0.1-client,-server,-cli)latest= 1.0.1,v1-latest= 1.0.1@<2.0.0origin/mainrootpackage.jsonengines.node >=22.7.5,publish-allstill has no--tagv1/mainrootpackage.jsonpublish-allpinned--tag v1-latestv2/mainrootpackage.jsonengines.node >=22.19.0v1/mainbranch1.0.1at tip, ruleset-protected, CI greenv1/main - full protection(branch) +v1 release tags(tag,refs/tags/1.*, deletion+update) — both activev1/main)push: branches: [main, v1/main]+ barepull_request:+release: [published]main)push: branches: [main]+pull_request:+release: [published]push:(all branches) +release: [published]; publish job asserts release tag == root version, runspack:verify, thennpm publish --access public --provenanceOriginal ground truth, as verified 2026-07-27 (the state this plan was written against)
@modelcontextprotocol/inspectorlatest= 1.0.0; no other dist-tagsorigin/mainrootpackage.json1.0.0at branch tip,engines.node >=22.7.5v2/mainrootpackage.jsonengines.node >=22.19.0publish-all=npm publish --workspaces --access public && npm publish --access public— no--taginspector-client,inspector-server,inspector-cli, all atlatest= 1.0.0push: branches: [main]+release: [published]mainbranch protection~DEFAULT_BRANCH— sov1/maininherited nothingBoth branches' workflows live at
.github/workflows/main.yml, and the release's target commit selects which one runs — that's the mechanism the whole plan leans on.Proven since: trusted publishing works from a non-default branch (the 1.0.1 publish from
v1/main, §4). npm matched on repo + workflow filename +releaseenvironment, with no branch or ref pinning — so theworkflow_dispatchfallback below is not needed.The plan as proposed
main→v1/mainso the v1 line continues to exist and can conceivably take security fixes.v1/main, merge a PR adding a prominent deprecated notice (README + runtime banner) plus the CI-trigger fix (§2), then publish 1.0.1 fromv1/main— which doubles as the trusted-publishing rehearsal (§4). Apply the npm-side deprecation handles (§3) after it's live.mainthat replaces the entire tree with v2.v2) with a note that v1 is deprecated and takes only required security fixes.v1/main, avoiding a future emergency branch-swap to ship a v1 security fix.Opinion — extra steps, and four things that will bite
§1–§4 are blockers or near-blockers for step 7 specifically; §5 onward are sequencing and hygiene.
🔴 1. A v1 security release will silently steal the
latesttagThis is the most important finding, and it makes step 7 mandatory, not optional.
publish-allhas no--tag, so npm assignslatestto whatever it publishes. Once 2.0.0 is out, publishing a 1.0.2 security fix fromv1/mainwould movelatestback to 1.0.2 — everynpx @modelcontextprotocol/inspectorin the world silently reverts to deprecated v1.Fixes, all needed:
v1/main'spublish-allto publish under--tag v1-latest(both the root and the three workspace packages).v1— npm rejects any dist-tag that parses as a SemVer range, andv1parses as1.x(npm error Tag name must not be a valid SemVer range: v1). Usingv1does not publish to the wrong tag; it fails the publish outright, at release time. Corrected in fix: v1 dist-tag must bev1-latest— npm rejectsv1#1829.latestlands correctly: publish 1.0.1 first (whilelatestis still the v1 line), then 2.0.0. If 1.0.1 slips to after 2.0.0, it must go out as--tag v1-latest.npm dist-tag add @modelcontextprotocol/inspector@1.0.1 v1-latest, sonpm i @modelcontextprotocol/inspector@v1-latestkeeps working and the deprecation message has somewhere to point. Applied to all four names, not just the root — the sub-packages'latestis equally stranded, so@v1-latestshould resolve consistently whichever name someone installed.🔴 2.
v1/maingets no CI at all, so step 7 is half-donev1's workflow triggers on
push: branches: [main]. The momentv1/mainexists, pushes to it match nothing — no build, no tests, no gate on the branch we're reserving for security fixes. Step 7 must also addv1/mainto that trigger (and to thepull_requestgate), otherwise a security fix would be published from an unvalidated branch.Whether v1 can be published from
v1/mainat all under trusted publishing is a separate and more serious question — see §4.🔴 3. The three v1 sub-packages need deprecating too
v1 publishes
inspector-client,inspector-server, andinspector-clialongside the root package. v2 publishes only the root package as a single tarball — so those three become permanently orphaned at 1.0.0 with a livelatesttag. Step 2's npm deprecation must cover all four names, or people keep installing sub-packages that will never be updated again.On the npm deprecation mechanics: use a version range, not the whole package —
npm deprecate pkg@"*"would warn on v2 installs too. For the three sub-packages the range is<2.0.0as well.<=1.0.0, written when 1.0.0 was their newest version — phase 2 published 1.0.1 to all three, so<=1.0.0would leave the versionlatestresolves to (the one everyone installs) un-deprecated.<2.0.0is safe for them since no 2.x will ever exist: v2 ships only the root package. Note deprecation is retroactive-but-mutable — it can be lifted with an empty message, so it's the one reversible step here.🟡 4. Can v1 publish from
v1/mainat all, given trusted publishing? — probably yes, but prove itThe concern raised: we use npm trusted publishing (OIDC), and the belief is that a publish must come from
main.ymlon the default branch. If that were true, step 7 would be impossible as written and a v1 security fix really would require swappingv1/mainintomain— the exact scenario step 7 exists to prevent.Per npm's documentation, the trusted publisher config does not pin a branch or ref. The fields are:
modelcontextprotocolinspectormain.ymlreleasenpm publishThere is no branch or ref field — authorization is repo + workflow filename (+ environment), and npm's own documented example is triggered from a tag push rather than a branch. So a release cut from a
v1/maincommit, runningv1/main's.github/workflows/main.ymlunderenvironment: release, should satisfy the same trusted publisher, because both branches use the identical workflow filenamemain.yml. That coincidence is what makes step 7 viable at all.Two consequences worth pulling out:
v1/main(plan step 2) rather than burning a throwaway1.0.1-rc.0. Same evidence, one fewer publish; see the rehearsal notes below.v1/main'smain.ymlis already authorized to publish the root package name — including tolatest. Anyone who can push tov1/maincan publish the v2 package. That's a concrete argument for protectingv1/mainwith the same rules asmain(§10), not leaving it as an unguarded parking branch.Fallback if the rehearsal fails — a dispatchable publish job on the default branch. This satisfies the "must run from
main.ymlon the default branch" constraint literally, and is worth considering even if the rehearsal passes, since it centralizes the release button on one branch:…with the job checking out
inputs.ref, building it, and runningnpm publish --tag ${{ inputs.npm_tag }}. The OIDC identity then comes frommain.ymlonmain— unambiguously matching the trusted publisher — while the published code comes fromv1/main.Keep the publish job inline in
main.yml: npm validates the calling workflow's filename, so factoring it out into a reusableworkflow_callworkflow breaks OIDC (and fails looking like a config mismatch).One caveat, only if we take this fallback route:
--provenancebuilds its attestation from the running workflow's OIDC claims (which referencemain), while the packed tree came from a different ref. Confirm npm accepts that rather than rejecting it as a source mismatch. If it doesn't, decide consciously whether a v1 security patch ships without provenance (acceptable) or via the branch-swap of last resort (much worse) — and write that decision down. This does not apply to the 1.0.1 rehearsal: there the workflow runs from the same ref it publishes.Rehearsing via the real 1.0.1 publish
Verified against
origin/main's workflow: v1 already uses OIDC (id-token: write,environment: release, noNPM_TOKEN) withNPM_CONFIG_PROVENANCE: "true", andpublishisif: github.event_name == 'release'withneeds: build—buildhas no branch condition, so a release cut from av1/maincommit runs the whole workflow and reaches publish. Five things to get right:Settings → Environments → release— verified 2026-07-27: Deployment branches and tags is "No restriction". So a1.0.1tag onv1/mainreaches the publish job; this is not a blocker. (If it had been restricted to selected refs, the job would have been blocked before npm was ever contacted, failing in a way that looks nothing like an OIDC rejection.) If required reviewers are configured, the run pauses for approval rather than failing — we have someone with approval rights, so that's a pause, not a risk. Note the flip side: with no restriction,v1/main'smain.ymlcan publish the root package name tolatestfrom the moment the branch exists, which is why §10's branch protection onv1/mainis a requirement rather than hygiene.--tag v1-latestyet. 1.0.1 must takelatest, which is the whole point of shipping the deprecation notice tonpxusers. Commit the--tag v1-latestchange topublish-allimmediately after the release, as its own commit — that gap is exactly where a forgotten flag later stealslatestback from 2.0.0 (§1).package.jsons to 1.0.1, or thecheck-versionstep fails the build.publish-allisnpm publish --workspaces && npm publish. If the workspaces succeed and the root fails, 1.0.1 lands for the three sub-packages with the root still at 1.0.0. Recoverable (bump to 1.0.2, go again), not clean.--tag v1-latestpublish path, and a publish in the post-2.0.0 world wherelatestis genuinely at risk. The first real v1 security fix is still the first true exercise of that.Also confirm npm ≥ 11.5.1 is in use (v1's workflow already has a step for this, since Node 22 bundles npm 10.x).
Sources: npm trusted publishers · npm trusted publishing GA changelog
5. How to actually do the tree replacement (step 3)
mainandv2/mainhave diverged enormously; a plaingit mergeis an unresolvable conflict field. I'd recommend a true merge that takes v2's tree wholesale, not a reset:This preserves
main's history (no force-push to the default branch, PR review trail intact) and makesv2/maina merge parent — somainis a true superset andv2/maincan afterwards be fast-forwarded tomainor retired cleanly. Agit reset --hard v2/main+ force-push would look simpler but discards main's history and requires disabling branch protection on the default branch. Verify after merge:git diff main v2/mainis empty.6. Re-validating the publishing pipeline before 2.0.0 (step 4)
The strongest available confidence check, and it's already built:
npm run pack:verifybuilds, packs, installs the real tarball into a clean throwaway consumer, and drives the installed bin (--help, a real--cli tools/listover stdio, a prod--webboot from the shippeddist). The publish job already runs it as a gate.Beyond that, I'd add one thing the repo can't self-test: publish a
2.0.0-rc.1under--tag nextto the real registry and install it vianpx @modelcontextprotocol/inspector@nexton a clean machine. That is the only way to validate the parts that only exist against live npm — provenance/OIDC minting, thefilesallowlist as npm actually packs it, the postinstall cascade's early-exit under a real dependency install, and the Docker job. A wasted prerelease version number is cheap next to a broken 2.0.0.Also confirm before cutting: the release tag is
v2.0.0or2.0.0(the assert tolerates the leadingv) targeting the post-mergemaincommit, and the Dockerlatesttag moves to 2.0.0 while a1-pinned tag is preserved for v1 users.7. Rollback plan (there needs to be one in writing)
npm unpublish is effectively unavailable (72h window, and disallowed once others depend on it). So the rollback for a broken 2.0.0 is dist-tag surgery, not unpublish:
npm dist-tag add @modelcontextprotocol/inspector@1.0.1 latest, thennpm deprecatethe bad 2.0.0, then ship 2.0.1. Write this down before publishing, and make sure whoever cuts the release has the npm rights to do it.8. Bulk-closing v1 issues and PRs (step 5) — do it auditably
closed-v1-deprecated) rather than only a comment, so the set stays findable later. A comment alone is very hard to query.v2" without a review pass: some open v1 issues may describe real bugs that still exist in v2, or security items we actually want. Triage into port to v2 (relabelv2, add to board Add tab and approval flow for server -> client sampling #28) vs. close as deprecated before running the script.9. Restricting PRs to write-access contributors (step 6)
Settings → Features → Pull requests → "Collaborators only". The PR tab stays visible and anyone can read and comment, but only users with write access can open new ones. Issues are unaffected — exactly the model step 6 describes. One click, reversible.
Before relying on it, confirm what "collaborator" resolves to here — it means write/maintain/admin on the repo, which may include org-wide base write, not just the maintainer team:
gh api repos/modelcontextprotocol/inspector/collaborators --jq '.[] | select(.permissions.push) | .login'The setting gives the contributor no explanation — they just find no "Create pull request" button. So
CONTRIBUTORS.md(issue #1517) and issue templates still need to land, and land first.Docs · changelog
Also decide the security-report path explicitly while we're here:
SECURITY.mdshould carry a supported-versions table (v2 = supported, v1 = security fixes only, everything below 1.0.0 = unsupported) and private reporting should be enabled.10. Things easy to forget in the swap
v1/main(protect it, or the "it can take security fixes" story is fiction), and tag protection for1.xtags.Closes #Nstarts working. Per AGENTS.md, closing keywords only auto-close on the default branch — v2 PRs targetingv2/mainnever auto-closed. After the swap, PRs intomaindo auto-close their issues. Update AGENTS.md, and stop the manual close-and-move-to-Done ritual.v2/mainas the base branch and describemainas legacy. All of that inverts. The board conventions section needs a full pass.v2label is noise — decide whether to retire it or invert to av1label. Archive project get tools working #11; Add tab and approval flow for server -> client sampling #28 becomes the only board.blob/main/...will silently resolve to the v2 tree. Anything that must keep pointing at v1 should be re-pinned to the1.0.1tag orv1/main.--configsemantics are genuinely different in v2 (read-only session file, with--catalogas the writable one). Without this, the deprecation notice sends people somewhere confusing. Pairs with the docs-site rewrite in docs: new modelcontextprotocol.io Inspector documentation for v2 (legacy vs. modern era, more screenshots) #1803.v2/mainduring the swap, and pick a low-traffic window. Createv1/main→ deprecation notice + CI-trigger fix on it → publish 1.0.1 fromv1/main(the rehearsal) → add--tag v1-latesttopublish-all→ merge v2 tree tomain→ verify CI green onmain→ publish 2.0.0 → then the cleanup steps (5, 6, 7), which are all reversible.Deliverable
An agreed, written go-live runbook (ordered, with the irreversible steps flagged and a rollback line for each), broken into per-phase tracked issues on board #28.