Release Publish #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Publish | |
| on: | |
| # Trigger after SemVer Release completes | |
| workflow_run: | |
| workflows: ["SemVer Release"] | |
| types: [completed] | |
| branches: [ main, master ] | |
| # Also support manual dispatch | |
| workflow_dispatch: | |
| env: | |
| REGISTRY: ghcr.io | |
| jobs: | |
| prepare: | |
| name: Prepare release metadata | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: master | |
| fetch-depth: 0 | |
| - name: Extract version from pyproject.toml | |
| id: version | |
| run: | | |
| VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/') | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Release version: $VERSION" | |
| publish_pypi: | |
| name: Publish PyPI package | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/ha-mcp | |
| permissions: | |
| contents: read | |
| id-token: write # Required for PyPI trusted publishing | |
| env: | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: master | |
| fetch-depth: 0 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Build distribution | |
| run: uv build | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist | |
| skip-existing: true | |
| publish_docker: | |
| name: Publish Docker image | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write # Required to push to GHCR | |
| env: | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: master | |
| fetch-depth: 0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata for Docker | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ github.repository }} | |
| tags: | | |
| type=semver,pattern={{version}},value=${{ env.VERSION }} | |
| type=semver,pattern={{major}}.{{minor}},value=${{ env.VERSION }} | |
| type=semver,pattern={{major}},value=${{ env.VERSION }} | |
| type=raw,value=latest | |
| type=raw,value=stable | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| # Match architectures supported by uv base image | |
| # uv image only provides amd64 and arm64, not 32-bit platforms | |
| platforms: linux/amd64,linux/arm64 | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-args: | | |
| BUILD_VERSION=${{ env.VERSION }} | |
| publish_mcp: | |
| name: Publish MCP server manifest | |
| needs: [publish_pypi, publish_docker, prepare] | |
| runs-on: ubuntu-latest | |
| if: ${{ always() }} | |
| permissions: | |
| contents: read | |
| id-token: write # Required for MCP Registry OIDC authentication | |
| env: | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| IMAGE: ghcr.io/${{ github.repository }}:${{ needs.prepare.outputs.version }} | |
| steps: | |
| - name: Report upstream job results | |
| run: | | |
| echo "PyPI publish conclusion: ${{ needs.publish_pypi.result }}" | |
| echo "Docker publish conclusion: ${{ needs.publish_docker.result }}" | |
| - uses: actions/checkout@v6 | |
| if: ${{ needs.publish_pypi.result == 'success' && needs.publish_docker.result == 'success' }} | |
| with: | |
| ref: master | |
| fetch-depth: 0 | |
| - name: Install MCP Publisher CLI | |
| if: ${{ needs.publish_pypi.result == 'success' && needs.publish_docker.result == 'success' }} | |
| run: | | |
| set -euo pipefail | |
| 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: Render server manifest for publish | |
| if: ${{ needs.publish_pypi.result == 'success' && needs.publish_docker.result == 'success' }} | |
| run: | | |
| set -euo pipefail | |
| python scripts/render_server_manifest.py \ | |
| --input server.json \ | |
| --output server.json \ | |
| --version "$VERSION" \ | |
| --oci-image "$IMAGE" | |
| cat server.json | |
| - name: Validate server manifest | |
| if: ${{ needs.publish_pypi.result == 'success' && needs.publish_docker.result == 'success' }} | |
| run: | | |
| set -euo pipefail | |
| python -m pip install --quiet jsonschema | |
| python scripts/validate_server_manifest.py server.json | |
| - name: Login to MCP Registry | |
| if: ${{ needs.publish_pypi.result == 'success' && needs.publish_docker.result == 'success' }} | |
| run: ./mcp-publisher login github-oidc | |
| - name: Publish server to MCP Registry | |
| if: ${{ needs.publish_pypi.result == 'success' && needs.publish_docker.result == 'success' }} | |
| run: | | |
| set +e # Don't exit on error | |
| OUTPUT=$(./mcp-publisher publish --server server.json 2>&1) | |
| EXIT_CODE=$? | |
| echo "$OUTPUT" | |
| # If publish succeeded, we're done | |
| if [ $EXIT_CODE -eq 0 ]; then | |
| echo "✓ Successfully published to MCP Registry" | |
| exit 0 | |
| fi | |
| # If version already exists, treat as success | |
| if echo "$OUTPUT" | grep -qi "cannot publish duplicate version\|version.*already.*exist\|already.*published"; then | |
| echo "⚠ Version $VERSION already exists in MCP Registry (published previously)" | |
| echo "✓ Treating as success" | |
| exit 0 | |
| fi | |
| # Any other error is a real failure | |
| echo "✗ Failed to publish to MCP Registry" | |
| exit $EXIT_CODE | |
| - name: Skip MCP publish | |
| if: ${{ needs.publish_pypi.result != 'success' || needs.publish_docker.result != 'success' }} | |
| run: | | |
| echo "Skipping MCP publish because one or more upstream publish jobs failed." |