diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7fe6d37..2498028 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,11 @@ on: branches: - main workflow_dispatch: + inputs: + force_publish: + description: 'Skip release-please and publish current package.json versions' + type: boolean + default: false permissions: contents: write @@ -32,33 +37,56 @@ jobs: with: token: ${{ steps.generate-token.outputs.token }} - # Everything below only runs when a release PR is merged. - # Shared setup steps gate on releases_created (true if any package released). + - name: Validate force publish ref + if: ${{ inputs.force_publish == true }} + env: + GH_TOKEN: ${{ github.token }} + REF_TYPE: ${{ github.ref_type }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + if [ "$REF_TYPE" != "tag" ]; then + echo "Select a release tag instead of a branch when running force_publish." + exit 1 + fi + if gh api --silent "repos/$GITHUB_REPOSITORY/releases/tags/$REF_NAME"; then + echo "Force publishing from release tag $REF_NAME." + exit 0 + fi + echo "Selected tag $REF_NAME is not associated with a GitHub Release." + exit 1 + + # Everything below runs when a release PR is merged, or when + # force_publish is manually enabled for retrying current package versions. + # Shared setup steps gate on releases_created (true if any package released) + # or force_publish. # Per-package publish steps gate on --release_created so only the - # released package(s) are published each run. + # released package(s) are published each run. force_publish runs all + # publish steps; already-published npm versions and MCP registry + # versions are skipped below. # Ref: https://github.com/googleapis/release-please-action#path-outputs - name: Checkout repository uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - if: ${{ steps.release.outputs.releases_created }} + if: ${{ steps.release.outputs.releases_created || inputs.force_publish == true }} - name: Setup tools uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 - if: ${{ steps.release.outputs.releases_created }} + if: ${{ steps.release.outputs.releases_created || inputs.force_publish == true }} with: install: true cache: true - name: Install dependencies - if: ${{ steps.release.outputs.releases_created }} + if: ${{ steps.release.outputs.releases_created || inputs.force_publish == true }} run: pnpm install --frozen-lockfile - name: Build packages - if: ${{ steps.release.outputs.releases_created }} + if: ${{ steps.release.outputs.releases_created || inputs.force_publish == true }} run: pnpm --filter @supabase/mcp-utils --filter @supabase/mcp-server-supabase --filter @supabase/mcp-server-postgrest build - name: Publish @supabase/mcp-utils to npm - if: ${{ steps.release.outputs['packages/mcp-utils--release_created'] }} + if: ${{ steps.release.outputs['packages/mcp-utils--release_created'] || inputs.force_publish == true }} working-directory: packages/mcp-utils run: | set -euo pipefail @@ -78,7 +106,7 @@ jobs: pnpm publish --no-git-checks --ignore-scripts --access public --provenance - name: Publish @supabase/mcp-server-supabase to npm - if: ${{ steps.release.outputs['packages/mcp-server-supabase--release_created'] }} + if: ${{ steps.release.outputs['packages/mcp-server-supabase--release_created'] || inputs.force_publish == true }} working-directory: packages/mcp-server-supabase run: | set -euo pipefail @@ -98,7 +126,7 @@ jobs: pnpm publish --no-git-checks --ignore-scripts --access public --provenance - name: Publish @supabase/mcp-server-postgrest to npm - if: ${{ steps.release.outputs['packages/mcp-server-postgrest--release_created'] }} + if: ${{ steps.release.outputs['packages/mcp-server-postgrest--release_created'] || inputs.force_publish == true }} working-directory: packages/mcp-server-postgrest run: | set -euo pipefail @@ -117,14 +145,47 @@ jobs: rm -f "$NPM_VIEW_STDERR" pnpm publish --no-git-checks --ignore-scripts --access public --provenance + # The MCP registry rejects re-publishing an existing version. + # Probe the registry first and gate both login and publish on the result. + - name: Check MCP registry version + id: mcp-registry-check + if: ${{ steps.release.outputs['packages/mcp-server-supabase--release_created'] || inputs.force_publish == true }} + working-directory: packages/mcp-server-supabase + run: | + set -euo pipefail + ALREADY_PUBLISHED=$(node -e ' + const { name, version } = require("./server.json"); + const url = `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(name)}&version=${encodeURIComponent(version)}`; + fetch(url) + .then((res) => { + if (!res.ok) throw new Error(`Registry query failed: ${res.status} ${res.statusText}`); + return res.json(); + }) + .then((data) => { + const exists = (data.servers ?? []).some( + (entry) => entry.server?.name === name && entry.server?.version === version + ); + process.stdout.write(exists ? "true" : "false"); + }) + .catch((err) => { + console.error(err.message); + process.exit(1); + }); + ') + echo "already_published=$ALREADY_PUBLISHED" >> "$GITHUB_OUTPUT" + if [ "$ALREADY_PUBLISHED" = "true" ]; then + VERSION=$(node -p "require('./server.json').version") + echo "MCP registry version $VERSION already published, skipping." + fi + - name: Authenticate to MCP registry - if: ${{ steps.release.outputs['packages/mcp-server-supabase--release_created'] }} + if: ${{ steps.mcp-registry-check.outputs.already_published == 'false' }} working-directory: packages/mcp-server-supabase env: DOMAIN_VERIFICATION_KEY: ${{ secrets.DOMAIN_VERIFICATION_KEY }} run: pnpm registry:login - name: Publish to MCP registry - if: ${{ steps.release.outputs['packages/mcp-server-supabase--release_created'] }} + if: ${{ steps.mcp-registry-check.outputs.already_published == 'false' }} working-directory: packages/mcp-server-supabase run: pnpm registry:publish diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb7198d..2115b88 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,8 @@ Most contributors don't need to do anything beyond merging the release PR and up If the release PR gets into a bad state, close it and manually re-run the workflow from the [Actions tab](https://github.com/supabase/mcp/actions/workflows/release.yml) → **Run workflow**. release-please will recreate the PR from scratch. +If the workflow creates GitHub releases and tags but fails before publishing to npm or the MCP registry, re-run the workflow from one of the release tags created by the failed workflow run and enable `force_publish`. + ## Manual MCP registry publish (optional) This is only needed if the automated publish failed or needs to be re-run manually. The MCP registry stores metadata about the server (defined in `packages/mcp-server-supabase/server.json`) — it does not host the server itself. diff --git a/packages/mcp-server-supabase/src/management-api/types.ts b/packages/mcp-server-supabase/src/management-api/types.ts index 54b591d..d6f7111 100644 --- a/packages/mcp-server-supabase/src/management-api/types.ts +++ b/packages/mcp-server-supabase/src/management-api/types.ts @@ -217,7 +217,10 @@ export interface paths { }; get?: never; put?: never; - /** [Beta] Exchange auth code for user's access and refresh token */ + /** + * [Beta] Exchange auth code for user's access and refresh token + * @description Supports `authorization_code`, `refresh_token`, and `urn:ietf:params:oauth:grant-type:jwt-bearer` grant types. The `jwt-bearer` grant type (IDJAG — identity-directed JWT assertion) is in beta and available on Team and Enterprise plans only. + */ post: operations["v1-exchange-oauth-token"]; delete?: never; options?: never; @@ -2396,7 +2399,7 @@ export interface components { * } */ OAuthTokenBody: { /** @enum {string} */ - grant_type?: "authorization_code" | "refresh_token"; + grant_type?: "authorization_code" | "refresh_token" | "urn:ietf:params:oauth:grant-type:jwt-bearer"; /** Format: uuid */ client_id?: string; client_secret?: string; @@ -2404,6 +2407,8 @@ export interface components { code_verifier?: string; redirect_uri?: string; refresh_token?: string; + /** @description IDJAG assertion JWT for grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. Beta - available on Team and Enterprise plans only. */ + assertion?: string; /** * Format: uri * @description Resource indicator for MCP (Model Context Protocol) clients @@ -2413,7 +2418,8 @@ export interface components { }; OAuthTokenResponse: { access_token: string; - refresh_token: string; + /** @description The `urn:ietf:params:oauth:grant-type:jwt-bearer` grant type issues access tokens only, no refresh token is returned and the token cannot be revoked via `/v1/oauth/revoke`. */ + refresh_token?: string; expires_in: number; /** @enum {string} */ token_type: "Bearer";