From 07a27b9fd3f035770ef8f86841b554460c43c537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 28 Oct 2026 23:31:20 +0100 Subject: [PATCH 1/3] chore(gates): rebuild agent-device.dev after each stable release /.well-known/mcp.json takes the npm latest version when the site builds, but a stable release is a tag push, which builds nothing on Vercel, so the manifest stayed a version behind until the next site change on main. A redeploy-site job now calls the site's Vercel deploy hook (secret VERCEL_SITE_DEPLOY_HOOK) after publish. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 15 +++++++++++++++ website/src/pages/.well-known/mcp.json.ts | 9 +++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 556c57b2ba..b267a91ed1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -232,6 +232,21 @@ jobs: with: release_tag: v${{ needs.plan.outputs.version }} + # agent-device.dev serves /.well-known/mcp.json with the npm `latest` version read when the + # site builds. A tag push builds nothing on Vercel, so ask for a production build of main. + redeploy-site: + name: Redeploy agent-device.dev + needs: [plan, publish] + if: needs.plan.outputs.mode == 'stable' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: {} + steps: + - name: Trigger the site's production build + env: + DEPLOY_HOOK: ${{ secrets.VERCEL_SITE_DEPLOY_HOOK }} + run: curl -fsS -X POST "$DEPLOY_HOOK" > /dev/null + mark-released: name: Move main past ${{ needs.plan.outputs.version }} needs: [plan, publish] diff --git a/website/src/pages/.well-known/mcp.json.ts b/website/src/pages/.well-known/mcp.json.ts index ea82de1cff..37a2d90d25 100644 --- a/website/src/pages/.well-known/mcp.json.ts +++ b/website/src/pages/.well-known/mcp.json.ts @@ -1,8 +1,9 @@ // /.well-known/mcp.json: the stdio MCP server's registry identity, from the repo's server.json, -// with the version set to the latest one published on npm when the site is built. The site -// redeploys on every push to main, so the version follows releases. Without npm the build -// fails: server.json between releases carries an unpublished `-dev` version, and agents would -// be told to install a package that does not exist. +// with the version set to the latest one published on npm when the site is built. The release +// workflow (release.yml, redeploy-site) rebuilds the site right after each stable release, so +// the version follows releases. Without npm the build fails: server.json between releases +// carries an unpublished `-dev` version, and agents would be told to install a package that +// does not exist. import type { APIRoute } from 'astro'; import server from '../../../../server.json'; From 82c6c370a44bc683cee648b94c37c4779bd084f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 29 Oct 2026 07:44:44 +0100 Subject: [PATCH 2/3] fix(release): scope the OIDC guard's secret check, wait for npm latest - test/ci/release-workflow.test.ts rejected any `secrets.` in release.yml, so the deploy hook failed it. The token check still covers the whole file; the secret check now covers the jobs that build and publish packages, which read none, and names redeploy-site's deploy hook as the workflow's only secret. - redeploy-site waits (up to 5 minutes) until npm serves the release as latest before it calls the hook, so the site never builds the previous version; a comment names what VERCEL_SITE_DEPLOY_HOOK holds. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 19 +++++++++++++++++-- test/ci/release-workflow.test.ts | 14 +++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b267a91ed1..18e025d3e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -233,15 +233,30 @@ jobs: release_tag: v${{ needs.plan.outputs.version }} # agent-device.dev serves /.well-known/mcp.json with the npm `latest` version read when the - # site builds. A tag push builds nothing on Vercel, so ask for a production build of main. + # site builds. A tag push builds nothing on Vercel, so ask for a production build of main once + # npm serves the new version. The VERCEL_SITE_DEPLOY_HOOK secret holds the Vercel deploy hook + # of the agent-device-website project for main; it is the only secret this workflow reads. redeploy-site: name: Redeploy agent-device.dev needs: [plan, publish] if: needs.plan.outputs.mode == 'stable' runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 permissions: {} steps: + - name: Wait for npm to serve the release as latest + env: + VERSION: ${{ needs.plan.outputs.version }} + run: | + for attempt in $(seq 1 30); do + if [ "$(npm view agent-device@latest version 2>/dev/null)" = "$VERSION" ]; then + exit 0 + fi + sleep 10 + done + echo "npm does not serve $VERSION as latest after 5 minutes." + exit 1 + - name: Trigger the site's production build env: DEPLOY_HOOK: ${{ secrets.VERCEL_SITE_DEPLOY_HOOK }} diff --git a/test/ci/release-workflow.test.ts b/test/ci/release-workflow.test.ts index ca4be4eb67..7f4fb51541 100644 --- a/test/ci/release-workflow.test.ts +++ b/test/ci/release-workflow.test.ts @@ -26,8 +26,20 @@ test('only the npm publisher and the MCP Registry call can mint an id-token', () expect(workflow.jobs.publish?.environment).toBe('npm-publish'); }); +/** The secret names a job references, wherever in the job they appear. */ +const secretsOf = (job: Job) => + [...JSON.stringify(job).matchAll(/secrets\.([A-Za-z0-9_]+)/g)].map(([, name]) => name); + test('the publisher authenticates through OIDC alone', () => { - expect(source).not.toMatch(/NPM_TOKEN|NODE_AUTH_TOKEN|secrets\./); + expect(source).not.toMatch(/NPM_TOKEN|NODE_AUTH_TOKEN/); + // No secret reaches the jobs that build or publish the packages. The one secret in the file + // is the site's deploy hook, which only the redeploy job after a stable release reads. + expect(secretsOf(workflow.jobs.publish ?? {})).toEqual([]); + expect(secretsOf(workflow.jobs.build ?? {})).toEqual([]); + const withSecrets = Object.entries(workflow.jobs) + .map(([name, job]) => [name, secretsOf(job)] as const) + .filter(([, names]) => names.length > 0); + expect(withSecrets).toEqual([['redeploy-site', ['VERCEL_SITE_DEPLOY_HOOK']]]); const setupNode = workflow.jobs.publish?.steps?.find((step) => step.uses?.startsWith('actions/setup-node@'), ); From 91131f607915343e8270b011606baf29fd3f871a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 29 Oct 2026 08:03:40 +0100 Subject: [PATCH 3/3] fix(release): accept a newer npm latest before redeploying the site When two stable releases run close together, the newer one moves latest past the older run's version, and the exact match timed out although the site only needs the current release. The wait now accepts a latest at or past the planned version (sort -V). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18e025d3e2..aa2e410ec5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -244,17 +244,22 @@ jobs: timeout-minutes: 10 permissions: {} steps: - - name: Wait for npm to serve the release as latest + # Any latest at or past this version will do: a newer stable release may already have + # moved latest on, and the site only needs the current one. + - name: Wait for npm to serve this release or a newer one as latest env: VERSION: ${{ needs.plan.outputs.version }} run: | for attempt in $(seq 1 30); do - if [ "$(npm view agent-device@latest version 2>/dev/null)" = "$VERSION" ]; then + latest="$(npm view agent-device@latest version 2>/dev/null || true)" + if [ -n "$latest" ] && + [ "$(printf '%s\n' "$VERSION" "$latest" | sort -V | head -n 1)" = "$VERSION" ]; then + echo "npm serves $latest as latest." exit 0 fi sleep 10 done - echo "npm does not serve $VERSION as latest after 5 minutes." + echo "npm does not serve $VERSION or newer as latest after 5 minutes." exit 1 - name: Trigger the site's production build