name: Release
on:
push:
tags:
- 'v*.*.*' # Triggers on version tags like v1.0.0, v2.1.3, etc.
jobs:
build-and-publish:
runs-on: ubuntu-latest
permissions:
contents: write # Required for creating releases
id-token: write # Required for PyPI trusted publishing
steps:
- uses: actions/checkout@v4
with:
# Fetch full history for setuptools-scm to work properly
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Cache uv dependencies
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-release-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-uv-release-
${{ runner.os }}-uv-
- name: Install build dependencies
run: |
uv pip install --system build setuptools-scm[toml]
- name: Verify version matches tag
run: |
# Extract version from tag (remove 'v' prefix)
TAG_VERSION=${GITHUB_REF#refs/tags/v}
# Debug git state
echo "Current commit: $(git rev-parse HEAD)"
echo "Current tag: $(git describe --tags)"
echo "All tags: $(git tag --list | tail -3)"
# Get version from setuptools-scm with debug
DETECTED_VERSION=$(python -c "import setuptools_scm; print(setuptools_scm.get_version())")
echo "Tag version: $TAG_VERSION"
echo "Detected version: $DETECTED_VERSION"
# Verify they match
if [ "$TAG_VERSION" != "$DETECTED_VERSION" ]; then
echo "Version mismatch! Tag: $TAG_VERSION, Detected: $DETECTED_VERSION"
echo "This might be due to setuptools-scm caching or git fetch issues"
exit 1
fi
- name: Build package
run: |
python -m build
- name: Verify package contents
run: |
uv pip install --system twine
twine check dist/*
- name: Create GitHub Release
run: |
# Extract version without 'v' prefix for pip install
VERSION=${GITHUB_REF_NAME#v}
# Get previous tag for release notes generation
PREV_TAG=$(git tag --sort=-version:refname | grep -v "$GITHUB_REF_NAME" | head -1)
# Create release with auto-generated notes
if [ -n "$PREV_TAG" ]; then
gh release create $GITHUB_REF_NAME \
--title "Release $GITHUB_REF_NAME" \
--generate-notes \
--notes-start-tag "$PREV_TAG"
else
# First release, no previous tag
gh release create $GITHUB_REF_NAME \
--title "Release $GITHUB_REF_NAME" \
--generate-notes
fi
# Add installation instructions to the beginning
gh release edit $GITHUB_REF_NAME \
--notes "$(echo "## Installation
\`\`\`bash
pip install mcpcap==$VERSION
\`\`\`
$(gh release view $GITHUB_REF_NAME --json body -q .body)")"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to PyPI
id: pypi_publish
uses: pypa/gh-action-pypi-publish@release/v1
# Uses PyPI's trusted publisher (OIDC). Configure project on PyPI first.
# -------- MCP Registry: best practice - align server.json version ----------
- name: Update server.json version to tag
if: steps.pypi_publish.outcome == 'success'
run: |
sudo apt-get update && sudo apt-get install -y jq
VERSION=${GITHUB_REF#refs/tags/v}
if [ -f server.json ]; then
echo "Setting server.json version to ${VERSION}"
jq --arg version "$VERSION" '.version = $version' server.json > server.json.tmp
mv server.json.tmp server.json
else
echo "server.json not found at repo root; skipping version update"
fi
# -------- MCP Registry: install publisher (pre-built binary) ---------------
- name: Install MCP Publisher
if: steps.pypi_publish.outcome == 'success'
run: |
set -euo pipefail
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.0.0/mcp-publisher_1.0.0_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
sudo mv mcp-publisher /usr/local/bin/
mcp-publisher --version
# -------- MCP Registry: login with DNS (mcpcap.ai) ------------------------
# Secret MCP_PRIVATE_KEY must be a 64-character hex Ed25519 private key
- name: Validate MCP_PRIVATE_KEY format
if: steps.pypi_publish.outcome == 'success'
run: |
KEY="${{ secrets.MCP_PRIVATE_KEY }}"
if ! [[ "$KEY" =~ ^[0-9a-fA-F]{64}$ ]]; then
echo "MCP_PRIVATE_KEY must be 64 hex chars (Ed25519 seed)."
exit 1
fi
- name: Login to MCP Registry (DNS)
if: steps.pypi_publish.outcome == 'success'
run: mcp-publisher login dns --domain mcpcap.ai --private-key "${{ secrets.MCP_PRIVATE_KEY }}"
# -------- MCP Registry: publish -------------------------------------------
- name: Publish to MCP Registry
if: steps.pypi_publish.outcome == 'success'
run: mcp-publisher publish
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist-files
path: dist/
retention-days: 30