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
docs: describe the release-triggered publishing flow in RELEASING.md
Documents the merged state of the Phase 2 pipeline: changesets/semver
for TypeScript, CalVer prepare-release for Python, publishing triggered
by manually-created GitHub Releases, and the registry-diff self-healing
behavior (#4472).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: RELEASING.md
+28-16Lines changed: 28 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,22 +1,38 @@
1
1
# Releasing
2
2
3
-
How the packages in this repository are published, and what to do when a publish fails.
3
+
How the packages in this repository are versioned and published, and what to do when a publish fails.
4
+
5
+
## How versioning works
6
+
7
+
**No workflow ever computes or stamps a version at release time.** The version in each package's manifest on `main` is the source of truth, and versions only change through reviewed PRs:
8
+
9
+
-**TypeScript servers** (`everything`, `filesystem`, `memory`, `sequentialthinking`) use **semver, managed by [changesets](https://github.com/changesets/changesets)**. Feature PRs include a changeset file (see [CONTRIBUTING.md](CONTRIBUTING.md)); merged changesets accumulate in a rolling **"Version Packages" PR** (maintained by [`version-packages.yml`](.github/workflows/version-packages.yml) on every push to `main`), which applies version bumps and CHANGELOG entries when merged. Semver policy: **patch** = bug fixes; **minor** = new tools, prompts, resources, or options; **major** = breaking changes (tool removed or renamed, schema change that breaks clients, protocol or Node floor bump).
10
+
-**Python servers** (`fetch`, `git`, `time`) use **CalVer** (e.g. `2026.8.1`). A maintainer dispatches the **Prepare Python Release** workflow ([`prepare-release.yml`](.github/workflows/prepare-release.yml)), which stamps today's date onto each Python package that changed since its last version bump and opens a normal PR.
11
+
12
+
> [!NOTE]
13
+
> PRs opened by these workflows use the workflow token, which doesn't trigger CI. Close and reopen the PR to run CI before merging.
4
14
5
15
## How publishing works
6
16
7
-
All packages publish exclusively from the [`release.yml`](.github/workflows/release.yml) GitHub Actions workflow, gated by the `release` environment (a required reviewer must approve each deployment). Releases are triggered deliberately by a maintainer via **workflow_dispatch** (Actions → Release → Run workflow, or `gh workflow run release.yml`) — there is no scheduled/automatic release.
17
+
Publishing is triggered by a maintainer **creating a GitHub Release** (Releases → Draft a new release → choose or create a tag on `main` → auto-generate notes → publish). The release tag is just a label — it carries no version semantics.
18
+
19
+
The [`release.yml`](.github/workflows/release.yml) workflow runs on `release: published`, gated by the `release` environment (a required reviewer must approve each deployment). It runs every package as an independent matrix job (`fail-fast: false` — one package's failure never blocks another). Each job: checkout at the release tag → registry-diff guard → install → **run the package's tests** (plus `pyright` for Python) → build → publish.
20
+
21
+
The **registry-diff guard** makes releases idempotent and self-healing: a package whose version already exists on the registry is **skipped, not failed** (npm: an explicit version check, where a never-published package counts as "publish it"; PyPI: `skip-existing` on the upload action). A package whose publish failed or was skipped simply publishes on the next release.
8
22
9
23
**Authentication is OIDC trusted publishing on both registries — there are no registry tokens.**
10
24
11
-
-**npm** (TypeScript servers): each `@modelcontextprotocol/*` package is registered on npmjs.com with a [trusted publisher](https://docs.npmjs.com/trusted-publishers) bound to this repository, workflow filename `release.yml`, and environment `release` (the binding is case-sensitive). Packages publish with [provenance attestations](https://docs.npmjs.com/generating-provenance-statements).
12
-
-**PyPI** (Python servers): published via [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) using `pypa/gh-action-pypi-publish`.
25
+
-**npm**: each `@modelcontextprotocol/*` package is registered on npmjs.com with a [trusted publisher](https://docs.npmjs.com/trusted-publishers) bound to this repository, workflow filename `release.yml`, and environment `release` (the binding is case-sensitive). Packages publish with [provenance attestations](https://docs.npmjs.com/generating-provenance-statements).
26
+
-**PyPI**: published via [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) using `pypa/gh-action-pypi-publish`, with the same `release.yml` + `release` environment binding.
27
+
28
+
Because of those bindings, the publish jobs must stay in `release.yml` and keep the `release` environment.
13
29
14
-
A release run:
30
+
## Cutting a release
15
31
16
-
1.**Detects changed packages** since the last release tag — a package counts as changed if any `.py`, `.ts`, or `.md` file in its directory changed (READMEs ship inside the published artifacts).
17
-
2.**Stamps versions and pushes the release tag** — versions are date-based (CalVer, e.g. `2026.7.4`).
18
-
3.**Publishes each changed package as an independent matrix job** (`fail-fast: false` — one package's failure never blocks another). Each job: checkout at the release tag → install → double-publish guard → **run the package's tests** (plus `pyright` for Python) → build → publish. The guard differs by registry: the npm job aborts before tests if the version already exists; for PyPI the skip happens at the publish step itself (`skip-existing` on the upload action).
19
-
4.**Creates the GitHub release** with generated notes.
32
+
1.Make sure the version bumps you want to ship are on `main`: merge the **Version Packages** PR (TypeScript) and/or the **Prepare Python Release** PR (Python). CI validates these like any other PR.
33
+
2.Create a GitHub Release on the releases page with auto-generated notes. Any tag name works (it's a label, not a version); dating them (e.g. `release-2026-08-01`) keeps the list readable.
34
+
3.Approve the `release` environment deployments when prompted.
35
+
4.Each package publishes if its version isn't on the registry yet; already-published packages skip cleanly.
- A re-run is still a `release.yml` run in the `release` environment, so it satisfies the trusted-publisher binding.
32
-
- It re-runs only the failed legs, checked out at the original release tag — it publishes exactly the tagged code, and the double-publish guard keeps already-published packages safe.
48
+
- It re-runs only the failed legs, checked out at the original release tag — it publishes exactly the released code, and the registry-diff guard keeps already-published packages safe.
33
49
- It needs a fresh `release` environment approval, and the run must be complete first (approve or reject any pending deployments).
34
50
- GitHub's re-run window is ~30 days from the original run, and re-runs execute the *original* workflow snapshot — workflow fixes on `main` don't apply to a re-run.
35
51
36
-
**Otherwise: let the next release pick it up.**If the re-run window has closed (or the fix required a workflow change), the failed version simply never exists on that registry — that's benign; npm and PyPI version histories don't need to match. The package publishes at the next version, provided it has a qualifying change (`.py`, `.ts`, or `.md`) since the last release tag.
52
+
**Otherwise: just cut the next release.**The registry-diff guard picks up any version that never made it to the registry — no stranded versions, no artificial file touches.
37
53
38
54
**Never:**
39
55
40
56
- Publish manually with an npm token or from a laptop — there are no registry tokens, and manual publishes would break the provenance/trust chain.
41
-
-Dispatch a fresh `release.yml` run expecting it to retry a failed version — versions are date-granular, so a same-day dispatch collides with the existing tag, and a later dispatch mints a *new* version. Neither retries the failed one.
57
+
-Edit versions directly on `main`to force a publish — versions change only through the Version Packages and Prepare Python Release PRs.
42
58
43
59
## Environment approvals
44
60
45
61
The `release` environment's required-reviewer list is configured in the repository settings (Settings → Environments → `release`). Reviewer rights come only from that list — repository admin does not confer deployment approval.
46
-
47
-
---
48
-
49
-
Planned changes to this process — semver via changesets for the TypeScript packages, publishing triggered by manually-created GitHub Releases — are tracked in [#4463](https://github.com/modelcontextprotocol/servers/issues/4463). This document will be updated when that work merges.
0 commit comments