chore: release v2.6.0#135
Conversation
Aggregates the changes that have landed on `main` since v2.5.1: - fix(windows): UTF-8 stdin encoding on PowerShell write paths so non-ASCII characters (em dash, curly quotes, non-Latin scripts) are no longer corrupted by `clipboard_copy` / `clipboard_copy_markdown`. Found and fixed via a QEMU Windows guest reproduction (#129, #131). - feat(cli): `--version`, `--help`, `--check` flags on the `mcp-clipboard` binary so users can verify their install before any MCP-host wiring (#130, #134). - ci: `github-release` job in `publish.yml` auto-creates the GitHub Release on tag push, with notes pulled from this CHANGELOG section. First firing of this workflow lands with v2.5.2 (#127). - docs: README `## Setup` rewritten into a five-step quick-start that acknowledges both `pipx` and `uv` as install runners; platform status corrected to reflect that Windows has been exercised on a QEMU guest as of v2.5.x (#130, #134). Three-file release commit per repo convention: `pyproject.toml` (version bump), `CHANGELOG.md` (date stamp + new `## [Unreleased]` shell), and `server.json` (synced via `scripts/sync-server-json.py`). Local verification: 610 tests pass, ruff clean, mypy clean, server.json in sync, wheel builds cleanly to `dist/mcp_clipboard-2.5.2-py3-none-any.whl`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
cmeans
left a comment
There was a problem hiding this comment.
QA review — release PR #135 (round 1)
Verdict: QA Failed for one substantive finding on the bump level. The diff itself is clean and the CHANGELOG content is accurate; the issue is that the version label doesn't match the content per semver and project precedent.
F1 (substantive) — version bump should be 2.6.0 (minor), not 2.5.2 (patch)
The Unreleased CHANGELOG content being stamped under this release contains two ### Added entries:
- Added — CLI flags
--version,--help,--checkon themcp-clipboardbinary (#134, closes #130). User-visible new functionality on the binary's interface;mcp-clipboard --checkis a brand-new invocable command. - Added —
github-releasejob inpublish.ymlauto-creates the GitHub Release on tag push (#127, closes #126). Internal CI infra, but conventionally CHANGELOGd as### Addedper Keep a Changelog.
Per semver §6: "MINOR version when you add functionality in a backwards-compatible manner". The Added entries here are by definition backwards-compatible feature additions, which is exactly what triggers a MINOR bump.
Project precedent (this repo's own pattern):
| Release | Bump | What landed | Convention |
|---|---|---|---|
v2.5.0 (2026-05-05) |
minor (2.4.0 → 2.5.0) | 3 PRs aggregating ### Added + ### Changed (registry registration, clipboard_copy_markdown, PRIMARY selection) |
minor for any Added |
v2.5.1 (2026-05-05) |
patch (2.5.0 → 2.5.1) | Pure README mcp-name token fix; only ### Fixed |
patch for fix-only |
v2.5.2 (this PR, proposed) |
patch (2.5.1 → 2.5.2) | Fixed + 2× Added + 2× Changed |
mismatched — should be minor |
The v2.5.0 release is the direct precedent: same shape (Added + Changed + Fixed entries aggregated into one release) → minor bump. Following that pattern, this release should be v2.6.0.
Why this matters concretely:
- A consumer pinning
mcp-clipboard~=2.5.0would silently auto-upgrade across this release because~=2.5.0covers2.5.x. They'd get--checkas a new invocable surface they didn't ask for and didn't see in their dependency declaration. With2.6.0, that pin would correctly not match — they'd opt in via~=2.6.0once they're ready for the new surface. - The MCP Server registry's
version: "2.5.2"would tell registry consumers "patch-level only; no behavior change." That's wrong. - The PyPI release page would communicate the same wrong signal.
The cost of getting this right now is small (rename branch + update 3 files + update PR body); the cost of getting it wrong is a permanent versioning lie that future consumers can't unscrew without us cutting a follow-up 2.6.0 that adds nothing new.
Suggested fix (mechanical):
- Branch rename:
release/v2.5.2→release/v2.6.0(git branch -m release/v2.5.2 release/v2.6.0, push the new branch, delete the old remote ref). Or just open a fresh release PR if the rename is fiddly. pyproject.tomlline 7:version = "2.5.2"→version = "2.6.0".server.jsonlines 6 and 15: both"version": "2.5.2"→"version": "2.6.0". (Keep them in lockstep —scripts/sync-server-json.pywill catch drift.)CHANGELOG.md: change the new heading from## [2.5.2] - 2026-05-07to## [2.6.0] - 2026-05-07.- PR title:
chore: release v2.5.2→chore: release v2.6.0. - PR body: replace
v2.5.2/2.5.2withv2.6.0/2.6.0in the title, "What's in this release" header context, and all four post-merge test-plan checkboxes (lines 46-50). - Re-run
python scripts/sync-server-json.py --checklocally to confirm the three files agree at2.6.0.
What's clean
- Diff scope is exactly 5 added / 3 deleted across the three expected files (pyproject.toml, server.json, CHANGELOG.md). No drift.
- CHANGELOG content accurately mirrors the Unreleased section. Each entry traces back to a merged PR (#131 / #134 / #127). The
## [Unreleased]shell is correctly preserved at the top per Keep a Changelog convention. server.jsonsync is consistent: top-level andpackages[0]versions both updated together.- CI is fully green on this commit: all 11 actual checks pass (lint, typecheck, test ×3, integration-x11, codecov/patch, on-push, qa-approved, version-sync, validate-server-json).
- Local verification numbers in the PR body are consistent with what I observed on the post-#134 main: pytest 610 / 19 deselected / 5 xfailed, ruff/format/mypy/sync clean.
uv.lockexclusion rationale is correct —.gitignore:17does list it (verified) and that matches this repo's convention even though sibling projects (e.g.pypi-winnow-downloads) track it.- American-English / em-dash sweep on the new section is clean (verified via
git diff origin/main..HEAD -- CHANGELOG.md | grep "—"— only matches are pre-existing prose dashes preserved from the Unreleased entries).
Test-plan checkboxes
Leaving all five unticked. Three of them embed 2.5.2 / v2.5.2 in their text; if the version moves to v2.6.0 per F1, those bullets need to be re-typed and re-verified, so ticking them now would add work for Dev to un-tick. Once the new release branch is pushed, I'll re-verify on the new head.
Round 1 → Round 2
Single mechanical fix in three files plus the branch/PR-body rename. After Dev pushes the new release branch, Round 2 will: re-confirm version-sync and validate-server-json CI gates pass at 2.6.0, re-run pytest/ruff/mypy locally on the new head, confirm the CHANGELOG heading matches the version, sweep the PR body for residual 2.5.2 references, and tick the (re-typed) test-plan checkboxes that are pre-tag.
Applying QA Failed.
QA reviewer flagged the bump level: this release contains two `### Added` entries (CLI flags --version/--help/--check via #134, and the github-release job via #127), both of which are backwards-compatible feature additions. Per semver §6, that's a MINOR bump trigger, not patch. This repo's own precedent confirms the convention: - v2.5.0 (2026-05-05): minor bump for the same release shape (Added + Changed + Fixed aggregated). Direct precedent. - v2.5.1 (2026-05-05): patch bump for fix-only release. - v2.5.2 (proposed): mismatched -- patch bump for an Added-bearing release. Concrete consumer impact of getting it wrong: a `~=2.5.0` pin would silently auto-upgrade across this release and pull in the new --check surface unannounced. With 2.6.0, that pin correctly does not match. Mechanical fix: - pyproject.toml: version "2.5.2" -> "2.6.0" - server.json: top-level + packages[0] versions synced to 2.6.0 via scripts/sync-server-json.py - CHANGELOG.md: heading "## [2.5.2] - 2026-05-07" -> "## [2.6.0] - 2026-05-07" Branch name `release/v2.5.2` is now a cosmetic mismatch -- preserved to keep PR #135's QA review history intact rather than re-opening a fresh PR. The squash-merge will collapse this commit into the release commit and the branch will be deleted. Local verification at this commit: - python scripts/sync-server-json.py --check: in sync at 2.6.0 - uv run pytest -q: 610 passed, 19 deselected, 5 xfailed - uv run ruff check src tests: clean - uv run mypy src: clean - uv build --wheel: dist/mcp_clipboard-2.6.0-py3-none-any.whl built cleanly - grep "2.5.2" across pyproject.toml + server.json + CHANGELOG.md: zero hits Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
QA Round 1 → Round 2 responseF1 (semver bump level) — addressed at You're right on both the semver argument and the project precedent. Two Mechanical changes on this commit:
PR title and body updated to Branch name is now a cosmetic mismatch ( Local verification at
Label reset to Ready for QA. |
cmeans
left a comment
There was a problem hiding this comment.
QA review — release PR #135 (round 2)
Verdict: Zero new findings. Recommending Ready for QA Signoff.
Round 1 F1 — fixed at e9d7553
Single commit chore(release): bump 2.5.2 -> 2.6.0 per QA Round 1 F1 (semver minor). The diff is exactly the requested version swap in all four required surfaces (3 files + PR title/body):
| Surface | Before | After |
|---|---|---|
pyproject.toml:7 |
version = "2.5.1" |
version = "2.6.0" |
server.json:6 (top-level) |
"version": "2.5.1" |
"version": "2.6.0" |
server.json:15 (packages[0].version) |
"version": "2.5.1" |
"version": "2.6.0" |
CHANGELOG.md:7 (new section heading) |
## [2.5.2] - 2026-05-07 |
## [2.6.0] - 2026-05-07 |
| PR title | chore: release v2.5.2 |
chore: release v2.6.0 |
| PR body (10 occurrences across summary, diff scope, tag-push effects, local verification, test plan) | mix of 2.5.2 / v2.5.2 |
all 2.6.0 / v2.6.0 |
grep -nE "2\.5\.2|v2\.5\.2" against the current PR body returns zero hits. The remaining 2.5.1 references in the body (e.g., pyproject.toml: "version = "2.5.1" → "2.6.0") are intentional — they describe the pre-release version being bumped FROM, which is correct.
Verification on e9d7553
| Check | Result |
|---|---|
pyproject.toml version |
2.6.0 ✓ |
server.json top-level + packages[0] |
both 2.6.0 ✓ |
CHANGELOG.md heading position |
## [Unreleased] shell preserved at line 5; ## [2.6.0] - 2026-05-07 at line 7 (Keep a Changelog convention) ✓ |
uv run pytest -q |
610 passed, 19 deselected, 5 xfailed |
uv run ruff check src tests scripts |
All checks passed |
uv run ruff format --check src tests scripts |
11 files already formatted |
uv run mypy src |
clean (4 source files) |
uv run python scripts/sync-server-json.py --check |
in sync at 2.6.0 |
uv build --wheel |
dist/mcp_clipboard-2.6.0-py3-none-any.whl builds cleanly |
CI on e9d7553 |
11 actual checks all green (lint, typecheck, test ×3, integration-x11, codecov/patch, on-label, qa-approved, version-sync, validate-server-json) |
Diff scope
Still exactly 5 added / 3 deleted across the same three files (pyproject.toml, server.json, CHANGELOG.md). uv.lock correctly absent from the diff per .gitignore:17. No drift outside the release-bump triad.
Test-plan checkboxes
- CI passes on this PR — all 11 actual checks green on
e9d7553. Ticked. - Post-merge
publish.ymljobs all green — gated on tag push, not verifiable pre-merge. Leaving unticked. - PyPI shows
mcp-clipboard==2.6.0— gated onpublish-pypirunning. Leaving unticked. - GitHub Releases shows
v2.6.0— gated ongithub-releaserunning (this is the first real firing of the job ported in #127, so it's the most worth-watching of the four post-tag verifications). Leaving unticked. - MCP Server registry shows
2.6.0— gated onpublish-registryrunning. Leaving unticked.
Note on the leftover branch name
The branch name is still release/v2.5.2 (Dev kept the existing branch and updated its content rather than renaming). I'm not flagging this as a finding — branch names are developer-side artifacts that don't surface anywhere consumers see (tag, PyPI, registry, GitHub Release page, squash-merge commit message will all carry 2.6.0), and head branches typically get deleted post-merge. Mentioning here only so the trail is documented.
Round 2 verdict
This is a clean version-bump fix. The release content (CHANGELOG entries, server.json sync, pyproject.toml version) was already correct in Round 1 — only the version label needed correcting, which is exactly what e9d7553 did. The four post-tag-push verifications remain Dev's job to confirm green after the squash-merge and git tag v2.6.0 push.
Applying Ready for QA Signoff.
Release PR for v2.6.0.
What's in this release
--version,--help,--checkon themcp-clipboardbinary so users can verify their install before any MCP-host wiring.--checkexits 1 with a stderr diagnostic on failure so it doubles as a CI smoke check.github-releasejob inpublish.ymlauto-creates the GitHub Release on tag push, with notes pulled from this CHANGELOG section. v2.6.0 is the first firing of this workflow on a real release.## Setuprewritten into a five-step quick-start. Step 1 acknowledges that mcp-clipboard is a regular PyPI package and links to the official pipx and uv install docs (the project advertises both runners in its install-counts badges). Steps 3, 4, 5 show bothpipx runanduvxforms. Windows tip on Claude Desktop's environment caching is preserved and made runner-agnostic.## Setupcallout, README## Limitationsbullet,CLAUDE.mdConventions). The previous "Windows untested on real hardware" boilerplate is no longer accurate; v2.5.x exercised the Windows code paths end-to-end on a QEMU Windows guest, surfacing #129. X11 and macOS still hold "complete with unit tests but unverified beyond that".Diff scope
Standard three-file release commit per repo convention:
pyproject.toml:version = "2.5.1"→"2.6.0"server.json: top-levelversionandpackages[0].versionsynced viascripts/sync-server-json.py(theversion-syncandvalidate-server-jsonCI gates from chore(release): register with MCP Server registry via server.json + publish-registry workflow #120 will fail PRs that drift)CHANGELOG.md: stamp## [Unreleased]content as## [2.6.0] - 2026-05-07; leave a fresh empty## [Unreleased]shell at the topuv.lockis intentionally not in the diff (gitignored on this repo per.gitignore:17; different frompypi-winnow-downloadswhich tracks it).Tag-push effects
Once squash-merged and tagged
v2.6.0:publish.yml'spublish-pypijob pushes the wheel to PyPI.publish.yml'svalidate-server-jsonandpublish-registryjobs (added in chore(release): register with MCP Server registry via server.json + publish-registry workflow #120) update the MCP Server registry entry to2.6.0.publish.yml'sgithub-releasejob (new in ci: auto-create GitHub Release on tag push (port mcp-synology github-release job) #127) creates the GitHub Release page with notes auto-extracted from the## [2.6.0]CHANGELOG section.This is the first release where the
github-releasejob actually fires on a tag push (the prior backfilled releases were created manually withgh release create). If anything goes wrong with thegithub-releasejob, the PyPI publish is unaffected — the job is idempotent and gatedneeds: publish-pypi, so a re-run after a fix will edit-in-place rather than failing on duplicate-tag-create.Local verification at this commit
uv run pytest -q: 610 passed, 19 deselected (integration-marked, run inintegration-x11CI), 5 xfailed.uv run ruff check src tests: All checks passed.uv run ruff format --check src tests: 10 files already formatted.uv run mypy src: no issues, 4 source files.python scripts/sync-server-json.py --check: in sync at2.6.0.uv build --wheel:dist/mcp_clipboard-2.6.0-py3-none-any.whlbuilds cleanly.## [2.6.0]section: clean.## [2.6.0]section: clean.Test plan
publish.ymlruns all four jobs green:publish-pypi,validate-server-json,publish-registry,github-release.mcp-clipboard==2.6.0as the latest release (https://pypi.org/project/mcp-clipboard/).v2.6.0release with notes drawn from this CHANGELOG section (https://github.com/cmeans/mcp-clipboard/releases).io.github.cmeans/mcp-clipboardat version2.6.0,isLatest=true.