Skip to content
Merged
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
74 changes: 62 additions & 12 deletions .github/workflows/Release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -271,20 +271,70 @@ jobs:
- name: Authenticate to the MCP Registry via GitHub OIDC
run: ./mcp-publisher login github-oidc

# A "cannot publish duplicate version" 400 means the registry already
# has this exact version — the desired end state for the
# workflow_dispatch recovery path (RELEASING.md step 5: re-run this
# job for a tag whose publish was missed or failed). On the normal
# push-tag path the same error means something is wrong: this job
# only runs once per tag (needs: [test, release]), so an existing
# entry there is unexpected and must still fail loudly. The verify
# step below still runs after a swallowed duplicate and independently
# confirms the registry serves this version before the job can pass —
# this branch narrows *which* mcp-publisher exit codes are treated as
# non-fatal, it never widens what "passing" means.
- name: Publish server.json to the MCP Registry
run: ./mcp-publisher publish
run: |
set +e
OUTPUT=$(./mcp-publisher publish 2>&1)
STATUS=$?
set -e
echo "$OUTPUT"
if [ "$STATUS" -ne 0 ]; then
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && echo "$OUTPUT" | grep -qi "duplicate version"; then
echo "::notice::mcp-publisher reports ${{ steps.resolve.outputs.tag }} already published (workflow_dispatch recovery path) — treating as a candidate success, pending independent registry verification below."
else
echo "::error::mcp-publisher publish failed (exit ${STATUS})"
exit "$STATUS"
fi
fi

# A green `mcp-publisher publish` is not proof (Move 2: verify
# externally, not by exit code alone). Query the registry's own API
# and fail the job if it disagrees with server.json.
# A green `mcp-publisher publish` (or a swallowed duplicate-version
# error above) is not proof (Move 2: verify externally, not by exit
# code alone). Query the registry's own API and fail the job if it
# disagrees with server.json.
#
# Bounded retry, not zero-tolerance: the registry API sits behind a
# CDN cache in front of all public read endpoints (modelcontextprotocol/
# registry docs/design/tech-architecture.md, "CDN Layer"), so a read
# immediately after publish can observe a stale, pre-publish entry.
# Measured on the v3.1.1 release (2026-08-09): publish completed at
# 16:48:46 UTC and a read one second later, 16:48:47, still returned
# the prior version — an empirical floor of >1s of staleness. No SLA
# for the cache TTL is published, so the bound below is a stated
# engineering assumption, not a measured constant: 12 attempts x 5s
# = 60s, an order of magnitude above the observed 1s staleness, meant
# to absorb ordinary CDN propagation without masking a genuine
# publish failure. It still fails — this is a timeout, not an
# unbounded retry-until-pass — when the registry never converges,
# which is exactly the case this check exists to catch.
- name: Verify the registry now serves the published version
run: |
set -euxo pipefail
set -euo pipefail
VERSION=$(python3 -c "import json; print(json.load(open('server.json'))['packages'][0]['version'])")
ACTUAL=$(curl -fsSL "https://registry.modelcontextprotocol.io/v0/servers?search=hypermnesia-mcp-viz" \
| python3 -c "import json, sys; d = json.load(sys.stdin); latest = next(s for s in d['servers'] if s['_meta']['io.modelcontextprotocol.registry/official']['isLatest']); print(latest['server']['version'])")
echo "Registry reports latest version: ${ACTUAL} (expected ${VERSION})"
if [ "$ACTUAL" != "$VERSION" ]; then
echo "::error::MCP Registry serves ${ACTUAL}, expected ${VERSION} after publish"
exit 1
fi
MAX_ATTEMPTS=12
SLEEP_SECONDS=5
ACTUAL=""
for attempt in $(seq 1 "$MAX_ATTEMPTS"); do
ACTUAL=$(curl -fsSL "https://registry.modelcontextprotocol.io/v0/servers?search=hypermnesia-mcp-viz" \
| python3 -c "import json, sys; d = json.load(sys.stdin); latest = next(s for s in d['servers'] if s['_meta']['io.modelcontextprotocol.registry/official']['isLatest']); print(latest['server']['version'])")
echo "Attempt ${attempt}/${MAX_ATTEMPTS}: registry reports latest version ${ACTUAL} (expected ${VERSION})"
if [ "$ACTUAL" = "$VERSION" ]; then
echo "Registry converged to ${VERSION} on attempt ${attempt}."
exit 0
fi
if [ "$attempt" -lt "$MAX_ATTEMPTS" ]; then
sleep "$SLEEP_SECONDS"
fi
done
echo "::error::MCP Registry still serves ${ACTUAL}, expected ${VERSION} after ${MAX_ATTEMPTS} attempts (~$((MAX_ATTEMPTS * SLEEP_SECONDS))s). This is a non-convergence failure, not a propagation delay."
exit 1