Skip to main content

release

Cut an Inspector v2 release — two PRs and then a GitHub Release. PR 1 puts the npm audit, any fixes it forces, and the version bump on v2/main; PR 2 merges v2/main into main and is smoke-tested from the production build with a ledger artifact for the maintainers; the maintainer then drafts the Release with a script and publishes it. Also covers the v1 line and what the publish jobs gate on.

Quellinformationen

Repository
modelcontextprotocol/inspector
Letzte Quellaktivität
5. Oktober 2026 um 11:38
Erkannte Sprache von SKILL.md
Englisch
Sterne
11.055
Forks
1.543

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
release
description
Cut an Inspector v2 release — two PRs and then a GitHub Release. PR 1 puts the npm audit, any fixes it forces, and the version bump on v2/main; PR 2 merges v2/main into main and is smoke-tested from the production build with a ledger artifact for the maintainers; the maintainer then drafts the Release with a script and publishes it. Also covers the v1 line and what the publish jobs gate on.
disable-model-invocation
true
# Cutting a release Background on what ships and why (the `files` allowlist, the bundling rules, the container image) is in [`docs/publishing.md`](../../../docs/publishing.md). This skill is the procedure. A v2 release is cut from **`main`**, after the milestone's work has been merged there from `v2/main` — not from `v2/main` itself. The v1 line releases independently from `v1/main` to the `v1-latest` tag and never touches `main`. Publishing is automated by release-gated jobs in `.github/workflows/main.yml` (`github.event_name == 'release'`), all downstream of `needs: [build, coverage]` — so a release cannot publish with either the build job or the coverage gate red: - **`package`** — asserts the release tag matches the root `package.json` version, installs, runs `npm run pack:verify` as the pre-publish gate, then `npm pack`s the tarball and uploads it as an artifact. It holds **no** `id-token`. - **`publish`** — `needs: [package]`; holds `id-token: write` and only downloads that tarball and runs `npm publish <tgz> --access public --provenance`. No checkout, no dependency install, no build — the split keeps install scripts away from the OIDC publish token (#2483). - **`publish-github-container-registry`** — the GHCR image. ## The shape: two PRs, then the Release There is **one version number** (only the root `package.json` has one — the clients carry none), and the release moves through **two pull requests** in order. They are not interchangeable and neither one's content belongs on the other. | | PR 1 — prep | PR 2 — the milestone merge | | --- | --- | --- | | Branch | `v2/chore/<ISSUE>-bump-<X-Y-Z>`, cut from `origin/v2/main` | the milestone-merge branch, cut from `origin/main` | | Base | **`v2/main`** | **`main`** | | Carries | the `npm audit` report, **any fixes the audit forces**, and the **version bump** — all three, one PR | the milestone's work, arriving whole from `v2/main`. **No commits of its own.** | | Verified by | `npm run local:gate` | `npm run local:gate`, **plus** `npm run pack:verify` (not a gate stage), **plus** a hand-driven smoke of every contribution in the milestone, from the **production build**, written up as a **ledger artifact** | | Merged when | reviewed and green | the ledger is reviewed by the maintainers and clean | Then, and only then, a maintainer tags and publishes the **GitHub Release** (step 3), which is what triggers the publish jobs. ⚠️ **Do not fold the two together.** The bump must exist on `v2/main` before the merge (see [Why the bump goes on `v2/main` first](#why-the-bump-goes-on-v2main-first-2010)), and PR 2 must stay a pure merge — a commit authored on the merge branch is a change that exists downstream of `v2/main` and nothing carries it back. ## 1. PR 1 — audit, audit fixes and the bump, on `v2/main` All three are part of the milestone's work, so all three belong on the develop branch and flow into `main` together, **in the same PR** — audit first, so the bump sits on top of a tree you have just checked, and so a reviewer sees the report and the fixes it forced as one change. ```sh # Branch from the REMOTE ref, and read the version only once you are on it. # A default clone has just `main` checked out, so a local `v2/main` may not # exist and `package.json` here is `main`'s — the released version, not the one # you are bumping from (Copilot). git fetch origin v2/main git checkout -b v2/chore/<ISSUE>-bump-<X-Y-Z> origin/v2/main # Audit every install that has its own lockfile — root and each client. # REPORT ONLY. Read the output; do not let npm mutate the tree (see below). npm audit --audit-level=high for c in web cli tui launcher; do (cd "clients/$c" && npm audit --audit-level=high); done node -p "require('./package.json').version" # what is on v2/main now npm version minor --no-git-tag-version # or major / patch; bump only, no tag node -p "require('./package.json').version" # confirm, then PR → v2/main ``` Anything it reports is fixed **deliberately** — a direct bump, or an `overrides` entry — and each fix is its own commit, gated by `npm run local:gate` before the version bump goes on top. ⚠️ **Do not run `npm audit fix`, with or without `--force`.** [Dependency placement](../../../AGENTS.md#dependency-placement) rules it out, and the reason is not `--force`: plain `audit fix` resolves an advisory that has no *upward* escape inside a declared range by silently **downgrading**. That is not hypothetical here — `tsup@8.5.1` declares `esbuild: ^0.27.0` against an advisory covering `0.27.3 - 0.28.0`, and `audit fix` walked three installs back to `0.27.2` (~700 lines of lockfile churn for a low-severity dev-only advisory; tried and reverted in #2058, written up in the `local-dev` skill). `local:gate` does not detect a version regression, so nothing downstream would have caught it. `--force` is worse again — it applies fixes *outside* the declared range, trading a known vulnerability for an unvetted major. So the release step is the **report**, and the judgment stays with a person. Where `audit` names something with no in-range fix, pin it with `overrides`; where it needs a major, that is its own issue and its own PR, not a release-day edit. If something can't be resolved before the release ships, say so in the release notes and leave it to the alert-driven pipeline (#2229) rather than forcing it here. This step is a **backstop, not a substitute** for #2229's alert-driven issues — those are what surface a transitive vulnerability well before a release is cut, tracked and fixed as their own PRs. This exists so a release is never gated on remembering to check `npm audit` separately. The branch name carries the version you are bumping **to**, so it is named after that second reading. If you want it before branching: `git show origin/v2/main:package.json | node -p "JSON.parse(require('fs').readFileSync(0)).version"`. ⚠️ **Never copy a version out of this file.** It would be a version that has already shipped by the time you read it, and following it would cut a release branch named for the wrong release (Copilot). ⚠️ **`--no-git-tag-version` is load-bearing.** A bare `npm version` also tags, and the tag would land on a `v2/main` commit — but the release must be cut from `main`, so the tag has to point at the merge commit there (step 3). Tagging here creates a tag on a commit that is never released. **PR 1 merges before PR 2 is opened.** The merge branch is cut from `main` and takes `v2/main` whole, so opening it early means merging a `v2/main` that does not yet carry the bump. ## 2. PR 2 — merge `v2/main` → `main`, smoke-test it, and write the ledger Through the usual milestone-merge branch. It now carries the bump, so the release lands on `main` with the version already correct. Between steps 1 and 2 the two branches **do** differ, and that is expected, not drift: `v2/main` reads the version being built while `main` still reads the one currently released. What this ordering removes is *post-release* drift — once the milestone merge lands they agree again, and `v2/main` is never left **behind** `main`. If you see `v2/main` ahead of `main`, a release is in flight; if you see it behind, something went wrong. ### 2a. Smoke-test the release candidate from the production build The merge branch's tree **is** the release candidate. Check that rather than assume it — the merge commit's tree and `origin/v2/main`'s must be identical: ```sh git rev-parse origin/v2/main^{tree} git rev-parse <merge-commit>^{tree} # must print the same hash ``` Then drive it. Work from a **dedicated worktree** with its own full `npm install` (a symlinked `node_modules` passes lint and tests and then fails every story file), run `npm run local:gate` there, and exercise the app from the **production build** — the packaged bin and the built bundles, not `vite dev`. The `local-dev`, `test-servers` and `pre-push-gate` skills cover the mechanics. **Then run `npm run pack:verify` there as its own step.** It is what proves the tarball a consumer installs actually resolves, and ⚠️ **`local:gate` does not run it** — `local:gate:stages` has no packaging stage, and a green gate says nothing about the published tarball (#2380). CI runs it only in the `package` job, which fires on the published GitHub Release — after the tag exists — so skipping it here means the first signal of a broken package arrives too late to stop the release. It needs network access; record its result (tarball size and the `pack:verify OK` line) for the ledger below. **Every contribution closed in the milestone gets driven, not read.** The bar is observed behavior from the running app — a rendered panel, a status attribute, a server's own stderr — against a real test server, through whichever clients the change touches (web, CLI, TUI). "Its tests pass" is not evidence for this step; the gate already said that. For a change with no observable surface, the evidence is the thing that holds it — a probe that makes the guard fire, a counted before/after, a resolved binary path. ### 2b. The ledger artifact Write the results up as a **published artifact** for the maintainers to review, and link it from PR 2. Shape it like the [v2.5.0 ledger](https://claude.ai/code/artifact/6f25d292-3623-419f-af7f-26aba57247ef): - **Masthead** — repo, PR number and merge commit, version, date; and a standfirst saying what tree was tested and that its hash matches `origin/v2/main`, plus whether the milestone payload is complete (the only issue left open should be the merge itself). - **Verdict band** — `local:gate` and `pack:verify` results, milestone issues verified as `N / N`, distinct test count, regressions found. - **The automated gate** — one cell per `local:gate` stage with its number (file counts, test counts, smoke count), and a note on what is new this milestone. - **The packaging check** — `pack:verify` in its own cell, apart from the gate stages because it is not one of them: its result and the tarball size. - **One section per theme**, each a table of *Issue · What was driven · Observed · Status*. One row per closed issue, issue-linked, with the actual output in the Observed cell. - **Notes / findings** — anything that is a caveat rather than a pass, called out rather than folded into a row. A row that says "verified" without saying what was run is not a ledger entry. ### 2c. When the smoke finds something **The fix goes on `v2/main`, never on the merge branch.** File the issue, fix it through an ordinary PR against `v2/main`, then merge `v2/main` into the merge branch again so the fix arrives the same way everything else did. That keeps the merge tree byte-identical to `origin/v2/main` — which is both the invariant checked in 2a and the reason a finding here does not create a commit that only exists downstream (#2000 → #2092; #2215 → #2216–2224). Re-run the affected part of the smoke afterwards and update the ledger; it is the artifact the maintainers approve the merge on. ## 3. Tag and publish the Release ### 3a. Assemble the notes and draft the Release A release's notes have four parts, in this order: 1. **What's Changed.** GitHub's generated list of every PR since the previous tag. 2. **The smoke-ledger line**, linking the artifact from 2b. 3. **`## Known issue`** (or `issues`), only when there is one. It names the issue, who is affected and the workaround. Deciding what counts as a known issue is a maintainer judgment, so it is written by hand and passed in, never generated. 4. **`## Thanks for helping us improve`.** Credit to the community members whose issues the release addresses. GitHub adds everyone `@`-mentioned in a release body to that release's **Contributors** avatar strip, so the people credited here appear there too (confirmed on 2.9.0). **`npm run release:notes` assembles all four and creates the Release** (`scripts/release-notes.mjs`, #2550). It derives the version from `origin/main` after an explicit fetch, picks the previous **stable** tag itself (this repo also carries `-rc.N`, `-hotfix`, `-amended` and `v2-alpha-1` tags, any of which would drop changes from the list), and asks the same `releases/generate-notes` API the UI's *Generate release notes* button uses. Run it twice, then publish in 3b: ```sh ARGS=(--merge-branch "v2/chore/milestone-merge-vX.Y.Z" --ledger-url "https://claude.ai/artifact/<id>") # Add one --known-issue "<markdown paragraph>" per known issue, if any. npm run release:notes -- "${ARGS[@]}" # 1. preview: prints the notes, creates nothing npm run release:notes -- "${ARGS[@]}" --draft # 2. creates the Release as a DRAFT ``` Read the preview, then create the draft and read it again on the Releases page. The preview prints the notes on stdout and its progress on stderr, so `> notes.md` captures just the notes. The rules the helper applies: - **An issue counts when a listed PR closes it**, through either the manual closing link (`closingIssuesReferences`, followed across every page) or a closing keyword (`close`/`fix`/`resolve` in any tense, optional colon) before a bare `#N` in the PR body. So an issue older than the release still counts when this release closed it. A cross-repo `owner/repo#N` does not count. - **Maintainers and bots are excluded by permission, not by name.** A maintainer is anyone with `admin`, `maintain` or `write` on the repo. Bot authors are dropped, which covers the issues the SDK-watch and Dependabot sweeps file. On a public repo, anyone without a role reads as `read`, so they are credited. - **One line per person, most issues first:** `* @user (#1, #2, …)`. - **"Addresses", not "fixes."** The credited issues include feature requests. - **The section is left out** when no community reporter remains, as with 2.1.0. - ⚠️ **Any API failure aborts the whole run.** A failed permission lookup never reads as "community", which could credit a maintainer, and a rate-limited PR or issue lookup is never skipped. A partial Thanks list is never published, so on a rate limit, wait for the reset and run it again. To regenerate an **older** release's notes (to check them, or after editing a PR body), pass `--version x.y.z`, and optionally `--previous-tag`. This works for preview only. `--draft` and `--publish` refuse `--previous-tag`, and any version other than the one on `origin/main`, because the Release would carry the wrong range of changes or attach to the wrong tree. Previewing `--version 2.9.0` with 2.9.0's ledger and known issue reproduces its published notes exactly. ### 3b. Publish the Release **Publishing is the deliberate maintainer action.** It fires `package` → `publish` (npm) and `publish-github-container-registry`, so the helper never does it implicitly. Publish the draft from 3a on the Releases page (*Edit → Publish release*, with **Set as the latest release** checked), or from the CLI: ```sh git fetch origin main # FETCH_HEAD, not origin/main: the tracking ref can lag (see release-tag.mjs) VERSION=$(git show FETCH_HEAD:package.json | node -p "JSON.parse(require('fs').readFileSync(0)).version") gh release edit "$VERSION" --repo modelcontextprotocol/inspector --draft=false --latest ``` A draft has no tag yet. GitHub creates the bare `x.y.z` tag **on publish**, at `main`'s head at that moment, so publish only while `main` is still the merge commit PR 2 landed. `npm run release:notes -- "${ARGS[@]}" --publish` creates and publishes in one step, for when the notes were already reviewed in a preview. The helper passes the bare `$VERSION` as the tag and `--target main`, so the tag name and the target are right by construction. If the tag has to exist before the Release (for example, to pin the commit before drafting), push it first with `scripts/release-tag.mjs` (#2558). It derives the tag from the version that just landed rather than taking one as input, since a hard-coded tag is either already taken (so `git tag` aborts) or, worse, wrong. A Release created afterwards attaches to that existing tag: ```sh npm run release:tag # dry run: prints what would be tagged npm run release:tag -- --push # tags origin/main's SHA and pushes the tag # then 3a and 3b; the Release attaches to this tag ``` ⚠️ **It tags `origin/main`, not your local `HEAD`.** `git checkout main && git pull` resolves through whatever merge-or-rebase strategy you have configured, so a divergent local `main` can quietly produce or replay local commits. Tagging `HEAD` there tags a commit that is not on `origin/main`, and `git push origin <tag>` pushes only the tag — leaving a release whose commit was never published. The script resolves the SHA from `origin/main` after an explicit fetch; the UI path avoids this by construction, since the target is `main` itself. ⚠️ **No `v` prefix.** This repo's release tags are bare `x.y.z` — which is why the script tags `$VERSION` and not `v$VERSION`, and why the tag typed into the UI carries no prefix either. npm's own `tag-version-prefix` defaults to `v` and the repo sets no `.npmrc`, so a bare `npm version` would have produced a mismatched tag. (The workflow's assert step strips a leading `v` before comparing, so a `v`-prefixed tag would still publish — it would just be inconsistent with every previous release.) The release's target commit selects which workflow runs, so this only publishes when a release is cut from a commit carrying the v2 workflow. **Editing a published Release's notes is safe.** Every tag's `main.yml` triggers only on `release: types: [published]` (checked for every tag from 2.0.0 through 2.9.0), so an `edited` event never re-runs publishing. Fixing a typo or adding a known issue after the fact needs no ceremony. ### 3c. If the release run fails
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen