Skip to content

Publish to MCP Registry #65

Publish to MCP Registry

Publish to MCP Registry #65

Workflow file for this run

name: Publish to MCP Registry
# Publishes the generated MCP manifests to the official MCP registry
# (registry.modelcontextprotocol.io) via GitHub Actions OIDC.
#
# Determinism + honesty guarantees (docs/CORRECTIONS_2026-07-14.md):
# * mcp-publisher is PINNED and checksum-verified — a new publisher release
# can never silently change publish behaviour.
# * the server name MUST be `io.github.AgentTanuki/agent-guild` — the exact
# (case-sensitive) namespace the repo's OIDC identity is granted; this is
# the same identity as the published 1.0.0/1.1.0 listing, so legacy
# consumers see 1.2.0 as a new `isLatest` version of the same server.
# * every step fails the workflow loudly; nothing is reported published
# until the OFFICIAL registry serves the exact release back
# (live/scripts/registry_readback.py — unit-tested parser).
# * concurrency: one publish at a time, newest wins; an older run can never
# race a newer one into certifying the wrong version.
# Publication is dispatched only after ship.yml has proved that the exact
# merged SHA is live. A direct push trigger races the deployment and can make
# the Registry point at a surface production does not serve yet.
on:
workflow_dispatch:
concurrency:
group: publish-mcp-registry
cancel-in-progress: true
env:
# Pinned publisher release + its official SHA-256 (from
# registry_1.8.0_checksums.txt on the v1.8.0 release, verified 2026-07-14).
MCP_PUBLISHER_VERSION: v1.8.0
MCP_PUBLISHER_SHA256: 1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # required for GitHub OIDC login
contents: read
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Install mcp-publisher (pinned + checksum-verified)
run: |
set -euo pipefail
curl -fsSL -o mcp-publisher.tar.gz \
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/mcp-publisher_linux_amd64.tar.gz"
echo "${MCP_PUBLISHER_SHA256} mcp-publisher.tar.gz" | sha256sum -c -
tar xzf mcp-publisher.tar.gz mcp-publisher
chmod +x mcp-publisher
./mcp-publisher --version
- name: Guard — server.json name matches the OIDC-granted namespace
# The registry grants io.github.<repository_owner>/* with the owner's
# EXACT GitHub casing and matches case-sensitively. Catch drift here,
# before burning a publish attempt (root cause of run 29274449452).
run: |
set -euo pipefail
name=$(python3 -c "import json;print(json.load(open('server.json'))['name'])")
expected="io.github.${{ github.repository_owner }}/agent-guild"
if [ "$name" != "$expected" ]; then
echo "::error::server.json name '$name' != OIDC-publishable '$expected'"
exit 1
fi
- name: Guard — focused name and remote are distinct and OIDC-publishable
run: |
set -euo pipefail
path="registry/x402-payment-safety/server.json"
name=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['name'])" "$path")
remote=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['remotes'][0]['url'])" "$path")
expected="io.github.${{ github.repository_owner }}/x402-payment-safety"
if [ "$name" != "$expected" ]; then
echo "::error::focused name '$name' != OIDC-publishable '$expected'"
exit 1
fi
if [ "$remote" != "https://agent-guild-5d5r.onrender.com/mcp/payment-safety/" ]; then
echo "::error::focused remote is not the dedicated payment-safety surface"
exit 1
fi
- name: Hard gate — exact workflow SHA is live and both MCP surfaces conform
run: |
set -euo pipefail
live_sha=$(curl -fsSL https://agent-guild-5d5r.onrender.com/release | \
python3 -c "import json,sys;print(json.load(sys.stdin)['git_sha'])")
if [ "$live_sha" != "$GITHUB_SHA" ]; then
echo "::error::production serves $live_sha, workflow tree is $GITHUB_SHA"
exit 1
fi
python3 live/scripts/live_contract_probe.py
- name: Validate both generated manifests
run: |
./mcp-publisher validate server.json
./mcp-publisher validate registry/x402-payment-safety/server.json
- name: Decide canonical publication idempotently
id: canonical
run: |
python3 live/scripts/registry_publish_needed.py \
--server-json server.json --github-output "$GITHUB_OUTPUT"
- name: Decide focused publication idempotently
id: focused
run: |
python3 live/scripts/registry_publish_needed.py \
--server-json registry/x402-payment-safety/server.json \
--github-output "$GITHUB_OUTPUT"
- name: Authenticate (GitHub OIDC)
if: steps.canonical.outputs.needed == 'true' || steps.focused.outputs.needed == 'true'
run: ./mcp-publisher login github-oidc
- name: Publish canonical Agent Guild server
if: steps.canonical.outputs.needed == 'true'
run: ./mcp-publisher publish server.json
- name: Publish focused x402 payment-safety server
if: steps.focused.outputs.needed == 'true'
run: ./mcp-publisher publish registry/x402-payment-safety/server.json
- name: Verify both Registry READBACKS (hard gate)
# Not "published" until the official registry serves the exact
# name+version+repository+remote (and the publisher-provided trust
# _meta) back. Exit codes: 1 never served, 2 served-but-mismatched,
# 3 malformed responses — all fail the workflow.
run: |
python3 live/scripts/registry_readback.py \
--server-json server.json --attempts 30 --interval 10
python3 live/scripts/registry_readback.py \
--server-json registry/x402-payment-safety/server.json \
--attempts 30 --interval 10 \
--search x402 --search payment --search safety \
--search x402-payment-safety