From 66bbe389954eb739d4b6c281ccb056f19c3e069b Mon Sep 17 00:00:00 2001 From: s-b-e-n-s-o-n <80784472+s-b-e-n-s-o-n@users.noreply.github.com> Date: Thu, 7 May 2026 14:21:03 -0400 Subject: [PATCH 1/2] ci: add GitHub release automation --- .github/workflows/release.yml | 63 +++++++++++++++++++++++++++++++++++ docs/RELEASE.md | 26 +++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 docs/RELEASE.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d3cb998 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,63 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "Existing v* tag to publish as a GitHub Release" + 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[@]}" diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000..8b9d92d --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,26 @@ +# 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. +2. Run `npm run ci`. +3. Commit the release changes. +4. Create and push a stable tag: + + ```bash + git tag v0.4.0 + git push origin v0.4.0 + ``` + +The `Release` workflow 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. + +## 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. From d0bd54c1cb41c312f5b4db6d34f69553de6efb13 Mon Sep 17 00:00:00 2001 From: s-b-e-n-s-o-n <80784472+s-b-e-n-s-o-n@users.noreply.github.com> Date: Sat, 16 May 2026 08:41:21 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20feat(release):=20auto-publish?= =?UTF-8?q?=20to=20MCP=20Registry=20on=20tagged=20releases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🔧 config(ci): add OIDC-authed publish-registry job to release workflow with version and npm-availability guards - 📝 docs(release): document npm publish step and registry publishing job - 🔧 config(gitignore): ignore .claude, .playwright-mcp, .vercel artifacts --- .github/workflows/release.yml | 80 ++++++++++++++++++++++++++++++++++- .gitignore | 5 +++ docs/RELEASE.md | 25 ++++++++--- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3cb998..44484b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: tag: - description: "Existing v* tag to publish as a GitHub Release" + description: "Existing v* tag to publish as a GitHub Release and to the MCP Registry" required: true type: string @@ -61,3 +61,81 @@ jobs: 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 diff --git a/.gitignore b/.gitignore index 20b6de8..8250a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,11 @@ dist/ *.swp *.swo +# Agent/tooling artifacts +.claude/ +.playwright-mcp/ +.vercel/ + # OS specific .DS_Store Thumbs.db diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 8b9d92d..6bc6864 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -6,19 +6,34 @@ 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. + `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. Create and push a stable tag: +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 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. +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