Releasing
Releases are driven by GitHub releases. Publishing a release with a tag like v1.2.3 triggers the Release workflow. That workflow builds the Docker image for linux/amd64 and linux/arm64 and pushes it to ghcr.io/mattoddie/unifi-mcp. Nothing is published by merging to main alone.
Versioning
Section titled “Versioning”The project follows Semantic Versioning. Its public API is the set of tool names, their arguments and the environment variables:
| Change | Bump | Examples |
|---|---|---|
| Breaking | major (2.0.0) |
Renaming or removing a tool, argument or variable; changing a default so that more is allowed |
| New features | minor (1.3.0) |
New tools, toolsets, arguments or prompts |
| Fixes | patch (1.2.4) |
Bug fixes, compatibility fixes, docs, dependency updates |
While the version is 0.x, minor bumps may contain breaking changes. Call them out in the release notes.
The version comes from the tag. package.json stays at 0.0.0-dev in the repository, and release builds stamp the tag’s version into the image. The server reports it to MCP clients and prints it on its first log line.
Release checklist
Section titled “Release checklist”-
Check
mainis green on CI. -
Update the changelog. In a small PR, move the Unreleased entries in
CHANGELOG.mdunder a new heading,## [1.2.0] - YYYY-MM-DD, and update the comparison links at the bottom. Merge it. -
Publish the release. Use either the CLI or the web UI.
With the CLI:
Terminal window gh release create v1.2.0 --target main --title "v1.2.0" --generate-notesOr in the web UI: Releases → Draft a new release → Choose a tag → type
v1.2.0→ Create new tag on publish, targetmain. Click Generate release notes, edit them as needed, then Publish release. -
Watch the workflow. Open Actions → Release. It takes a few minutes. When it finishes, the release notes gain a Container image section with the pull command, digest and tags.
-
Check the image:
docker pull ghcr.io/mattoddie/unifi-mcp:1.2.0.
Release notes are generated from merged PR titles and grouped by label. The groups are set in .github/release.yml. Label PRs breaking, enhancement, new-tool, bug, compatibility, documentation or dependencies to sort them, or skip-changelog to leave a PR out.
Pre-releases
Section titled “Pre-releases”To test a release candidate, use a pre-release tag and tick Set as a pre-release:
gh release create v1.3.0-rc.1 --target main --prerelease --generate-notesPre-releases are published only under their exact version (ghcr.io/mattoddie/unifi-mcp:1.3.0-rc.1). They never move latest, 1 or 1.3.
What the workflow does
Section titled “What the workflow does”| Step | Detail |
|---|---|
| Validate the tag | Must match vMAJOR.MINOR.PATCH with an optional -prerelease suffix, otherwise the workflow fails before building. |
| Build | docker buildx for linux/amd64 and linux/arm64. The test suite runs inside the build, so failing tests mean no image. |
| Tag | X.Y.Z always. For stable releases also X.Y, X (from 1.0.0 on) and latest. |
| Push | To ghcr.io/mattoddie/unifi-mcp using the workflow’s GITHUB_TOKEN. No personal tokens are needed. |
| Supply chain | Attaches an SBOM and SLSA provenance to the image, and pushes a signed build provenance attestation (gh attestation verify). |
| Release notes | Appends the pull command, digest and tags. |
If something goes wrong
Section titled “If something goes wrong”- The workflow failed for a transient reason: re-run it from the Actions tab. It’s safe to re-run, and it pushes the same tags again.
- The release was wrong (bad code or wrong tag): don’t move or reuse a published tag, because people may already have pulled it. Fix the problem on
mainand publish the next patch version. You can delete the bad GitHub release and mark it as such in the changelog, but leave the image tag alone. - Wrong tag format (e.g.
1.2.0without thev): delete the release and tag (gh release delete 1.2.0 --cleanup-tag) and publish again with the right tag.
Repository setup
Section titled “Repository setup”Rulesets (in place)
Section titled “Rulesets (in place)”Two rulesets protect the repository. You can view and edit them under Settings → Rules → Rulesets.
Protect main (the default branch):
- Changes land only through pull requests. Nobody can push directly, force-push or delete the branch, and there are no bypasses.
- The CI checks Test (Node 22), Test (Node 24) and Docker image must pass, and the branch must be up to date with
main. - Review threads must be resolved before merging. The only merge method is squash.
- No approving review is required yet, because GitHub doesn’t let authors approve their own pull requests and the project has a single maintainer. Raise this when there are more maintainers.
Protect release tags (v*):
- Only repository admins can create, move or delete version tags, so only maintainers can publish releases. Published versions can’t be re-pointed.
Container package visibility
Section titled “Container package visibility”New GHCR packages normally start private, and GitHub doesn’t offer an API to change this. This package became public automatically because it’s linked to this public repository. If a new package ever starts private, open the package page (Your profile → Packages → unifi-mcp → Package settings) and choose Change visibility → Public under Danger Zone.
The package links to the repository automatically through the image’s org.opencontainers.image.source label, which makes it show in the repository sidebar.