Skip to content

chore(release): register with MCP Server registry via server.json + publish-registry workflow#120

Merged
cmeans-claude-dev[bot] merged 2 commits into
mainfrom
chore/mcp-registry-114
May 6, 2026
Merged

chore(release): register with MCP Server registry via server.json + publish-registry workflow#120
cmeans-claude-dev[bot] merged 2 commits into
mainfrom
chore/mcp-registry-114

Conversation

@cmeans-claude-dev
Copy link
Copy Markdown
Contributor

@cmeans-claude-dev cmeans-claude-dev Bot commented May 6, 2026

Closes #114. Five-file registry-publish setup ported from mcp-synology. No runtime code change.

Files added

File Role
server.json (root) Registry manifest. io.github.cmeans/mcp-clipboard name (the GitHub-OIDC namespace), starts at version 2.4.0 to match what just shipped to PyPI.
scripts/sync-server-json.py Single-source-of-truth sync from pyproject.toml's [project].version to server.json's two version fields (top-level + packages[0]). Stdlib only — runs before any uv sync in CI. --check mode exits 1 on drift.
.github/actions/install-mcp-publisher/action.yml Composite action that downloads a pinned mcp-publisher (default v1.7.6). The pin is load-bearing: the registry rolled out a new GitHub-OIDC audience on 2026-04-30; earlier publisher releases (incl. v1.5.0) fail with 401 invalid audience: expected https://registry.modelcontextprotocol.io, got [mcp-registry].

Workflow changes

ci.yml gains two jobs:

  • version-sync — runs python scripts/sync-server-json.py --check. Fails PRs that bump pyproject.toml without re-running the sync.
  • validate-server-json — runs ./mcp-publisher validate server.json against the live registry schema. Catches schema drift at PR time rather than at tag-push time.

publish.yml gains:

  • validate-server-json (release-time backstop). publish-pypi is now needs: [build, validate-server-json]. Job renamed from publishpublish-pypi for symmetry with the new publish-registry sibling. (Workflow runs only on tag pushes, no PR branch protection references this job name, so the rename is safe.)
  • publish-registry (runs after publish-pypi). Uses GitHub OIDC token exchange — no API key, no secret. Idempotent on duplicate-version errors so a partially-failed tag rerun is a no-op; if the upstream error text changes, falls through to exit $status and surfaces the real error rather than silently swallowing it.

Why this matters

PyPI publishes are irreversible per-version: a yanked release still occupies the version slot. If the registry schema changes between PR-merge and tag-push and we discover it only on the publish run, PyPI ships fine but the registry leg fails — leaving a discoverable PyPI release that isn't in the MCP registry, with no way to fix without bumping the version. Both gates (ci.yml PR-time + publish.yml release-time) close that window.

Reference for the same pattern landing in mcp-synology:

Verification

  • python scripts/sync-server-json.py --checkserver.json in sync with pyproject.toml (2.4.0).
  • uv run pytest -q: 548 passed, 16 deselected, 5 xfailed (no runtime code changed).
  • uv run ruff check src/ tests/: clean.
  • New CI jobs (version-sync, validate-server-json) will run on this PR and serve as the first smoke test of the gates themselves.

First-run plan

server.json starts at 2.4.0 (the just-shipped version). After this PR merges:

  • The next release tag (whether v2.4.1, v2.5.0, or whatever comes next) fires publish.ymlvalidate-server-jsonpublish-pypipublish-registry.
  • publish-registry registers io.github.cmeans/mcp-clipboard with the registry for the first time.
  • Subsequent releases update the entry in place.

Optionally we could re-tag v2.4.0 after merge to register immediately — publish-pypi will hit a duplicate-version no-op on PyPI, and publish-registry will register the entry. Not strictly necessary; can wait for the next bump.

Test plan

  • CI green across lint, typecheck, test (3.11/3.12/3.13), integration-x11, version-sync, validate-server-json.
  • First post-merge tag-push lands all three publish jobs green.
  • curl -s 'https://registry.modelcontextprotocol.io/v0/servers?search=mcp-clipboard' returns a hit.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

…ublish-registry workflow

Adds the five-file registry-publish setup ported from mcp-synology
(reference: cmeans/mcp-synology PRs #16, #79, #89, #92, issue #44).

New files:

- server.json — registry manifest at the repo root. Carries the
  io.github.cmeans/mcp-clipboard name (the GitHub-OIDC namespace) and
  duplicates pyproject.toml's version in two fields (top-level and
  packages[0]) per the registry schema.
- scripts/sync-server-json.py — single-source-of-truth sync from
  pyproject.toml's [project].version into server.json's two version
  fields. Stdlib only (so CI can run it before any uv sync). Two CLI
  surfaces: bare invocation rewrites server.json in place; --check
  exits 1 on drift for the CI gate.
- .github/actions/install-mcp-publisher/action.yml — composite action
  that downloads a pinned mcp-publisher (default v1.7.6). The pin is
  load-bearing: the registry rolled out a new GitHub-OIDC audience on
  2026-04-30, and earlier publisher releases (incl. v1.5.0) send the
  legacy audience and fail authentication with 401 invalid audience.

Workflow changes:

- ci.yml gains two jobs:
  - version-sync — runs `python scripts/sync-server-json.py --check`,
    failing PRs that bump pyproject.toml without re-running the sync.
  - validate-server-json — runs `./mcp-publisher validate server.json`
    against the live registry schema, catching schema drift at PR time
    rather than at tag-push time. Critical because PyPI publishes are
    irreversible per-version, so a half-publish where PyPI accepts but
    the registry leg fails on schema drift would require a yank-and-bump
    cleanup.
- publish.yml gains:
  - validate-server-json (release-time backstop in case the registry
    schema changes between merge and tag-push). publish-pypi is now
    `needs: [build, validate-server-json]` (renamed from `publish` to
    `publish-pypi` for symmetry with the new sibling).
  - publish-registry (runs after publish-pypi, since the registry
    validates the referenced PyPI package+version before accepting).
    Uses GitHub OIDC token exchange — no API key. Idempotent on
    duplicate-version errors so a partially-failed tag rerun is a no-op
    rather than a crash; if the upstream error text changes, this falls
    through to `exit $status` and surfaces the real error rather than
    silently swallowing it.

CHANGELOG entry under [Unreleased] / Added.

server.json starts at 2.4.0 (the just-shipped version). The first
post-merge release tag will register io.github.cmeans/mcp-clipboard
for the first time; subsequent releases update the entry in place.

No runtime code change. 548 tests still pass; ruff clean; sync-script
--check passes.

Closes #114.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA CI Failed CI failed — dev needs to fix and removed Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels May 6, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

PR #120 CI Round 1: validate-server-json failed with

    server returned status 422: "expected length <= 100"
    body.description value: "MCP server that reads and writes the
    system clipboard — tables, text, code, JSON, URLs, images, and
    more." (107 chars)

The MCP registry caps server.json's `description` at 100 chars; our
pyproject.toml / README / FastMCP-instructions copy is 107 chars and
gets used everywhere else as-is. The cap only applies to the registry
manifest, so the fix is server.json-local: drop the "MCP server that"
prefix (the registry context already implies that), keep the value-rich
tail. Down to 87 chars and validates clean against the live registry
schema.

Other description copy (pyproject.toml, README, instructions/server.md)
intentionally left longer — pip search results, FastMCP startup banner,
and the GitHub repo description are not subject to the registry cap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA Ready for QA Dev work complete — QA can begin review and removed CI Failed CI failed — dev needs to fix Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels May 6, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@cmeans cmeans added the QA Active QA is actively reviewing; Dev should not push changes label May 6, 2026
@github-actions github-actions Bot removed the Ready for QA Dev work complete — QA can begin review label May 6, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA Review — Round 1

Verdict: Pass — zero findings.

Clean port of the mcp-synology registry-publish setup. Five files added (server.json, scripts/sync-server-json.py, .github/actions/install-mcp-publisher/action.yml, plus two workflow patches) + a CHANGELOG ### Added entry. No runtime code touched.

Issue #114 acceptance

Item Status
server.json (root, registry manifest) Present, namespaced as io.github.cmeans/mcp-clipboard, schema 2025-12-11, version 2.4.0 (matches just-shipped PyPI), packages[0] = pypi/mcp-clipboard/2.4.0/stdio. Description 87 chars (under the registry's 100-char cap — addressed by follow-up commit 5ab1a56).
scripts/sync-server-json.py (stdlib only, --check mode) Present and clean. tomllib → [project].version, propagates to top-level version + each packages[i].version, preserves 2-space JSON indent + trailing newline. Drift simulation (manually setting top_level to 99.0.0) → exit 1 with "server.json version drift detected. pyproject.toml = 2.4.0 top_level: '99.0.0'". In-sync run on the actual repo → exit 0 with "server.json in sync with pyproject.toml (2.4.0)".
install-mcp-publisher composite action Pinned to v1.7.6 (the audience-binding fix per registry PR #1229 deployed 2026-04-30); rationale comment in the action input default explains the v1.5.0 → v1.7.6 jump precisely. Cross-platform os/arch derivation handles linux/amd64 + linux/arm64.
ci.yml gains version-sync + validate-server-json Both jobs SUCCESS on this PR's CI run. version-sync uses stdlib python (no uv sync needed); validate-server-json uses the same composite action as release-time.
publish.yml gains release-time validate-server-json + publish-registry publish-pypi (renamed from publish) now needs: [build, validate-server-json]. Rename is safe — verified publish.yml triggers on push: tags: v* only (no PR runs), and main isn't branch-protected (404 on repos/.../branches/main/protection), so no required-check renames needed. publish-registry runs after publish-pypi, uses id-token: write for OIDC, and the duplicate-version idempotency block correctly falls through to exit $status for any non-matching error text rather than silently swallowing real failures.
CHANGELOG ### Added entry Present under [Unreleased], accurate description, (#120) - closes #114 link.
Schema/registry self-validation in CI validate-server-json against mcp-publisher v1.7.6 SUCCESS — proves the actual JSON passes the live registry schema.

Reference cross-check (mcp-synology pattern)

mcp-synology PR What it taught Picked up here?
#16 Initial server.json + first registration attempt ✓ Same shape
#44 Why validate-server-json runs at both PR-time and release-time ✓ Both gates present
#79 The v1.5.0 → v1.7.6 mcp-publisher bump after the OIDC audience mismatch ✓ Pinned to v1.7.6
#89 publish-pypi wired to needs: [build, validate-server-json] ✓ Same shape
#92 sync-server-json.py ergonomics (distinguish missing-[project] vs missing-version-field) ✓ Both errors distinguished (error: [project] section missing vs error: [project].version not found)

Verification (run locally on chore/mcp-registry-114 @ 5ab1a56)

Check Result
uv run pytest -q 548 passed / 16 deselected / 5 xfailed (matches PR body — no runtime change)
uv run ruff check src tests All checks passed
uv run ruff format --check src tests 10 files already formatted
uv run mypy src Success: no issues found in 4 source files
uv run ruff check scripts All checks passed (new file lints clean)
python3 scripts/sync-server-json.py --check server.json in sync with pyproject.toml (2.4.0)
Drift simulation (top_level=99.0.0) exits 1 with the correct diagnostic
server.json description length 87 chars (under 100-char registry cap)
actions/checkout@v6 etc. pin convention Floating major-version tags throughout existing ci.yml and publish.yml; new jobs match the convention. (mcp-synology has SHA-pinned via #94 but mcp-clipboard hasn't adopted that yet — out of scope for #114; follow-up if/when desired.)
publish.yml trigger surface on: push: tags: ['v*'] — no PR-time runs, so the publishpublish-pypi rename does not affect any branch-protection required-check name
CI on 5ab1a56 All green: lint, typecheck, test (3.11/3.12/3.13), integration-x11, codecov/patch, plus the new version-sync and validate-server-json SUCCESS

Test-plan checkbox status (post-review)

  • CI green across lint, typecheck, test (3.11/3.12/3.13), integration-x11, version-sync, validate-server-json — verified
  • First post-merge tag-push lands all three publish jobs green — post-merge step
  • curl -s 'https://registry.modelcontextprotocol.io/v0/servers?search=mcp-clipboard' returns a hit — post-merge step (will land on the next release tag)

Follow-up tickets

None for #114 itself. One observation worth surfacing to the user (not gating, not filing as an issue unless you want):

  • The description field in server.json (87 chars) is necessarily different from pyproject.toml's description (107 chars) because of the registry's 100-char cap. The sync script currently only syncs version, not description, so if the pyproject description ever evolves the registry one will silently stay frozen. This is the right call for #114 (description-sync is harder than version-sync because of the cap), and updating the registry description is rare, so flagging it just to keep it on your radar — no action needed.

Applying Ready for QA Signoff as the final act.

@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 6, 2026

QA Audit — PR #120 / round 1

  • Branch: chore/mcp-registry-114 @ 5ab1a56
  • 5 new files + workflow patches; 0 runtime code changes.
  • Verification re-run locally: pytest 548 passed / 16 deselected / 5 xfailed; ruff check src/tests/scripts + format + mypy clean; sync-server-json.py --check reports in-sync; drift simulation exits 1 correctly.
  • server.json shape correct, description 87 chars (under registry 100-char cap addressed by follow-up commit 5ab1a56).
  • validate-server-json PR-time gate SUCCESS in CI — proves the JSON passes the live registry schema against pinned mcp-publisher v1.7.6.
  • publishpublish-pypi rename is safe — publish.yml triggers on push: tags: v* only (no PR-time runs); main isn't branch-protected (404 on protection API), so no required-check name to update.
  • Floating-tag pin convention (actions/checkout@v6) consistent with the rest of ci.yml/publish.yml. (mcp-synology has SHA-pinned via chore: add dependabot version-update configuration #94; mcp-clipboard hasn't adopted that yet — out of scope for Register mcp-clipboard with the MCP Server registry (server.json + publish-registry workflow) #114; could be its own follow-up.)
  • 1 of 3 test-plan checkboxes ticked (CI); the other two are post-merge tag-push observations.
  • No findings; no follow-up tickets.

Applying Ready for QA Signoff as the final act.

@cmeans cmeans added Ready for QA Signoff QA passed — ready for maintainer final review and merge QA Approved Manual QA testing completed and passed and removed QA Active QA is actively reviewing; Dev should not push changes Ready for QA Signoff QA passed — ready for maintainer final review and merge labels May 6, 2026
@cmeans-claude-dev cmeans-claude-dev Bot merged commit 1a872e8 into main May 6, 2026
35 checks passed
@cmeans-claude-dev cmeans-claude-dev Bot deleted the chore/mcp-registry-114 branch May 6, 2026 02:48
@cmeans-claude-dev cmeans-claude-dev Bot mentioned this pull request May 6, 2026
4 tasks
cmeans-claude-dev Bot added a commit that referenced this pull request May 6, 2026
Release v2.5.0. Aggregates the four PRs since v2.4.0 (2026-05-05).

## Scope

### Added
- **#120 — register with the MCP Server registry** (closes #114). New
`server.json` (root) carries the registry manifest;
`scripts/sync-server-json.py` is the single-source-of-truth sync from
`pyproject.toml`'s `[project].version` to `server.json`'s two version
fields. New composite action `.github/actions/install-mcp-publisher`
pins `mcp-publisher` to `v1.7.6+` for the post-2026-04-30 OIDC audience
requirement. CI gains `version-sync` + `validate-server-json` jobs;
`publish.yml` gains a release-time `validate-server-json` gate (now
`needs:` of `publish-pypi`) and a new `publish-registry` job that runs
after `publish-pypi`. **This release is the first to fire
`publish-registry`, registering `io.github.cmeans/mcp-clipboard` for the
first time.** Subsequent releases update the entry in place.
- **#121 — `clipboard_copy_markdown` tool** (closes #109). Renders a
markdown source to HTML via `markdown-it-py` (with raw HTML escaped,
safe by construction) and writes both `text/html` (rendered) and
`text/plain` (the markdown source) to the clipboard. macOS and Windows
write both formats atomically (NSPasteboard / `DataObject`); Wayland and
X11 are single-MIME-per-call and write `text/html` only — Wayland's
`wl-copy` auto-advertises `text/plain` whose bytes are the rendered HTML
markup, X11 has no `text/plain` target. Adds `markdown-it-py>=3.0` as a
new runtime dependency (pure Python, ~250 KB, no native deps).
- **#122 — PRIMARY-selection support on read tools** (closes #110).
`clipboard_paste`, `clipboard_read_raw`, and `clipboard_list_formats`
now accept an optional `selection` argument (`"clipboard"` default,
`"primary"` for the X11 PRIMARY / Wayland primary middle-click
selection). macOS and Windows have no PRIMARY analog and return a clear
error if `"primary"` is passed. Public APIs `read_clipboard`,
`list_clipboard_formats`, and `read_clipboard_image` gained the same
`selection` parameter.

### Closed without engagement
- **#119 — SafeSkill drive-by scanner promotion.** Closed as duplicate
of an identical filing on cmeans/mcp-synology. Same auto-template hit
both repos; the methodology has gaps (`Files Scanned: 0` on Python
projects, then asks for a promotional badge).

## Verification

- `uv run pytest -q`: **599 passed**, 19 deselected, 5 xfailed.
- `uv run ruff check src/ tests/ scripts/`: clean.
- `uv run mypy src/`: clean.
- `python scripts/sync-server-json.py --check`: server.json in sync with
pyproject.toml (2.5.0).
- `uv build --wheel`: builds `dist/mcp_clipboard-2.5.0-py3-none-any.whl`
successfully.
- All four landing PRs were QA-approved on `main` with green CI before
this aggregation.

## Release commit shape

Three-file diff: `pyproject.toml` (2.4.0 → 2.5.0), `CHANGELOG.md`
(header roll, no other content changes), and `server.json` (synced via
`scripts/sync-server-json.py` per the new release flow introduced in
#120). The `version-sync` CI gate verifies the sync is correct before
merge.

## Tag plan

After this PR merges, push `v2.5.0` tag to trigger `publish.yml`:

1. `validate-server-json` — schema check against the live registry.
2. `publish-pypi` — uploads `mcp_clipboard-2.5.0-py3-none-any.whl` +
sdist to PyPI via OIDC trusted publisher.
3. `publish-registry` — registers `io.github.cmeans/mcp-clipboard` in
the official MCP registry. **First-run.**
4. (no `github-release` job yet — that's a future-port from mcp-synology
if/when it's worth the diff.)

The `[Unreleased]` section is now empty, ready for the next cycle.

## Test plan

- [x] CI green across `lint`, `typecheck`, `test (3.11/3.12/3.13)`,
`integration-x11`, `version-sync`, `validate-server-json`.
- [x] `uv build --wheel` succeeds locally on a clean checkout (verified
pre-PR).
- [ ] Tag push triggers `publish.yml` and the PyPI release lands.
- [ ] `publish-registry` job lands green on the first run; `curl -s
'https://registry.modelcontextprotocol.io/v0/servers?search=mcp-clipboard'`
returns a hit afterward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Co-authored-by: cmeans-claude-dev[bot] <272174644+cmeans-claude-dev[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmeans-claude-dev Bot added a commit that referenced this pull request May 6, 2026
…idation (#125)

Patch release fixing the v2.5.0 `publish-registry` failure.

## What happened

v2.5.0 (PR #124, tag `v2.5.0` pushed) ran `publish.yml`. `publish-pypi`
succeeded — `mcp-clipboard 2.5.0` is on PyPI. But `publish-registry`
(the first-ever firing of the registry-publish workflow added in #120)
failed:

```
Error: publish failed: server returned status 400
{"title":"Bad Request","status":400,"detail":"Failed to publish server",
 "errors":[{"message":"registry validation failed for package 0 (mcp-clipboard):
   PyPI package 'mcp-clipboard' ownership validation failed.
   The server name 'io.github.cmeans/mcp-clipboard' must appear as
   'mcp-name: io.github.cmeans/mcp-clipboard' in the package README"}]}
```

The MCP registry validates ownership by looking for that literal token
in the README that ships **with the PyPI package**. mcp-synology embeds
the same comment at `README.md:31` (`<!-- mcp-name:
io.github.cmeans/mcp-synology -->`); we missed mirroring this in the
registry-setup PR (#120).

Adding the token to `main` alone doesn't satisfy the v2.5.0 validation —
the registry check runs against the already-published v2.5.0 PyPI
artifact, whose README is fixed at upload time. The only path forward is
a new release that bakes the token into a fresh wheel.

## Fix

- `README.md`: add `<!-- mcp-name: io.github.cmeans/mcp-clipboard -->`
after the intro paragraph, matching mcp-synology's placement.
- `pyproject.toml`: `2.5.0 → 2.5.1`.
- `server.json`: synced to `2.5.1` via `scripts/sync-server-json.py` per
the new release flow from #120.
- `CHANGELOG.md`: `[2.5.1]` entry under `### Fixed` explaining the
registry validation requirement and the v2.5.0 → v2.5.1 path.

`hatchling` re-bundles `README.md` into the wheel's
`PKG-INFO`/`long_description` on every build, so v2.5.1's PyPI entry
will carry the validating string.

## What stays as-is

- **v2.5.0 on PyPI:** stays. PyPI version slots are immutable, and
v2.5.0 is functionally correct (all features work). It just doesn't
satisfy the registry's ownership check, so it remains unregistered.
- **v2.5.1 on PyPI:** will land on tag-push, with the validating README.
- **v2.5.1 in the MCP registry:** `publish-registry` will re-run on the
new tag and register `io.github.cmeans/mcp-clipboard` for the first
time.

## Verification

- `uv run pytest -q`: **599 passed**, 19 deselected, 5 xfailed (no test
changes).
- `uv run ruff check src/ tests/ scripts/`: clean.
- `python scripts/sync-server-json.py --check`: server.json in sync
(2.5.1).
- `uv build --wheel`: builds `dist/mcp_clipboard-2.5.1-py3-none-any.whl`
successfully.

## Test plan

- [x] CI green across `lint`, `typecheck`, `test (3.11/3.12/3.13)`,
`integration-x11`, `version-sync`, `validate-server-json`.
- [x] `uv build --wheel` succeeds; the wheel's `PKG-INFO` /
`long_description` includes the `mcp-name` comment.
- [ ] Tag-push fires `publish.yml` and all three jobs (validate,
publish-pypi, publish-registry) land green.
- [ ] `curl -s
'https://registry.modelcontextprotocol.io/v0/servers?search=mcp-clipboard'`
returns a hit afterward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Co-authored-by: cmeans-claude-dev[bot] <272174644+cmeans-claude-dev[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cmeans cmeans mentioned this pull request May 8, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

QA Approved Manual QA testing completed and passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Register mcp-clipboard with the MCP Server registry (server.json + publish-registry workflow)

1 participant