Skip to content

Release Publish

Release Publish #3

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."