Skip to content

Releasing

Vinod Subramaniam edited this page Oct 18, 2026 · 5 revisions

A release is a version bump merged to main, then a v* tag on the merged commit. The Release workflow does the rest.

What a release produces

Output Where
Claude Desktop extension (.mcpb) GitHub release
Standalone archives for macOS, Linux and Windows on x64 and arm64, with Node.js 24 GitHub release
npm package (.tgz) GitHub release and the npm registry
SHA256SUMS GitHub release
Container image for linux/amd64 and linux/arm64 ghcr.io/visubramaniam/vsp360-mcp and Docker Hub, tagged <version> and latest
Tufte charts skill for Claude Desktop (vsp360-tufte-charts-<version>.zip) GitHub release
MCP Registry listing, io.github.visubramaniam/vsp360-mcp registry.modelcontextprotocol.io, which GitHub's MCP Registry reads

Steps

  1. Prepare a release branch from main:
    • npm version <x.y.z> --no-git-tag-version
    • Set version and packages[0].version in server.json to <x.y.z>. tests/unit/registry.test.mjs fails until both match package.json.
    • Rename the changelog's Unreleased section to <x.y.z> — <date>.
    • Update the version examples in INSTALL.md, README.md and README.docker.md.
    • Leave the Claude plugin on the previous version. Its lockfile can only name a version that's already on npm, so a follow-up pull request moves it (step 7). tests/unit/plugin.test.mjs allows the plugin to trail package.json.
    • Run npm test.
  2. Open a pull request into main in the release's milestone, and merge it once CI passes. Use Rebase and merge, unless main was merged into the branch: rebasing replays each commit and drops the merge commit, so GitHub reports conflicts that the merge already resolved. Use Squash and merge then.
  3. Tag the merged commit and push the tag:
git fetch origin && git tag v<x.y.z> origin/main && git push origin v<x.y.z>
  1. Watch the Release workflow. Its jobs:
    • packages checks that the tag matches package.json, runs the tests, builds every package and attaches them to the GitHub release. It creates the release from the changelog section if it doesn't exist.

    • npm publishes the release's .tgz to npm, after checking it against SHA256SUMS. It skips versions already on npm.

    • mcp-registry runs after npm and lists the version in the MCP Registry. It:

      • waits for the npm package to show the expected mcpName, and fails if it's wrong
      • skips the version if it's already listed
      • downloads mcp-publisher at the pinned version and checks its SHA-256
      • validates server.json, signs in with GitHub OIDC and publishes

      Tags without a server.json (before 0.1.26) are skipped.

    • docker builds the multi-arch image and pushes it to ghcr.io, and to Docker Hub when its secrets are set.

    • windows downloads the release's Windows zips on GitHub's Windows x64 and arm64 machines, checks them against SHA256SUMS and runs tests/smoke/package-smoke.mjs with their own Node.js. Run the Windows packages workflow by hand to test any earlier release.

  2. Check the result:
    • npm may hold a new version in Validating for a few minutes before it appears. npm view vsp360-mcp@<x.y.z> returns 404 until then, even though the npm job's log says + vsp360-mcp@<x.y.z>.
    • Check that npm view vsp360-mcp shows the new latest with provenance.
    • Check that both image tags resolve to the same digest.
    • Check the MCP Registry listing shows the new version as latest. This lookup answers in under a second; registry searches can take 15–40 seconds.
curl -s "https://registry.modelcontextprotocol.io/v0.1/servers/io.github.visubramaniam%2Fvsp360-mcp/versions/latest" | jq '.server.version, ._meta'
  1. Update the wiki, milestones and project board. The wiki is a separate repository, so release pull requests don't change it.
    • Wiki: the version examples (Installation, Connecting Claude), the Roadmap, and any page describing behaviour the release changed.
    • Close the release's milestone, with a description of what shipped and the release link.
    • On the project board, mark the release's items Done, and add its pull requests with their Target release.
  2. Move the plugin to the new version in a follow-up pull request, once npm serves it: version in plugin/.claude-plugin/plugin.json and plugin/package.json, the exact vsp360-mcp dependency, and a regenerated plugin/package-lock.json. The plugin listing in Anthropic's directory follows main, so it picks up the change by itself.

One-time setup

What Where
npm trusted publisher npmjs.com → vsp360-mcp → Settings → Trusted Publisher → GitHub Actions: user visubramaniam, repository vsp360-mcp, workflow release.yml, no environment
Docker Hub (optional) Repository secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN, a Docker Hub personal access token with Read & Write access
ghcr.io Nothing: the workflow uses its own token. The package must be public, under Packages → vsp360-mcp → Package settings.
MCP Registry Nothing: signing in with the workflow's GitHub OIDC token proves the io.github.visubramaniam/ namespace. mcpName in package.json must equal name in server.json.

No npm or MCP Registry token is stored anywhere. npm trusts the workflow through OIDC and adds a provenance attestation to each version; the MCP Registry accepts the same kind of OIDC token.

When something fails

  • Tag doesn't match package.json: the packages job stops. Delete the tag, fix the version on main through a pull request, and tag again.
  • npm job fails with 404: the trusted publisher is missing or doesn't match, e.g. it names an environment the job doesn't use. Fix it on npmjs.com, then rerun the failed job: gh run rerun <run-id> --failed. The rerun publishes the same .tgz.
  • docker job fails at login: check the Docker Hub secrets. The ghcr.io push needs no secrets.
  • windows job fails in the smoke test itself: a re-run of the failed jobs checks out the tag, so it fails the same way. Fix tests/smoke/package-smoke.mjs on main, then run the Windows packages workflow by hand with the tag. It uses main's smoke test against the release's zips. This happened in 0.1.22: the smoke test's http:// mock needed VSP360_ALLOW_HTTP=true.
  • packages fails the Node.js signature check: a new Node.js releaser may have signed NODE_VERSION. Move NODE_KEYRING in scripts/package.mjs to a newer commit of nodejs/release-keys.
  • mcp-registry fails on mcpName: the published package doesn't carry the name in server.json. A published npm version can't be changed, so fix package.json and server.json on main and release a new version.
  • mcp-registry can't reach the registry or publish: the registry is in preview and sometimes slow or down. Rerun the failed job (gh run rerun <run-id> --failed). If the registry resets its data, rerun the workflow by hand for the latest tag; the job sees the version is missing and publishes it again.
  • Moving to a newer mcp-publisher: set MCP_PUBLISHER_VERSION in release.yml, and MCP_PUBLISHER_SHA256 to the SHA-256 of that release's mcp-publisher_linux_amd64.tar.gz.
  • Rebuilding an existing tag: run the workflow by hand with that tag. This replaces the GitHub release's files and the images; npm and the MCP Registry are skipped because the version already exists. npm versions can't be replaced.

Building packages locally

npm run package

Use --only npm,mcpb,standalone and --targets darwin-arm64,... to build fewer, e.g. npm run package -- --only standalone --targets darwin-arm64. The Node.js runtimes are downloaded once into release/.cache and checked against nodejs.org's published SHA-256 sums. The signature of those sums is checked with gpgv (GnuPG) and the Node.js release keys. Without gpgv, a local build prints a warning and goes on; in CI it fails.

Clone this wiki locally