This runbook covers operational procedures for releasing MCP Hangar.
- All CI checks passing on
mainbranch - Every non-trivial PR merged since the last release added its fragment in
changelog.d/(CHANGELOG.mdis never edited by hand) - No blocking issues in milestone
git checkout main
git pull origin main
git status # Should be clean
# Run full test suite
uv run pytest tests/ -v
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
uv run mypy src/Releases are driven by release-please (.github/workflows/release-please.yml). When Conventional Commit PRs merge to main, release-please opens a release PR, chore(release): release X.Y.Z, that bumps pyproject.toml, server.json and uv.lock. The same workflow folds the changelog.d/ fragments into a CHANGELOG.md section on that PR. Merging it makes release-please push the vX.Y.Z tag and create the GitHub Release, and the tag triggers the release pipeline.
After version tag is pushed, the Release workflow triggers automatically:
- Validate — Checks tag matches pyproject.toml
- Test — Runs full test matrix (Python 3.11-3.14)
- Smoke the wheel — Builds the wheel, installs it in a clean venv and drives a real tool call, before anything is published
- Publish PyPI — Builds, attests and uploads to PyPI
- Publish to MCP Registry — Stable releases only; checks that
server.jsonmatches the tag - Publish Docker — Builds multi-arch images (amd64, arm64), pushes to GHCR, signs them with cosign
- Create Release — Updates the GitHub Release with the changelog section, the wheel, the sdist and the provenance bundle
- Smoke published — Installs what PyPI serves and drives a tool call again
Monitor at: https://github.com/mcp-hangar/mcp-hangar/actions
# Verify PyPI package
pip install mcp-hangar==<VERSION> --dry-run
# Verify Docker image
docker pull ghcr.io/mcp-hangar/mcp-hangar:<VERSION>
docker run --rm ghcr.io/mcp-hangar/mcp-hangar:<VERSION> --version- Verify GitHub Release page has correct changelog
- Update documentation site if needed
- Announce in relevant channels (Discord, Slack, etc.)
- Close milestone in GitHub
Use this procedure for critical bugs in production releases.
| Severity | Description | Response Time |
|---|---|---|
| P0 | Security vulnerability, data loss | Immediate |
| P1 | Major functionality broken | < 4 hours |
| P2 | Significant bug, workaround exists | < 24 hours |
# From the affected version tag
git fetch --tags
git checkout -b hotfix/X.Y.Z vX.Y.Z-1 # e.g., hotfix/1.0.1 from v1.0.0# Make minimal fix, add regression test
# ...
# Run tests
uv run pytest tests/ -v
git add -A
git commit -m "fix(<scope>): description of fix"# Update the version everywhere the pipeline checks it. Anchored: an
# unanchored pattern also rewrites ruff's target-version and mypy's python_version.
perl -pi -e 's/^version = ".*"/version = "X.Y.Z"/' pyproject.toml
perl -pi -e 's/"version": "[^"]*"/"version": "X.Y.Z"/' server.json # both fields
uv lock # mcp-hangar's own entry
# Add a `## [X.Y.Z] - YYYY-MM-DD` section, with a `### Fixed` entry, at the top
# of the version sections in CHANGELOG.md: the release job copies that section
# into the GitHub Release.git add pyproject.toml server.json uv.lock CHANGELOG.md
git commit -m "chore(release): release X.Y.Z"
git tag -a vX.Y.Z -m "Hotfix: description"
git push origin hotfix/X.Y.Z
git push origin vX.Y.ZOpen a PR to main with the fix commit only, so commit the fix and the version
bump separately on the hotfix branch. The version files on main belong
to release-please, so do not carry the bump over, and do not push to main
directly: the PR runs the required checks.
git checkout -b fix/<scope>-<slug> origin/main
git cherry-pick <fix-commit-hash>
git push origin fix/<scope>-<slug>If a release introduces critical issues that can't be hotfixed quickly.
PyPI doesn't allow re-uploading deleted versions. Instead:
-
Yank the version (marks as not recommended):
Use the PyPI web interface: navigate to
https://pypi.org/manage/project/mcp-hangar/release/X.Y.Z/, then click "Yank". There is no CLI command for yanking. -
Release a new patch version with the fix or revert.
-
Update
latesttag to previous stable version. Retag the multi-arch manifest in the registry;docker pull,tagandpushwould republishlatestfor one architecture only:docker buildx imagetools create -t ghcr.io/mcp-hangar/mcp-hangar:latest \ ghcr.io/mcp-hangar/mcp-hangar:X.Y.Z-1
-
Document the issue in GitHub Release notes.
- Update GitHub Release to mark as "Known Issues"
- Post in announcement channels with:
- Affected versions
- Impact description
- Recommended action (upgrade to X.Y.Z+1 or pin to X.Y.Z-1)
Error: Tests failed on Python 3.X
Resolution:
- Check test logs in Actions
- Reproduce locally:
uv run pytest tests/ -v --tb=long - A flaky failure: re-run the failed jobs of the Release run. PyPI publishing
uses
skip-existing, so a re-run never fails on an upload that already happened. - A real defect: fix it on
mainand release the next patch. Do not move the tag: release-please has already created the GitHub Release for it.
Error: 403 Forbidden - trusted publishing not configured
Resolution:
- Verify PyPI Trusted Publisher is configured:
- Go to PyPI → Project → Publishing
- Add GitHub publisher:
mcp-hangar/mcp-hangar, workflowrelease.yml
- Ensure
environment: pypiis set in workflow
Error: buildx failed for linux/arm64
Resolution:
- Check if Dockerfile has architecture-specific dependencies
- May need to add platform-specific build stages
- Consider removing arm64 from platforms temporarily
Version mismatch!
pyproject.toml: X.Y.Z
Git tag: X.Y.W
Resolution:
-
Either update
pyproject.tomlto match tag -
Or delete tag and re-create with correct version:
git push origin :refs/tags/vX.Y.W git tag -d vX.Y.W
If automated workflows fail and manual release is needed:
# Build package
python -m build
# Upload to PyPI (requires API token)
twine upload dist/*
# Build and push Docker
docker buildx build --platform linux/amd64,linux/arm64 \
-t ghcr.io/mcp-hangar/mcp-hangar:X.Y.Z \
-t ghcr.io/mcp-hangar/mcp-hangar:latest \
--push .| Role | Contact |
|---|---|
| Release Manager | @maintainer |
| Security Issues | GitHub Security |
| Infrastructure | @infra-team |
| Date | Change | Author |
|---|---|---|
| 2026-01-12 | Initial runbook creation | CI/CD Setup |