Skip to content

fix(workflow): bump mcp-publisher v1.5.0 → v1.7.6 for registry OIDC audience#79

Merged
cmeans-claude-dev[bot] merged 3 commits into
mainfrom
fix/mcp-publisher-v1.7.6
May 1, 2026
Merged

fix(workflow): bump mcp-publisher v1.5.0 → v1.7.6 for registry OIDC audience#79
cmeans-claude-dev[bot] merged 3 commits into
mainfrom
fix/mcp-publisher-v1.7.6

Conversation

@cmeans-claude-dev
Copy link
Copy Markdown
Contributor

Summary

The v0.5.1 release run hit publish-registry failure at "Authenticate to MCP registry (GitHub OIDC)" with HTTP 401:

```
Token exchange failed: failed to validate OIDC token: invalid audience:
expected https://registry.modelcontextprotocol.io, got [mcp-registry]
```

PyPI publish ✅, GitHub release ✅, registry publish ❌. v0.5.1 is on PyPI; only directory metadata is missing.

Root cause

The MCP registry deployed modelcontextprotocol/registry#1229 — "auth: bind GitHub OIDC token exchange to a per-deployment audience" — in v1.7.6 on 2026-04-30, one day before our 2026-05-01 release. Our .github/actions/install-mcp-publisher/action.yml pinned mcp-publisher to v1.5.0, which sends audience mcp-registry. The new registry server requires https://registry.modelcontextprotocol.io, mints a 401 on mismatch.

Fix

One-line bump of the pin's default from v1.5.0v1.7.6. Added a stanza-level comment with the rationale so future maintainers understand why this can't drift back.

What this PR does NOT do

  • Does not re-publish v0.5.1 to the MCP registry. Re-running the failed publish-registry job on the existing v0.5.1 tag won't pick up this fix because actions/checkout@v6 resolves to the tag's commit (which doesn't have the fix). The next release tag-push will exercise the fix end-to-end. Until then, v0.5.1 lives on PyPI but not in the registry directory — purely a metadata gap, not a user-install issue.

QA

Manual tests

    • Spot-check .github/actions/install-mcp-publisher/action.yml shows default: v1.7.6 and the new explanatory comment.
    • CHANGELOG entry lives under ## Unreleased### Fixed (the next release will roll this up).
    • Confirm the v0.5.1 publish.yml run shows publish-pypi: SUCCESS, github-release: SUCCESS, publish-registry: FAILURE with the cited audience-mismatch error.
    • Cross-check the registry release notes for v1.7.6 — first bullet names PR #1229.
    • After merge, the next release tag-push (whether 0.5.2 or beyond) should produce a green publish-registry job.

Verification I already ran

Check Result
mcp-publisher v1.7.6 release notes ✅ confirms PR #1229 is the audience-binding change
v0.5.1 publish.yml run 25218030961 publish-pypi green, publish-registry red with the cited error
git diff --stat HEAD~1 2 files: .github/actions/install-mcp-publisher/action.yml + CHANGELOG.md

🤖 Generated with Claude Code

…udience

The v0.5.1 release ran with mcp-publisher v1.5.0 (the existing pin
in .github/actions/install-mcp-publisher/action.yml) and the
publish-registry job failed at "Authenticate to MCP registry
(GitHub OIDC)" with HTTP 401:

  Token exchange failed: failed to validate OIDC token: invalid
  audience: expected https://registry.modelcontextprotocol.io,
  got [mcp-registry]

PyPI publish succeeded, GitHub release succeeded, only the
registry-publish leg failed.

Root cause: registry PR #1229 (`auth: bind GitHub OIDC token
exchange to a per-deployment audience`) deployed in v1.7.6 on
2026-04-30 — one day before our 2026-05-01 release. v1.5.0's
`login github-oidc` flow uses audience `mcp-registry`; v1.7.6's
uses `https://registry.modelcontextprotocol.io`, matching what
the prod deployment now validates against.

Bumps the action's `default` from v1.5.0 → v1.7.6 and adds an
explanatory comment with the rationale for the next bump.

v0.5.1 itself is on PyPI cleanly. Re-running the failed registry
job against the existing v0.5.1 tag won't pick up this fix because
actions/checkout@v6 resolves to the tag's commit; the next release
tag will exercise the fix end-to-end.

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

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

cmeans
cmeans previously approved these changes May 1, 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 1, 2026
@github-actions github-actions Bot removed the Ready for QA Dev work complete — QA can begin review label May 1, 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 Round 1 — One observation

Tight, well-scoped fix for the v0.5.1 publish-registry OIDC failure. Verified the cited evidence chain end-to-end:

Claim Verification
mcp-publisher v1.5.0 audience mismatch on the v0.5.1 publish run Pulled run 25218030961 logs: Error: failed to get token: failed to exchange OIDC token: token exchange failed with status 401: ... "failed to validate OIDC token: invalid audience: expected https://registry.modelcontextprotocol.io, got [mcp-registry]". Verbatim match.
publish-pypi: SUCCESS, github-release: SUCCESS, publish-registry: FAILURE on the v0.5.1 run gh run view confirms the per-job tally exactly as stated.
Registry deployed PR #1229 in v1.7.6 on 2026-04-30 Pulled v1.7.6 release notes via gh api: published 2026-04-30T01:03:06Z, body mentions #1229 and audience.
Bumping the pin from v1.5.0 → v1.7.6 fixes the audience binding The failing flow is mcp-publisher login github-oidc; the audience the OIDC exchanger sends is set inside the publisher binary, so a binary upgrade is the right lever.

Code review

The bump itself is one line in .github/actions/install-mcp-publisher/action.yml:14 (default: v1.5.0default: v1.7.6). The new stanza-level comment quotes the actual error verbatim and links the upstream PR — exactly the right shape so the next contributor doesn't have to re-derive the rationale.

I grep'd mcp-publisher and v1.5.0 repo-wide for stale references — see F1 below.

Verification

Check Result
uv run pytest 550 passed, 100 deselected, 96.13% coverage (no Python touched, expected to be unchanged)
uv run ruff check src/ tests/ scripts/ clean
uv run ruff format --check src/ tests/ scripts/ 72 files already formatted
uv run mypy src/ scripts/ clean (30 files, strict-mode)
Required CI on fd13040f 13/13 green (vdsm completed SUCCESS)

PR-body manual tests 1–4 verified directly; #5 is a post-merge tag-push observation by design.

Findings

ID Finding
F1 (observation) Stale verification annotation in .github/workflows/publish.yml:78 — the comment reads (verified against tag v1.5.0) referring to the upstream ErrInvalidVersion constant that the duplicate-version idempotency grep anchors on. This PR bumps the installed publisher to v1.7.6, so the annotation now references the wrong version. I checked: the error string invalid version: cannot publish duplicate version is still present in internal/database/database.go at v1.7.6 (ErrInvalidVersion = errors.New("invalid version: cannot publish duplicate version")), so the idempotency grep still works correctly — this is purely an annotation freshness issue, not a logic bug. Suggested fix: either (a) flip the annotation to (verified against tag v1.7.6), or (b) rewrite version-agnostically (verified upstream against the currently-pinned mcp-publisher tag). One-line edit. Per the project's doc-drift-is-substantive rule, blocking.

Verdict

QA Failed Round 1. F1 is a single-line comment edit; round 2 should be quick. The actual fix is solid and the evidence chain checks out.

@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 1, 2026

Applying QA Failed as the final act of round 1. Single observation F1: stale (verified against tag v1.5.0) annotation in .github/workflows/publish.yml:78 should follow the v1.7.6 pin bump (or rewrite version-agnostically). I confirmed the upstream ErrInvalidVersion string is still present in internal/database/database.go@v1.7.6 so the idempotency grep still works — purely annotation freshness. The fix itself is solid: error log, run conclusions, v1.7.6 release notes, and PR #1229 attribution all check out.

@cmeans cmeans added QA Failed QA found issues — needs dev attention and removed QA Active QA is actively reviewing; Dev should not push changes labels May 1, 2026
…stically

Addresses QA F1 on PR #79. The annotation in publish.yml:78 read
"(verified against tag v1.5.0)" — it was true at write time but
became stale the moment we bumped the pin to v1.7.6 in this same
PR. Rewrote it as "verified upstream against the currently-pinned
mcp-publisher tag in .github/actions/install-mcp-publisher/action.yml"
so future bumps don't need to re-touch this comment. The
underlying grep string is unchanged — QA confirmed the upstream
ErrInvalidVersion string is still present in v1.7.6.

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 and removed QA Failed QA found issues — needs dev attention labels May 1, 2026
@cmeans-claude-dev
Copy link
Copy Markdown
Contributor Author

Round 2 — F1 addressed

Picked QA's option (b) — rewrote the annotation version-agnostically so future mcp-publisher bumps don't re-introduce the staleness:

```diff

  •      # (verified against tag v1.5.0). The publisher surfaces this as
    
  •      # part of the 400 response body from POST /v0/publish. If the
    
  •      # upstream error text changes, this step will fall through to
    
  •      # \`exit \$status\` and fail loudly — preferable to silently
    
  •      # swallowing a real publish error.
    
  •      # (verified upstream against the currently-pinned mcp-publisher
    
  •      # tag in .github/actions/install-mcp-publisher/action.yml). The
    
  •      # publisher surfaces this as part of the 400 response body from
    
  •      # POST /v0/publish. If the upstream error text changes, this
    
  •      # step will fall through to \`exit \$status\` and fail loudly —
    
  •      # preferable to silently swallowing a real publish error.
    

```

The underlying grep string is unchanged — QA already confirmed ErrInvalidVersion = errors.New("invalid version: cannot publish duplicate version") is still present in internal/database/database.go@v1.7.6, so the idempotency grep keeps working. The new wording points readers at where to find the current pin instead of fossilizing a specific tag.

No code or test changes; CI on the new head should match the pre-fix run.

Ready for round 2.

@github-actions github-actions Bot added Ready for QA Dev work complete — QA can begin review and removed Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels May 1, 2026
@cmeans cmeans added the QA Active QA is actively reviewing; Dev should not push changes label May 1, 2026
@github-actions github-actions Bot removed the Ready for QA Dev work complete — QA can begin review label May 1, 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 Round 2 — PASS

F1 closed via the version-agnostic option I suggested (option b). The new annotation in publish.yml:75-79 reads:

verified upstream against the currently-pinned mcp-publisher tag in
.github/actions/install-mcp-publisher/action.yml

That's the right shape — future pin bumps don't require touching this comment, so the same drift class can't recur. Underlying grep string is unchanged; the upstream ErrInvalidVersion constant text I verified at v1.7.6 in round 1 still anchors the idempotency check correctly.

Bonus: the commit message acknowledges the round-1 verification work explicitly ("QA confirmed the upstream ErrInvalidVersion string is still present in v1.7.6"). Helpful audit trail.

Verification (re-run on bdd9e7a)

Check Result
uv run pytest 550 passed, 100 deselected, 96.13% coverage
uv run ruff check src/ tests/ scripts/ clean
uv run ruff format --check src/ tests/ scripts/ 72 files already formatted
uv run mypy src/ scripts/ clean (30 files, strict-mode)
Required CI on bdd9e7a 13/13 green (vdsm completed SUCCESS)

Verdict

Ready for QA Signoff. F1 closed; no other findings. Final maintainer call.

@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 1, 2026

Applying Ready for QA Signoff as the final act of round 2. F1 closed via the version-agnostic rewrite (option b from round 1) — annotation now references the canonical pin location instead of a specific tag, so the same drift class can't recur on future bumps. 550/550 pass, 96.13% coverage, ruff/mypy clean, 13/13 required CI green. Final maintainer call.

@cmeans cmeans added Ready for QA Signoff QA passed — ready for maintainer final review and merge and removed QA Active QA is actively reviewing; Dev should not push changes labels May 1, 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 QA Approved Manual QA testing completed and passed and removed Ready for QA Signoff QA passed — ready for maintainer final review and merge labels May 1, 2026
@cmeans-claude-dev cmeans-claude-dev Bot merged commit 6685eee into main May 1, 2026
34 checks passed
@cmeans-claude-dev cmeans-claude-dev Bot deleted the fix/mcp-publisher-v1.7.6 branch May 1, 2026 15:38
cmeans-claude-dev Bot added a commit that referenced this pull request May 2, 2026
## Summary

Cuts v0.5.2, shipping six PRs that landed since v0.5.1 (2026-05-01):

- **#79** mcp-publisher v1.5.0 → v1.7.6 (registry OIDC audience fix)
- **#80** keyring exception handler narrowing (closes #38)
- **#81** bg update-check executor timeout + log (closes #39)
- **#82** pygments 2.19.2 → 2.20.0 (GHSA-5239-wwwm-4pmq, ReDoS)
- **#83** `--revert` version-string validation (closes #40)
- **#85** per-path serial for `move_files` + `copy_files` (closes #84)

## Why now

Two recent bug fixes (#83, #85) are user-visible enough to warrant
shipping, and #84 in particular is a confusing silent-no-op regression
on multi-file moves — getting that to PyPI promptly matters. The four
post-0.5.1 quality fixes (#79#82) are stacked behind it.

This release also exercises **#79's mcp-publisher v1.7.6 pin
end-to-end** so the registry can catch up to current. v0.5.1's registry
entry is missing because #79 landed AFTER the v0.5.1 tag-push, and
`actions/checkout@v6` resolved to the tag's commit on re-runs of the
failed `publish-registry` job — the fix wasn't picked up. The v0.5.2 tag
will pull the correct pin from main.

## State after merge

Bug-labeled issue queue is empty. The structural multi-path-serial fix
family (delete + getinfo + move + copy + restore) is now complete on
real DSM 7.x — every File Station write tool that takes a `paths:
list[str]` issues one DSM task per path, sidestepping the
comma-joined-multipath quirk that #68 and #84 each surfaced.

## Files changed

- `pyproject.toml` — version 0.5.1 → 0.5.2
- `server.json` — auto-synced via `python scripts/sync-server-json.py`
- `uv.lock` — refreshed via `uv lock`
- `CHANGELOG.md` — `## Unreleased` (with the six entries above) renamed
to `## 0.5.2 (2026-05-01)`, fresh empty `## Unreleased` inserted above
it for the next cycle

## Test plan

- [x] CI green on this branch (lint, typecheck, test 3.11/3.12/3.13,
vdsm integration tests, version-sync, validate-server-json)
- [ ] After merge: tag `v0.5.2` push fires `publish.yml`; PyPI publish
succeeds
- [ ] After merge: `publish-registry` job succeeds end-to-end (this is
the validation point for #79's fix — the failure mode in v0.5.1 was
`invalid audience: expected https://registry.modelcontextprotocol.io,
got [mcp-registry]`)
- [ ] After tag: `mcp-synology --check-update` from a v0.5.1 install
reports v0.5.2 available; `uv tool install mcp-synology@latest` upgrades
cleanly
- [ ] Smoke (post-install): two-file `move_files` actually moves both
files (the #84 regression scenario)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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 2, 2026
## Summary

Closes #44.

Adds a `validate-server-json` job to `publish.yml` that runs **before**
`publish-pypi` on every release tag, mirroring the same check that
already runs on every PR via `ci.yml`.

## Why

Without this gate, a malformed `server.json` (new required field in a
future registry schema, type change, etc.) would PyPI-publish cleanly
and then fail at the registry leg — leaving a discoverable PyPI release
that isn't in the MCP registry, with no way to roll back PyPI short of
yanking. Re-running the failed registry job can't fix that asymmetry.

v0.5.1 hit the registry-leg-fails-after-PyPI scenario for a different
reason (mcp-publisher OIDC audience mismatch, fixed in #79). The failure
mode isn't theoretical — it just hasn't yet fired for a *schema* reason.

## What changed

- New `validate-server-json` job in `publish.yml` runs `./mcp-publisher
validate server.json` using the same
`./.github/actions/install-mcp-publisher` composite that
`publish-registry` uses. Validating publisher version always matches
publishing publisher version.
- `publish-pypi` now lists `needs: [build, validate-server-json]` so a
validation failure stops the entire pipeline before any external
side-effect.
- `validate-server-json` runs in parallel with `build` — no dep on it —
so the gate adds ~10s to the critical path, not a serial round-trip.

## Why not the optional manifest artifact

The `publish-manifest` artifact (record `mcp-publisher`/`uv`/Python
versions for CI-PR vs release-tag reconciliation) is named as optional
in #44. Deferring — it's a debugging aid that's only useful if a CI-PR /
release-tag mismatch *does* fire, and is trivially additive later when
there's a concrete failure to investigate.

## Test plan

- [x] `python -c "import yaml;
yaml.safe_load(open('.github/workflows/publish.yml'))"` parses
- [x] CI green on this PR
(lint/typecheck/test/version-sync/validate-server-json/vdsm)
- [ ] After merge: next release tag (v0.5.3 or whichever) shows the new
`validate-server-json` job in the publish.yml run, gating `publish-pypi`
- [ ] Negative test (informal): if you intentionally corrupt
`server.json` on a branch and tag-push, the publish workflow stops at
validate-server-json with no PyPI side-effect — won't actually run this
since it requires breaking server.json on main; the validate step is the
same `./mcp-publisher validate` invocation that ci.yml exercises on
every PR, so it's already proven to fail loudly on schema errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

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 to cmeans/mcp-clipboard that referenced this pull request May 6, 2026
…ublish-registry workflow (#120)

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 `publish` →
`publish-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:
- cmeans/mcp-synology#16 — initial `server.json` + first registration
attempt.
- cmeans/mcp-synology#44 — gating story (why validate runs at both PR
time and release time).
- cmeans/mcp-synology#79 — the `v1.5.0 → v1.7.6` mcp-publisher bump
after v0.5.1's registry leg failed on the OIDC audience mismatch.
- cmeans/mcp-synology#89 — `publish-pypi` wired to `needs: [build,
validate-server-json]`.
- cmeans/mcp-synology#92 — `sync-server-json.py` ergonomics
improvements.

## Verification

- `python scripts/sync-server-json.py --check` → `server.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.yml` → `validate-server-json` → `publish-pypi` →
`publish-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

- [x] 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>

---------

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>
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.

2 participants