Skip to content

Commit 7097923

Browse files
authored
ci(release): switch npm publishing to OIDC trusted publishing (#4466)
ci(release): 4463 / Phase 1 - Switch npm publishing to OIDC trusted publishing
2 parents b2a94a2 + 5850690 commit 7097923

10 files changed

Lines changed: 103 additions & 69 deletions

File tree

.github/workflows/python.yml

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ name: Python
22

33
on:
44
push:
5-
branches:
6-
- main
75
pull_request:
8-
release:
9-
types: [published]
6+
7+
# Superseded runs on the same ref are cancelled (e.g. rapid amend-pushes),
8+
# except on main, where every merge keeps its own CI run
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
1012

1113
jobs:
1214
detect-packages:
@@ -95,27 +97,3 @@ jobs:
9597
with:
9698
name: dist-${{ matrix.package }}
9799
path: src/${{ matrix.package }}/dist/
98-
99-
publish:
100-
runs-on: ubuntu-latest
101-
needs: [build, detect-packages]
102-
if: github.event_name == 'release'
103-
104-
strategy:
105-
matrix:
106-
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
107-
name: Publish ${{ matrix.package }}
108-
109-
environment: release
110-
permissions:
111-
id-token: write # Required for trusted publishing
112-
113-
steps:
114-
- name: Download artifacts
115-
uses: actions/download-artifact@v7
116-
with:
117-
name: dist-${{ matrix.package }}
118-
path: dist/
119-
120-
- name: Publish package to PyPI
121-
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/release.yml

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
name: Automatic Release Creation
1+
name: Release
22

33
on:
44
workflow_dispatch:
5-
schedule:
6-
- cron: '0 10 * * *'
75

86
jobs:
97
create-metadata:
@@ -138,6 +136,15 @@ jobs:
138136
working-directory: src/${{ matrix.package }}
139137
run: uv run --frozen pyright
140138

139+
- name: Run tests
140+
working-directory: src/${{ matrix.package }}
141+
run: |
142+
if [ -d "tests" ] || [ -d "test" ]; then
143+
uv run --frozen pytest
144+
else
145+
echo "No tests directory, skipping"
146+
fi
147+
141148
- name: Build package
142149
working-directory: src/${{ matrix.package }}
143150
run: uv build
@@ -146,6 +153,7 @@ jobs:
146153
uses: pypa/gh-action-pypi-publish@release/v1
147154
with:
148155
packages-dir: src/${{ matrix.package }}/dist
156+
skip-existing: true # re-runs tolerate already-uploaded files
149157

150158
publish-npm:
151159
needs: [update-packages, create-metadata]
@@ -156,6 +164,9 @@ jobs:
156164
package: ${{ fromJson(needs.create-metadata.outputs.npm_packages) }}
157165
name: Build ${{ matrix.package }}
158166
environment: release
167+
permissions:
168+
contents: read
169+
id-token: write # Required for npm trusted publishing (OIDC)
159170
runs-on: ubuntu-latest
160171
steps:
161172
- uses: actions/checkout@v6
@@ -168,6 +179,10 @@ jobs:
168179
cache: npm
169180
registry-url: 'https://registry.npmjs.org'
170181

182+
# OIDC trusted publishing requires npm >=11.5.1; Node 22's bundled npm is 10.x.
183+
- name: Ensure npm CLI supports OIDC trusted publishing
184+
run: npm install -g npm@^11.5.1
185+
171186
- name: Install dependencies
172187
working-directory: src/${{ matrix.package }}
173188
run: npm ci
@@ -182,16 +197,22 @@ jobs:
182197
fi
183198
echo "Version $VERSION is new, proceeding with publish"
184199
200+
- name: Run tests
201+
working-directory: src/${{ matrix.package }}
202+
run: npm test --if-present
203+
185204
- name: Build package
186205
working-directory: src/${{ matrix.package }}
187206
run: npm run build
188207

208+
# Authenticates via OIDC trusted publishing (no token) — each package's
209+
# npm trusted publisher is bound to this workflow + the release environment
189210
- name: Publish package
190211
working-directory: src/${{ matrix.package }}
191212
run: |
192213
npm publish --access public
193214
env:
194-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
215+
NPM_CONFIG_PROVENANCE: "true"
195216

196217
create-release:
197218
needs: [update-packages, create-metadata, publish-pypi, publish-npm]

.github/workflows/typescript.yml

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ name: TypeScript
22

33
on:
44
push:
5-
branches:
6-
- main
75
pull_request:
8-
release:
9-
types: [published]
6+
7+
# Superseded runs on the same ref are cancelled (e.g. rapid amend-pushes),
8+
# except on main, where every merge keeps its own CI run
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
1012

1113
jobs:
1214
detect-packages:
@@ -68,35 +70,3 @@ jobs:
6870
working-directory: src/${{ matrix.package }}
6971
run: npm run build
7072

71-
publish:
72-
runs-on: ubuntu-latest
73-
needs: [build, detect-packages]
74-
if: github.event_name == 'release'
75-
environment: release
76-
77-
strategy:
78-
matrix:
79-
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
80-
name: Publish ${{ matrix.package }}
81-
82-
permissions:
83-
contents: read
84-
id-token: write
85-
86-
steps:
87-
- uses: actions/checkout@v6
88-
- uses: actions/setup-node@v6
89-
with:
90-
node-version: 22
91-
cache: npm
92-
registry-url: "https://registry.npmjs.org"
93-
94-
- name: Install dependencies
95-
working-directory: src/${{ matrix.package }}
96-
run: npm ci
97-
98-
- name: Publish package
99-
working-directory: src/${{ matrix.package }}
100-
run: npm publish --access public
101-
env:
102-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ See [ADDITIONAL.md](ADDITIONAL.md) for a curated list of frameworks and resource
145145

146146
See [CONTRIBUTING.md](CONTRIBUTING.md) for information about contributing to this repository.
147147

148+
## 📦 Releasing
149+
150+
See [RELEASING.md](RELEASING.md) for how packages are published (OIDC trusted publishing from CI — no registry tokens) and how to retry a failed publish.
151+
148152
## 🔒 Security
149153

150154
See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities.

RELEASING.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Releasing
2+
3+
How the packages in this repository are published, and what to do when a publish fails.
4+
5+
## How publishing works
6+
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.
8+
9+
**Authentication is OIDC trusted publishing on both registries — there are no registry tokens.**
10+
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`.
13+
14+
A release run:
15+
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.
20+
21+
## When a publish fails
22+
23+
A failed matrix leg means that one package didn't publish; everything that succeeded stays published.
24+
25+
**Preferred: re-run the failed jobs on the same run.**
26+
27+
```bash
28+
gh run rerun <run-id> --failed --repo modelcontextprotocol/servers
29+
```
30+
31+
- 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.
33+
- It needs a fresh `release` environment approval, and the run must be complete first (approve or reject any pending deployments).
34+
- 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+
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.
37+
38+
**Never:**
39+
40+
- 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.
42+
43+
## Environment approvals
44+
45+
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.

scripts/release.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ def has_changes(path: Path, git_hash: GitHash) -> bool:
113113
)
114114

115115
changed_files = [Path(f) for f in output.stdout.splitlines()]
116-
relevant_files = [f for f in changed_files if f.suffix in [".py", ".ts"]]
116+
# .md counts as a change: READMEs ship inside the published package
117+
# (npm tarball / PyPI long_description)
118+
relevant_files = [f for f in changed_files if f.suffix in [".py", ".ts", ".md"]]
117119
return len(relevant_files) >= 1
118120
except subprocess.CalledProcessError:
119121
return False

src/everything/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,7 @@ npx @modelcontextprotocol/server-everything sse
134134
```shell
135135
npx @modelcontextprotocol/server-everything streamableHttp
136136
```
137+
138+
## License
139+
140+
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.

src/filesystem/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Node.js server implementing Model Context Protocol (MCP) for filesystem operations.
44

5+
Published on npm as [`@modelcontextprotocol/server-filesystem`](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem).
6+
57
## Features
68

79
- Read/write files

src/memory/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
A basic implementation of persistent memory using a local knowledge graph. This lets Claude remember information about the user across chats.
44

5+
Published on npm as [`@modelcontextprotocol/server-memory`](https://www.npmjs.com/package/@modelcontextprotocol/server-memory).
6+
57
## Core Concepts
68

79
### Entities

src/sequentialthinking/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
An MCP server implementation that provides a tool for dynamic and reflective problem-solving through a structured thinking process.
44

5+
Published on npm as [`@modelcontextprotocol/server-sequential-thinking`](https://www.npmjs.com/package/@modelcontextprotocol/server-sequential-thinking).
6+
57
## Features
68

79
- Break down complex problems into manageable steps

0 commit comments

Comments
 (0)