Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,16 @@ jobs:

- name: Show what a release would contain
run: ls -l dist/*.tar.gz dist/checksums.txt

# server.json is only exercised during a tag release, which is a bad
# place to discover it is malformed: the image is already pushed and the
# GitHub release already cut by then. Validating on every PR moves that
# failure to where it is cheap to fix.
#
# This checks shape, not version numbers -- the release workflow rewrites
# `version` and the image tag from the tag before publishing, so the
# values committed here are last release's and are expected to lag.
- name: Validate server.json
run: |
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
./mcp-publisher validate
47 changes: 47 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,50 @@ jobs:
dist/checksums.txt
retention-days: 7
if-no-files-found: error

# Listing the release in the MCP Registry, which is metadata only: it records
# that this version exists and which image holds it. Kept as its own job that
# needs `release`, because the entry names an image tag -- publishing before
# goreleaser has pushed it would advertise something nobody can pull.
mcp-registry:
name: mcp registry
needs: release
# Tag pushes only. A workflow_dispatch run has a branch in GITHUB_REF_NAME
# rather than a version, so there is no release for an entry to describe,
# and a dry run pushes no image at all.
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
# The whole authentication story. `login github-oidc` trades this
# workflow's identity for a short-lived registry token, so there is no
# registry secret in this repository to store, rotate or leak, and no
# device-code flow to sit through by hand.
id-token: write
contents: read
steps:
- name: Check out
uses: actions/checkout@v4

- name: Install mcp-publisher
run: |
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher

# The committed server.json carries the version of the *previous*
# release, so by the time this runs it is stale by exactly one tag. The
# tag is the truth. Both fields are rewritten from it, and they have to
# move together: `version` is what the registry lists, `identifier` is
# the image that version actually lives in.
- name: Stamp the tag into server.json
run: |
version="${GITHUB_REF_NAME#v}"
jq --arg v "$version" \
--arg img "ghcr.io/${{ github.repository }}:" \
'.version = $v | .packages[0].identifier = $img + $v' \
server.json > server.tmp && mv server.tmp server.json
cat server.json

- name: Authenticate to the MCP Registry
run: ./mcp-publisher login github-oidc

- name: Publish
run: ./mcp-publisher publish
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,15 @@ The image is also what backs the [MCP Registry](https://registry.modelcontextpro
listing; [`server.json`](server.json) is that entry, and its `name` has to match
the `io.modelcontextprotocol.server.name` label baked into the image.

Publishing that entry is automatic. Tagging a release builds and pushes the
image, and then a second job rewrites `version` and the image tag in
`server.json` from the git tag and publishes to the registry, authenticating
with the workflow's own OIDC identity rather than a stored token.

**So the `version` committed in `server.json` is last release's, and lags by
one tag on purpose.** The tag is the source of truth; the file is a template
that CI stamps. Bumping it by hand achieves nothing.

### As a Claude Code skill

The repo ships a skill and plugin package under [`skills/`](skills/), which
Expand Down
Loading