modelcontextprotocol / modelcontextprotocol/servers
v2 release pipeline, Phase 2: semver via changesets (TS), GitHub-Release-triggered publishing
@olaservo is already working on this.
Since Aug 2, 2026.
- Dominant language
- TypeScript
- Stars
- 90.5k
- Forks
- 11.7k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 5
Description
Scope
Phase 2 of the release-pipeline rebuild: move the TypeScript packages to semver managed by changesets, keep CalVer for Python, and replace dispatch-triggered releases with publishing triggered by manually-created GitHub Releases.
Phase 0 (hygiene) and Phase 1 (OIDC trusted publishing, validated in production by release 2026.7.4) are complete — see #4463 for the full history, incident post-mortem, and what already landed in #4466. The pipeline works today (dispatch-only CalVer, documented in RELEASING.md); this issue makes it the pipeline we actually want.
Versioning design
- TypeScript → semver, managed by changesets. Each PR includes a changeset (patch/minor/major + changelog line); the changesets bot flags PRs missing one; a rolling "Version Packages" PR accumulates bumps and writes CHANGELOGs. This prevents the silent-skip hazard (code changed but version forgotten → nothing publishes).
- The semver lines are still open on npm (history stops at
0.6.2);npm install/npxresolve thelatestdist-tag, so publishing below the CalVer numbers is safe for the dominantnpxuse case. - All four servers start at
1.0.0. None has ever carried a true semver value (the CalVer era was date-stamping, andeverything's staged-but-never-published2.0.0on main resets to1.0.0for parity).1.0.0is unpublished for all four — no collisions. npm deprecatethe CalVer ranges (>=2025.0.0 <2027.0.0) with a pointer to the resumed semver line.- Semver policy: patch = fixes; minor = new tools/prompts/resources/options; major = breaking (tool removed/renamed, schema breaks clients, protocol or Node floor bump).
- The semver lines are still open on npm (history stops at
- Python → stays CalVer. pip/uv have no dist-tags and always install the numerically-highest version, so the existing
2026.xline continues cleanly (no PEP 440 epoch needed). A smallprepare-releasedispatch stamps the release date onto Python packages changed since their last published version and opens a normal PR (salvaging that part ofscripts/release.py; the rest retires).
How each server's version gets set (replacing release-time date-stamping)
- One-time reset PR: set
versionto1.0.0in all four TSpackage.jsons (everythingcomes down from its never-published staged2.0.0). From that moment,release.py's version-stamping no longer applies to TS packages. - TypeScript, ongoing: the version in
package.jsononmainis the source of truth and is only ever changed by merging the changesets "Version Packages" PR. A release publishes whatever version is onmainif the registry doesn't have it yet. - Python, ongoing: the version in
pyproject.tomlonmainis the source of truth; the prepare-release dispatch stamps the release date (CalVer) onto changed packages and opens a PR. - No workflow sets a version at release time. The release tag is just a label; it carries no version semantics.
Release flow (mirrors modelcontextprotocol/inspector)
feature PRs (TS PRs carry changesets; CI tests everything)
↓
merge "Version Packages" PR (TS) / prepare-release PR (Py CalVer stamp)
↓
maintainer creates a GitHub Release on the releases page (auto-generated notes)
↓ release: [published] — human-created releases DO trigger workflows
release.yml: test → build → publish per package
guard: version already on registry? → skip (not fail)
npm: OIDC + provenance | PyPI: trusted publishing + skip-existing
Properties:
- No workflow ever tags or computes a version. Versions live on main, reviewed in PRs; the release tag is a human act on tested code. Tag/version mismatch bugs are structurally impossible.
- Test-before-tag comes free: CI validated the bump PRs on main before anyone clicks "Draft a new release". The in-publish-job test gates (from #4466) stay as defense in depth — they test the exact ref being published, covering manual tags and any future path.
- Self-healing: a package whose publish failed (or was skipped) simply publishes on the next release — the registry-diff guard picks it up. No stranded versions, no artificial file touches.
- The
releaseenvironment approval remains as a second gate and satisfies the OIDC trusted-publisher binding (workflowrelease.yml+ environmentrelease— the publish jobs must stay in that file).
Task checklist
- One-time version-reset PR: all four TS servers →
1.0.0 - Adopt changesets for the TS workspaces (config + bot + CONTRIBUTING note)
- Rework
release.yml:release: [published]trigger, all-packages matrix, registry-diff skip guard (npm side; PyPIskip-existinglanded in #4466). ⚠️ The guard must handle a never-published package: the currentnpm view-based check errors (E404) for a package with no registry entry, which would abort a brand-new server's first publish — the redesigned guard should treat "package not found" as "publish it". -
prepare-releasedispatch for Python CalVer stamping; retire the rest ofscripts/release.py -
npm deprecatethe CalVer version ranges on the four TS packages - Verify PyPI trusted-publisher registrations (
mcp-server-fetch,mcp-server-git,mcp-server-time) are bound torelease.yml - Optional: reusable
workflow_calltest definitions to prevent CI/release drift (nice-to-have; no longer load-bearing) - Update
RELEASING.mdwhen this work merges: describe the new flow and drop the interim wording. The doc always describes the merged state, never the plan.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.