Skip to content

Commit 6d193ed

Browse files
olaservoclaude
andcommitted
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>
1 parent bd22331 commit 6d193ed

1 file changed

Lines changed: 28 additions & 16 deletions

File tree

RELEASING.md

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
11
# Releasing
22

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.
414
515
## How publishing works
616

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.
822

923
**Authentication is OIDC trusted publishing on both registries — there are no registry tokens.**
1024

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.
1329

14-
A release run:
30+
## Cutting a release
1531

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.
2036

2137
## When a publish fails
2238

@@ -29,21 +45,17 @@ gh run rerun <run-id> --failed --repo modelcontextprotocol/servers
2945
```
3046

3147
- 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.
3349
- It needs a fresh `release` environment approval, and the run must be complete first (approve or reject any pending deployments).
3450
- 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.
3551

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.
3753

3854
**Never:**
3955

4056
- 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.
4258

4359
## Environment approvals
4460

4561
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

Comments
 (0)