How to Automate Publishing with GitHub Actions

How to Automate Publishing with GitHub Actions

Source: MCP b4a909c3

Treat package upload and Registry publication as separate release steps

The workflow below writes to two systems: a package registry and the MCP Registry. They have separate identities and separate failure modes. Decide the intended namespace and package version before pushing a release tag, then make the job’s evidence precise enough to tell which write succeeded.

Job stops atInspect firstRecovery direction
Build or testsSource revision and dependency lockFix before publishing either system
Package uploadPackage credentials, name and versionDetermine whether the exact version already exists
Registry loginOIDC permission or the selected PAT/domain proofRepair that identity, without republishing the package
Registry publicationPublished package’s marker and server metadataReuse the intended artifact when it is already correct
Post-release queryExact name and Registry versionCheck the read result separately from job status

OIDC removes the dedicated Registry secret for that authentication path; it does not automatically authorize npm publication. Likewise, having NPM_TOKEN says nothing about Registry namespace permission. Choose one of the documented Registry authentication flows and retain the matching package-registry authentication independently.

Before automating, compare the tag, server.json version and package version. If the job copies the tag into metadata, inspect the resulting file before publication. For repeatable releases, pin the publisher version you reviewed and record it alongside the workflow revision; the reference’s latest-download example otherwise allows the publisher to change between runs.

If npm succeeds and the Registry step fails, rerunning the entire job without checking can fail on an already published package version. Recover from the failed stage after inspecting the existing artifact. When metadata itself needs correction, follow Versioning rather than trying to overwrite a published Registry record. These are workflow review recommendations based on the attributed examples; this site has not executed them against either public registry.

The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.

Step 1: Create a Workflow File

In your server project directory, create a .github/workflows/publish-mcp.yml file. Here is an example for npm-based local server, but the MCP Registry publishing steps are the same for all package types:

name: Publish to MCP Registry

on:
  push:
    tags: ["v*"] # Triggers on version tags like v1.0.0

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write # Required for OIDC authentication
      contents: read

    steps:
      - name: Checkout code
        uses: actions/checkout@v5

      ### Publish underlying npm package:

      - name: Set up Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "lts/*"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test --if-present

      - name: Build package
        run: npm run build --if-present

      - name: Publish package to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      ### Publish MCP server:

      - name: Install mcp-publisher
        run: |
          curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher

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

      # Optional:
      # - name: Set version in server.json
      #   run: |
      #     VERSION=${GITHUB_REF#refs/tags/v}
      #     jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json

      - name: Publish server to MCP Registry
        run: ./mcp-publisher publish
name: Publish to MCP Registry

on:
  push:
    tags: ["v*"] # Triggers on version tags like v1.0.0

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - name: Checkout code
        uses: actions/checkout@v5

      ### Publish underlying npm package:

      - name: Set up Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "lts/*"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test --if-present

      - name: Build package
        run: npm run build --if-present

      - name: Publish package to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      ### Publish MCP server:

      - name: Install mcp-publisher
        run: |
          curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher

      - name: Authenticate to MCP Registry
        run: ./mcp-publisher login github --token ${{ secrets.MCP_GITHUB_TOKEN }}

      # Optional:
      # - name: Set version in server.json
      #   run: |
      #     VERSION=${GITHUB_REF#refs/tags/v}
      #     jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json

      - name: Publish server to MCP Registry
        run: ./mcp-publisher publish
name: Publish to MCP Registry

on:
  push:
    tags: ["v*"] # Triggers on version tags like v1.0.0

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - name: Checkout code
        uses: actions/checkout@v5

      ### Publish underlying npm package:

      - name: Set up Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "lts/*"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test --if-present

      - name: Build package
        run: npm run build --if-present

      - name: Publish package to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      ### Publish MCP server:

      - name: Install mcp-publisher
        run: |
          curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher

      # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      # TODO: Replace `example.com` with your domain name
      # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      - name: Authenticate to MCP Registry
        run: ./mcp-publisher login dns --domain example.com --private-key ${{ secrets.MCP_PRIVATE_KEY }}

      # Optional:
      # - name: Set version in server.json
      #   run: |
      #     VERSION=${GITHUB_REF#refs/tags/v}
      #     jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json

      - name: Publish server to MCP Registry
        run: ./mcp-publisher publish

Step 2: Add Secrets

You may need to add a secret to the repository depending on which authentication method you choose:

  • GitHub OIDC Authentication: No dedicated secret necessary.
  • GitHub PAT Authentication: Add a MCP_GITHUB_TOKEN secret with a GitHub Personal Access Token (PAT) that has read:org and read:user scopes.
  • DNS Authentication: Add a MCP_PRIVATE_KEY secret with your Ed25519 private key.

You may also need to add secrets for your package registry. For example, the workflow above needs an NPM_TOKEN secret with your npm token.

For information about how to add secrets to a repository, see Using secrets in GitHub Actions.

Step 3: Tag and Release

Create and push a version tag to trigger the workflow:

git tag v1.0.0
git push origin v1.0.0

The workflow will run tests, build the package, publish the package to npm, and publish the server to the MCP Registry.

Troubleshooting

Error MessageAction
“Authentication failed”Ensure id-token: write permission is set for OIDC, or check secrets.
“Package validation failed”Verify your package successfully published to the package registry (e.g., npm, PyPI), and that your package has the necessary verification information.