Skip to content
Merged
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
141 changes: 141 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: Release

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Existing v* tag to publish as a GitHub Release and to the MCP Registry"
required: true
type: string

permissions:
contents: write

jobs:
github-release:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Publish GitHub Release
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
set -euo pipefail

tag="${GITHUB_REF_NAME}"
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
tag="$INPUT_TAG"
fi

if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Refusing to publish non-semver tag: $tag" >&2
exit 1
fi

git fetch --tags --force
if ! git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
echo "Tag does not exist on the remote: $tag" >&2
exit 1
fi

if gh release view "$tag" >/dev/null 2>&1; then
echo "GitHub Release already exists for $tag; nothing to do."
exit 0
fi

args=(--verify-tag --generate-notes --title "Release $tag")
if [[ "$tag" == *-* ]]; then
args+=(--prerelease --latest=false)
else
args+=(--latest)
fi

gh release create "$tag" "${args[@]}"

publish-registry:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24

- name: Resolve tag
id: tag
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
set -euo pipefail

tag="${GITHUB_REF_NAME}"
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
tag="$INPUT_TAG"
fi

if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Refusing to publish non-semver tag: $tag" >&2
exit 1
fi

echo "version=${tag#v}" >>"$GITHUB_OUTPUT"

- name: Verify server.json matches tag
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
set -euo pipefail

server_version="$(node -p "require('./server.json').version")"
package_version="$(node -p "require('./server.json').packages[0].version")"
if [[ "$server_version" != "$VERSION" || "$package_version" != "$VERSION" ]]; then
echo "server.json ($server_version / package $package_version) does not match tag v$VERSION" >&2
exit 1
fi

- name: Wait for npm package
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
set -euo pipefail

for attempt in $(seq 1 20); do
if npm view "portkey-admin-mcp@${VERSION}" version >/dev/null 2>&1; then
echo "Found portkey-admin-mcp@${VERSION} on npm"
exit 0
fi
echo "Waiting for portkey-admin-mcp@${VERSION} on npm (attempt ${attempt}/20)..."
sleep 15
done

echo "Timed out waiting for portkey-admin-mcp@${VERSION} on npm" >&2
exit 1

- name: Install mcp-publisher
run: |
set -euo pipefail
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" \
| tar -xz mcp-publisher

- name: Authenticate with the MCP Registry
run: ./mcp-publisher login github-oidc

- name: Publish to the MCP Registry
run: ./mcp-publisher publish
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ dist/
*.swp
*.swo

# Agent/tooling artifacts
.claude/
.playwright-mcp/
.vercel/

# OS specific
.DS_Store
Thumbs.db
Expand Down
41 changes: 41 additions & 0 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Release Process

This project uses stable SemVer tags plus GitHub Releases so package registries
and catalog scanners can detect published versions.

## Publish a New Stable Release

1. Update `package.json`, `package-lock.json`, `server.json`, and
`CHANGELOG.md` for the new version. Keep `server.json`'s top-level
`version` and `packages[0].version` in sync with `package.json`.
2. Run `npm run ci`.
3. Commit the release changes.
4. Publish the npm package:

```bash
npm publish --access public
```

5. Create and push a stable tag:

```bash
git tag v0.4.0
git push origin v0.4.0
```

The `Release` workflow runs two jobs on every pushed `v*` tag:

- **`github-release`** publishes a non-prerelease GitHub Release for stable
tags like `v0.4.0`. Tags containing a hyphen, such as `v0.4.0-beta.1`, are
published as prereleases and are not marked as the latest release.
- **`publish-registry`** publishes `server.json` to the
[MCP Registry](https://registry.modelcontextprotocol.io). It authenticates
via GitHub Actions OIDC (no secrets required), verifies `server.json`
matches the tag, and waits for the matching npm version to be available
before publishing. Publishing npm before pushing the tag (step 4 then 5)
avoids the wait.

## Backfill an Existing Tag

Use the `Release` workflow's manual dispatch and pass the existing tag name.
The workflow verifies that the tag exists before publishing a GitHub Release.