Release Process¶
This project uses component-specific versioning. Each component (MCP Server, Workbench) has its own version and release cycle, allowing independent updates without affecting other components.
Before publishing, follow the release-candidate and recovery guide to verify exact artifacts and handle a partial publication.
Quick Start¶
The complete release workflow in order:
# 1. Pre-release checks
just release::status # Check which components have unreleased changes
just release::changes-since mcp-server # View specific changes (or workbench)
just all # Run all quality checks (must pass)
# 2. Update release notes
just release::draft-notes mcp-server # Generate draft notes from commits
# Then edit the component's RELEASE_NOTES.md file (see "Release Notes Files" below)
# 3. Version bump (workbench only - MCP Server uses setuptools-scm)
just release::bump-workbench 1.0.0
# 4. Commit changes
git add -A
git commit -m "chore: bump workbench to 1.0.0" # or appropriate component/message
# 5. Create & push tag (triggers CI/CD automatically)
just release::tag-workbench 1.0.0 # or tag-mcp-server
# 6. Monitor release at GitHub Actions, then verify
just release::list-tags
just release::latest-versions
| Component | Bump Command | Tag Command |
|---|---|---|
| MCP Server | (none - uses setuptools-scm) | just release::tag-mcp-server X.Y.Z |
| Workbench | just release::bump-workbench X.Y.Z | just release::tag-workbench X.Y.Z |
Components and Their Release Targets¶
| Component | Tag Format | Releases To |
|---|---|---|
| MCP Server | robust-mcp-server-vX.Y.Z | PyPI/TestPyPI*, Docker Hub, GitHub Release |
| Robust MCP Bridge Workbench | robust-mcp-workbench-vX.Y.Z | GitHub Release (archive) |
*Stable releases (X.Y.Z) publish to PyPI; non-stable releases (alpha, beta, rc) publish to TestPyPI only.
Version Format¶
All versions follow Semantic Versioning 2.0:
X.Y.Z- Stable release (published to PyPI only)X.Y.Z-alphaorX.Y.Z-alpha.N- Alpha pre-release (published to TestPyPI only)X.Y.Z-betaorX.Y.Z-beta.N- Beta pre-release (published to TestPyPI only)X.Y.Z-rc.N- Release candidate (published to TestPyPI only)
Release Workflow Overview¶
Releases follow a two-step process:
- Bump: Update version in all source files locally, then commit
- Tag: Create and push the git tag (triggers CI/CD)
This ensures the version in source files matches the git tag on the same commit.
┌─────────────────────────────────────────────────────────────────┐
│ 1. just release::bump-<component> X.Y.Z │
│ └── Updates version in all relevant files │
│ │
│ 2. git add -A && git commit -m "chore: bump ... to X.Y.Z" │
│ └── Commit the version changes │
│ │
│ 3. just release::tag-<component> X.Y.Z │
│ └── Verifies versions match, then creates & pushes tag │
│ │
│ 4. GitHub Actions workflow runs automatically │
│ └── Verifies versions, builds, and publishes release │
└─────────────────────────────────────────────────────────────────┘
Pre-Release Checklist¶
Before creating any release, complete these steps:
1. Check Release Status¶
See which components have unreleased changes:
This shows the number of commits since the last release for each component.
2. Review Changes¶
View the specific changes for the component you're releasing:
# For MCP Server
just release::changes-since mcp-server
# For Workbench
just release::changes-since workbench
3. Run All Quality Checks¶
Ensure all tests and checks pass:
For a more thorough check including integration tests:
4. Update Release Notes¶
Each component has its own RELEASE_NOTES.md file. Release workflows automatically extract the relevant section for GitHub Releases.
Release Notes Files¶
| Component | Release Notes File |
|---|---|
| MCP Server | src/freecad_mcp/RELEASE_NOTES.md |
| Robust MCP Bridge Workbench | freecad/RobustMCPBridge/RELEASE_NOTES.md |
Draft Release Notes¶
Use the draft-notes command to generate a starting point from conventional commits:
# Generate draft notes for a component
just release::draft-notes mcp-server
just release::draft-notes workbench
This categorizes commits by type (feat, fix, refactor, etc.) to help you write the release notes.
Release Notes Format¶
Add a new version section at the top of the component's RELEASE_NOTES.md:
## Version X.Y.Z (YYYY-MM-DD)
Release notes for changes between vA.B.C and vX.Y.Z.
### Added
- New feature description
### Changed
- Changed behavior description
### Fixed
- Bug fix description
Important: The version header format must match exactly for the GitHub Release workflow to extract it:
- Format:
## Version X.Y.Z (YYYY-MM-DD) - Example:
## Version 1.0.0 (2026-01-15)
The release workflows automatically extract the version section and include it in the GitHub Release body.
Releasing Each Component¶
MCP Server Release¶
The MCP Server is the main Python package. It uses setuptools-scm to derive version from git tags at build time, so there's no version bump step needed.
# 1. Ensure all changes are committed
git status # Should show clean working tree
# 2. Update src/freecad_mcp/RELEASE_NOTES.md and commit
git add src/freecad_mcp/RELEASE_NOTES.md
git commit -m "docs: update release notes for MCP Server v1.0.0"
# 3. Create and push the release tag
just release::tag-mcp-server 1.0.0
What happens automatically:
- GitHub Actions validates the tag format
- Builds Python wheel and source distribution (version from tag)
- Tests wheel installation on Ubuntu, macOS, and Windows
- Builds and tests a multi-architecture Docker image (amd64 + arm64)
- Promotes the tested Docker image to
spkane/freecad-robust-mcp:1.0.0 - Publishes to PyPI (or TestPyPI for alpha, beta, and rc versions)
- Creates GitHub Release with wheel and tar.gz artifacts
Before tagging, confirm the cross-platform authentication workflow has passed against its freshly built, isolated wheel, including the sketch operations reported in issue #87. Each component release also runs the CAM artifact gate before publication: it installs the exact candidate artifact, builds the other component from the same tagged commit, pairs them, and exercises an actual FreeCAD CAM job. The server release uses the exact wheel to be published; the workbench release uses the exact zip to be published. The companion artifact built in either workflow is not itself published by that workflow.
The gate also checks an actual pre-CAM workbench and simulates older and newer CAM interfaces, requiring clear errors. Server and workbench release workflows share a concurrency group. Only one runs at a time; GitHub's default queue keeps one pending run, so another queued run replaces it. Wait for the active release to finish before pushing the next component tag. Check canceled runs and rerun them after the active release finishes. Do not manually publish a component outside these workflows.
After the artifact gate succeeds, a release carries cam-gate-v1.json, which binds its tag, asset name, and SHA-256 to the tested artifact. Server releases are staged as a GitHub draft before Docker promotion or PyPI publication; the workbench archive is staged before its GitHub Release is published. A remaining CAM draft blocks subsequent releases. Investigate the actual PyPI, Docker and GitHub states before recovering a partial release; do not delete the draft just to bypass this check. A CAM-capable published tag without a receipt also blocks publication until investigated.
After publication, CI opens an inventory PR against current main using the published GitHub Release metadata and receipt. Review the asset names and SHA-256 digests and merge it before tagging the next component. The next release fails if a gated release is absent or a pinned asset has changed. It also fails if its tagged inventory differs from current main; create its tag from the updated branch. This check prevents an unmerged inventory PR from silently reducing coverage. The gate then exercises every listed release against the opposite candidate. The workflows recheck the inventory before publishing each component, but those checks cannot lock merges to main. Do not merge inventory or support-policy changes while a component release is running.
The support policy includes every CAM-gated component release, including prereleases, until a reviewed PR moves its tag into retired_servers or retired_workbenches with a reason. Retiring a release is a deliberate support change; do not simply remove its entry. An incompatible future interface needs an explicit compatibility-policy change before release.
The inventory-update job needs contents: write and pull-requests: write and repository settings must allow GitHub Actions to create pull requests. If PR creation is disabled, the release remains published but the job fails with the branch name; create the PR from that branch or enable the setting and retry. Pull request workflows triggered by GITHUB_TOKEN require a maintainer to approve the workflow runs. Alternatively, an appropriately scoped GitHub App token can trigger them without that approval. Merging the inventory PR remains a reviewed maintainer action.
PyPI and TestPyPI use separate Trusted Publisher registrations. In each registry, verify the publisher for freecad-robust-mcp names GitHub owner spkane, repository freecad-addon-robust-mcp-server, workflow mcp-server-release.yaml, and the corresponding environment (pypi or testpypi). These registry-side settings cannot be verified by GitHub Actions alone. A successful registration check is not a substitute for a TestPyPI candidate publication and clean install.
Pre-release versions:
# Alpha (goes to TestPyPI only)
just release::tag-mcp-server 1.0.0-alpha.1
# Beta (goes to TestPyPI only)
just release::tag-mcp-server 1.0.0-beta.1
# Release candidate (goes to TestPyPI only)
just release::tag-mcp-server 1.0.0-rc.1
Robust MCP Bridge Workbench Release¶
The workbench is a FreeCAD addon that provides the Robust MCP Bridge GUI.
# 1. Bump version in source files
just release::bump-workbench 1.0.0
# 2. Review and commit the changes
git diff # Review changes
git add -A
git commit -m "chore: bump workbench to 1.0.0"
# 3. Create and push the release tag
just release::tag-workbench 1.0.0
Files updated by bump-workbench:
freecad/RobustMCPBridge/freecad_mcp_bridge/__init__.py(__version__)package.xml(workbench section:<version>and<date>)
What happens automatically:
- GitHub Actions validates the tag format
- Verifies version in source files matches tag
- Creates archive (tar.gz and zip)
- Creates GitHub Release with the archives
Verifying a Release¶
Check GitHub Actions¶
After pushing a tag, monitor the release workflow:
- Go to GitHub Actions
- Find the workflow run triggered by your tag
- Verify all steps complete successfully
Verify Published Artifacts¶
For MCP Server:
# Check PyPI
pip index versions freecad-robust-mcp
# Check Docker Hub
docker pull spkane/freecad-robust-mcp:1.0.0
For Workbench:
- Check the GitHub Releases page for the archive downloads
- Verify the
package.xmlversion was updated
Managing Release Tags¶
List All Tags¶
# All tags grouped by component
just release::list-tags
# Latest version of each component
just release::latest-versions
Delete a Tag (If Needed)¶
If a release has issues and needs to be removed:
Deleting Published Releases
Deleting a tag does not unpublish from PyPI or Docker Hub. Contact the respective registries to remove published artifacts if necessary.
Preview a Release (Dry Run)¶
To see what a release tag would look like without creating it:
Troubleshooting¶
Release Workflow Failed¶
- Check the GitHub Actions logs for the specific error
- Common issues:
- Invalid version format (must be semver)
- Version mismatch between source files and tag
- A mismatched Trusted Publisher registration or missing Docker credentials
- Tests failing during the release build
Version Mismatch Error¶
If the workflow fails with "Version mismatch":
# You forgot to bump the version before tagging
# Delete the tag
just release::delete-tag <full-tag-name>
# Bump the version
just release::bump-<component> X.Y.Z
# Commit the changes
git add -A && git commit -m "chore: bump <component> to X.Y.Z"
# Create the tag again
just release::tag-<component> X.Y.Z
Tag Already Exists¶
If you try to create a tag that already exists:
# Delete the existing tag first
just release::delete-tag robust-mcp-server-v1.0.0
# Then create the new tag
just release::tag-mcp-server 1.0.0
Wrong Version Released¶
- Delete the tag:
just release::delete-tag <tag> - Create a new release with the correct version
- For PyPI: you cannot re-upload the same version; increment the patch version instead
Release Cadence Recommendations¶
- MCP Server: Release when there are significant new features or important bug fixes
- Workbench: Release in sync with server changes that affect the bridge protocol
Quick Reference¶
# Check what needs releasing
just release::status
# View changes for a component
just release::changes-since mcp-server
# Draft release notes from commits
just release::draft-notes mcp-server
just release::draft-notes workbench
# Bump versions (for workbench only)
just release::bump-workbench 1.0.0
# Commit version changes
git add -A && git commit -m "chore: bump <component> to X.Y.Z"
# Create releases (verifies versions, creates and pushes tag)
just release::tag-mcp-server 1.0.0
just release::tag-workbench 1.0.0
# List existing releases
just release::list-tags
just release::latest-versions
# Extract changelog for a version (used by CI)
just release::extract-changelog mcp-server 1.0.0
# Delete a tag if needed
just release::delete-tag <full-tag-name>