Publish to MCP Registry #65
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |