Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
changelog:
exclude:
labels:
- ignore-for-release
- release
authors:
- github-actions[bot]
- dependabot[bot]
categories:
- title: Breaking Changes
labels:
- breaking-change
- breaking
- title: New Features
labels:
- feature
- enhancement
- title: New Examples
labels:
- examples
- title: Bug Fixes
labels:
- bug
- fix
- title: Documentation
labels:
- documentation
- docs
- title: Other Changes
labels:
- "*"
24 changes: 20 additions & 4 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ jobs:
- run: npm ci
- run: npm test

# All publish jobs depend directly on [build, test] (not on each other) so
# they all enter "waiting for approval" together and can be approved in a
# single "Review deployments" click.

publish:
runs-on: ubuntu-latest
if: github.event_name == 'release'
Expand Down Expand Up @@ -88,7 +92,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: Release
needs: [publish]
needs: [build, test]

permissions:
contents: read
Expand Down Expand Up @@ -134,16 +138,28 @@ jobs:
- name: Build example
run: npm run build --workspace examples/${{ matrix.example }}

- name: Determine npm tag
id: npm-tag
run: |
VERSION=$(node -p "require('./package.json').version")
if [[ "$VERSION" == *"-beta"* ]]; then
echo "tag=--tag beta" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.release.target_commitish }}" != "main" ]]; then
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2)
echo "tag=--tag release-${MAJOR_MINOR}" >> $GITHUB_OUTPUT
else
echo "tag=" >> $GITHUB_OUTPUT
fi

- name: Publish example
run: npm publish --workspace examples/${{ matrix.example }} --provenance --access public
run: npm publish --workspace examples/${{ matrix.example }} --provenance --access public ${{ steps.npm-tag.outputs.tag }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}

publish-mcpb:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: Release
needs: [publish-examples]
needs: [build, test]

permissions:
contents: write
Expand Down
93 changes: 93 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Release

on:
workflow_dispatch:
inputs:
bump:
description: "patch | minor | major | prerelease | explicit version (e.g. 1.4.0)"
required: true
default: patch
preid:
description: "Prerelease identifier (only used with bump=prerelease)"
type: choice
options: [beta]
default: beta
pull_request:
types: [closed]
branches: [main]

permissions:
contents: write
pull-requests: write

env:
GH_TOKEN: ${{ github.token }}

jobs:
# Opens a PR that bumps all package versions. Add release notes to
# RELEASES.md in the PR before merging.
prepare:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: main

- uses: actions/setup-node@v6
with:
node-version: "22"

- name: Bump versions
id: bump
run: |
VERSION=$(node scripts/bump-version.mjs "${{ inputs.bump }}" --preid="${{ inputs.preid }}")
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Create release PR
run: |
VERSION="${{ steps.bump.outputs.version }}"
BRANCH="release/v$VERSION"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add -u
git commit -m "chore: release v$VERSION"
git push -u origin "$BRANCH"
gh label create release --color B60205 --description "Release PR" 2>/dev/null || true
gh pr create \
--title "chore: release v$VERSION" \
--body "Bumps all packages to \`$VERSION\`. Add release notes to \`RELEASES.md\` before merging. Merging this PR will automatically tag and create the GitHub Release, which triggers npm-publish." \
--label release \
--base main

# When a release PR is merged, tag the commit and create the GitHub Release.
# This triggers the npm-publish workflow.
tag:
if: >
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'release')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}

- name: Create tag and release
env:
# PAT so the `release: published` event triggers npm-publish
# (events from GITHUB_TOKEN don't cascade to other workflows)
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
VERSION=$(node -p "require('./package.json').version")
TAG="v$VERSION"
if gh release view "$TAG" --repo ${{ github.repository }} >/dev/null 2>&1; then
echo "Release $TAG already exists, skipping"
exit 0
fi
git tag "$TAG"
git push origin "$TAG" || echo "tag already on remote"
PRERELEASE=""
[[ "$VERSION" == *-* ]] && PRERELEASE="--prerelease"
gh release create "$TAG" --title "$TAG" --generate-notes $PRERELEASE
47 changes: 22 additions & 25 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,42 +529,39 @@ Before publishing releases, ensure the following are configured:
- Name it `Release`
- Add required reviewers or other protection rules as needed

3. **`RELEASE_TOKEN` secret**: The release workflow creates GitHub Releases that must trigger the npm-publish workflow. Events from the default `GITHUB_TOKEN` don't cascade to other workflows, so a separate token is needed.
- Create a [fine-grained PAT](https://github.com/settings/personal-access-tokens/new) scoped to this repository with **Contents: write** permission
- Go to Settings > Secrets and variables > Actions > New repository secret
- Name: `RELEASE_TOKEN`, value: the PAT

### Publishing a Release

Releases are published automatically via GitHub Actions when a GitHub Release is created.
Releases are automated via the [Release workflow](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/release.yml).

#### Steps to publish:

1. **Update the version** in `package.json`:
1. **Trigger the Release workflow**:
- Go to Actions → [Release](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/release.yml) → "Run workflow"
- Select the bump type (`patch`, `minor`, `major`, or `prerelease`)
- Click "Run workflow"

```bash
# For a regular release
npm version patch # or minor, or major
2. **Review the release PR**:
- The workflow bumps the version across all packages and opens a PR labeled `release`
- Add release notes to `RELEASES.md` in the PR
- Approve and merge the PR
- _Note: re-running the workflow for the same version fails if the branch already exists. Delete the `release/vX.Y.Z` branch first if you need to redo the bump._

# For a beta release
npm version prerelease --preid=beta
```
3. **Done** — merging the PR automatically tags the commit and creates the GitHub Release (with auto-generated notes), which triggers the [npm-publish workflow](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/npm-publish.yml). Approve the deployment once when prompted.

2. **Commit the version bump** (if not done by `npm version`):
#### Manual alternative

```bash
git add package.json
git commit -m "Bump version to X.Y.Z"
git push origin main
```
You can also bump versions locally:

3. **Create a GitHub Release**:
- Go to [Releases](https://github.com/modelcontextprotocol/ext-apps/releases)
- Click "Draft a new release"
- Create a new tag matching the version (e.g., `v0.1.0`)
- Set the target branch (usually `main`)
- Write release notes describing the changes
- Click "Publish release"
```bash
npm run bump -- patch # or: minor | major | prerelease --preid=beta
```

4. **Monitor the workflow**:
- The [npm-publish workflow](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/npm-publish.yml) will trigger automatically
- It runs build and test jobs before publishing
- On success, the package is published to npm with provenance
Then commit, push, and create a GitHub Release manually — the npm-publish workflow triggers on release creation.

#### npm Tags

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"prettier": "prettier -u \"**/*.{js,jsx,ts,tsx,mjs,json,md,yml,yaml}\" --check",
"prettier:fix": "prettier -u \"**/*.{js,jsx,ts,tsx,mjs,json,md,yml,yaml}\" --write",
"check:versions": "node scripts/check-versions.mjs",
"bump": "node scripts/bump-version.mjs",
"update-lock:docker": "rm -rf node_modules package-lock.json examples/*/node_modules && docker run --rm --platform linux/amd64 -v $(pwd):/work -w /work -e HOME=/tmp node:latest npm i --registry=https://registry.npmjs.org/ --ignore-scripts && rm -rf node_modules examples/*/node_modules && npm i --registry=https://registry.npmjs.org/"
},
"author": "Olivier Chafik",
Expand Down
47 changes: 47 additions & 0 deletions scripts/bump-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env node
/**
* Bump the version in the root package.json and sync all workspace packages
* to the same version.
*
* Usage:
* node scripts/bump-version.mjs patch
* node scripts/bump-version.mjs minor
* node scripts/bump-version.mjs major
* node scripts/bump-version.mjs 1.4.0
* node scripts/bump-version.mjs prerelease --preid=beta
*
* Writes the new version to stdout (logs go to stderr).
*/

import { execSync } from "node:child_process";
import { readFileSync } from "node:fs";

const args = process.argv.slice(2);
if (!args[0]) {
console.error(
"Usage: node scripts/bump-version.mjs <patch|minor|major|prerelease|X.Y.Z> [--preid=<id>]",
);
process.exit(1);
}

const exec = (cmd) =>
execSync(cmd, { stdio: ["inherit", "pipe", "inherit"] })
.toString()
.trim();

const pkgName = JSON.parse(readFileSync("package.json", "utf-8")).name;

const newVersion = exec(
`npm version ${args.join(" ")} --no-git-tag-version`,
).replace(/^v/, "");
exec(`npm pkg set version=${newVersion} --workspaces`);

// Keep workspace dependency ranges compatible (needed on major bumps)
const [major, minor] = newVersion.split(".");
exec(`npm pkg set "dependencies.${pkgName}=^${major}.${minor}.0" --workspaces`);

// Sync package-lock.json so `npm ci` doesn't reject the release PR
exec("npm install --package-lock-only --ignore-scripts");

console.error(`Bumped root + workspaces to ${newVersion}`);
console.log(newVersion);
Loading