Releases are automatic: pushing a version bump to main makes
.github/workflows/release.yml tag it and create a GitHub Release, which
dispatches publish.yml to push the package to PyPI and then to the official
MCP registry (via GitHub OIDC, no
stored token). There is no manual release step — which means the version bump
itself is the point of no return.
The registry step runs after PyPI, so the registry never advertises a package
version that failed to upload. Note what it does not protect: by the time it
runs, the tag, the GitHub Release, and the PyPI upload have all happened. The
version-surface gate that actually guards a release is the pre-tag
check_versions.py in release.yml.
GitHub CI runs on Ubuntu with no Apple Music account and no tokens, so it can only test the mocked logic. The catalog→library/playlist add, folders, move, and ratings — the core of this tool — run as live mutations against a real, signed-in account. CI structurally cannot validate it. That gap is exactly how issue #28/#37 shipped, and how the branch-A "DELETE is broken on the public host" bug reached a release with every unit test still green.
So the gate is local and manual, and it is mandatory before a version bump.
On a machine with tokens for a signed-in Apple Music account (active
subscription) — a developer token (applemusic-mcp generate-token, or a
harvested one) plus a media-user-token (applemusic-mcp signin):
make preflightThis runs, in order:
- a version-surface check (
scripts/check_versions.py) — the repo carries the version in seven places, and two of them do not follow an edit topyproject.toml:server.json(nothing automates it; it once sat at1.0.0while the package shipped 0.16.0) anduv.lock(only updates whenuvruns, so a bump can commit without it).release.ymlruns the same check before tagging, so a mismatched release is blocked rather than published, - the fast/mocked suite (same as CI),
- a live-environment check (developer token + media-user-token + catalog reachable) that fails loudly instead of letting the live tests silently skip,
- the live API integration suite (
tests/test_live_integration.py,TEST_API=1) against your real account — it creates/deletes_UI_TEST_…playlists and folders, clears any rating it sets, and removes the probe song it adds. Fully self-cleaning — no residue.
It refuses to pass if the core live tests skipped rather than passed (a half-ready environment that skips everything is a false green).
This gate is cross-platform — it no longer needs macOS or the local Music.app.
make preflight validates the API engine. It does not exercise the native
Music.app UI-automation paths (catalog deep-link playback via CoreGraphics, UI
catalog search, transport controls) — those are version-fragile and have no CI
coverage. Any release that changes applescript.py UI logic or the native
playback flow must also pass make preflight-ui on both support
machines:
- the iMac (macOS 15 / Music 1.5)
- the mini (macOS 26 / Music 26)
Run it from an unlocked, active console session (Screen Sharing's GUI login, not SSH — synthetic mouse clicks need a real WindowServer session), signed into Apple Music, with Accessibility granted. It plays muted and pauses after each test. Like the API gate, it refuses to read as green if the core playback tests skipped (a locked screen skips everything — that is not validation).
- Bump the version in all three places (they must match or the lock/release
drifts):
pyproject.toml,src/applemusic_mcp/__init__.py, and theapplemusic-mcpentry inuv.lock. - Add a
CHANGELOG.mdentry under the new version. - Update
SKILL.mdif any user-facing behavior changed (errors, setup, add/lookup flow). - Merge to
main. The release + PyPI publish fire automatically.
Don't bump the version. A docs-only or tooling-only change that touches no
runtime behavior can skip the live gate (note that in the PR), but anything that
touches applescript.py or the add/resolve/playback flow in server.py must
pass make preflight first.