Help us improve
Share bugs, ideas, or general feedback.
From pubmed-mcp-server
Publishes a release to npm, MCP Registry, GitHub Releases, and GHCR after git wrapup. Retries transient network failures and reports partial state on terminal errors.
npx claudepluginhub cyanheads/cyanheads --plugin pubmed-mcp-serverHow this skill is triggered — by the user, by Claude, or both
Slash command
/pubmed-mcp-server:release-and-publishThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill runs **after** git wrapup. By the time it's invoked:
Publishes a release to npm, MCP Registry, GitHub Releases, and GHCR after git wrapup. Retries transient network failures and reports partial state on terminal errors.
Guides npm publishing for @mcp-b monorepo packages using changesets: validate build/test/typecheck, create/apply changesets for version bumps and CHangelogs, NPM_TOKEN auth, pnpm publish -r in topological order.
Finalizes documentation and project metadata for an MCP server after implementation is complete. Audits surface area, updates README and agent protocol files.
Share bugs, ideas, or general feedback.
This skill runs after git wrapup. By the time it's invoked:
field-test, security-pass, polish-docs-meta as applicable)package.json version is bumpedchangelog/<major.minor>.x/<version>.md is authoredCHANGELOG.md is regeneratedchore: release v<version>) existsv<version>) exists locallyIf any are missing, halt and tell the user to finish wrapup first. Do not attempt to redo wrapup work from inside this skill.
Steps 3–7 are network-bound. For those, retry transient failures up to 2 times with short backoff (~5 s before the first retry, ~15 s before the second) before halting. All other steps halt on the first non-zero exit — they're deterministic and a second attempt won't change the outcome.
Match stderr (case-insensitive) against any of these — if matched, the failure is almost always a network blip; retry:
integrity check failed / IntegrityCheckFailed — corrupt tarball during downloadECONNRESET / EAI_AGAIN / ETIMEDOUT / ENOTFOUND — network layerconnection reset / connection refused — transport bliptimed out / request timeout — server or network timeout502 / 503 / 504 — transient registry errorBefore retrying docker buildx --push (step 7), run docker builder prune -f to drop any cached corrupt layer. Skip this extra step for other retries.
These mean the step already succeeded on a prior run — treat as success and proceed to the next step:
bun publish): version already exists, You cannot publish over the previously published versionsmcp-publisher publish): cannot publish duplicate versiongh release create): release already exists — fall back to gh release upload --clobber (see step 6)If retries are exhausted, or the failure matches none of the transient patterns, halt and report:
.mcpb? GHCR?) — the partial state across destinationsThe user fixes locally and re-invokes. On re-invocation, already-published destinations hit the idempotent-success signal and skip naturally — no manual step-skipping required.
Read package.json → capture version. Then use your git tools to verify:
v<version> — matches the package.json versionIf working tree is dirty or HEAD isn't on v<version>, halt.
All three must succeed. Check package.json scripts for test:all; if absent, fall back to test:
bun run devcheck
bun run rebuild
bun run test:all # or `bun run test` if no test:all
Any non-zero exit → halt with the failing command's output.
Use your git tools to push the branch commits first, then push tags to origin. If the remote rejects either push, halt.
bun publish --access public
bun publish uses whatever npm auth the user has configured in ~/.npmrc. If 2FA is enabled on the npm account, the command will prompt for an OTP or open a browser — that's expected; the user completes it interactively.
Friction reducers (optional, configure once):
| Option | How |
|---|---|
| npm granular access token with "Bypass 2FA for publish" | Generate at npmjs.com → replace _authToken in ~/.npmrc → no OTP prompt at all |
1Password CLI TOTP injection (requires brew install --cask 1password-cli + signed-in op) | bun publish --access public --otp="$(op item get 'npm' --otp)" |
Halt on publish error other than "version already exists" (which means this step already ran).
Only if server.json exists at the repo root (otherwise skip). Note: server.json (MCP Registry metadata) and manifest.json (MCPB bundle manifest, step 6) are independent — a project may have either, both, or neither.
bun run publish-mcp
If publish-mcp isn't defined in package.json, add it permanently (one-time setup, macOS):
"publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish"
Prereq: a GitHub PAT with read:org + read:user scopes stored in Keychain under the service name mcp-publisher-github-pat:
security add-generic-password -a "$USER" -s mcp-publisher-github-pat -w
# paste PAT at the silent prompt
Halt on any publisher error other than "cannot publish duplicate version".
Only if manifest.json exists at the repo root (otherwise skip).
Build the bundle, then create a Release on the existing annotated tag and attach the .mcpb. The Release sits on top of the tag from wrapup — --verify-tag enforces that the tag already exists on the remote and prevents gh from creating a lightweight tag that would shadow the annotated one. --notes-from-tag pulls the tag annotation body as release notes. --title sets the release title from the tag subject — --notes-from-tag alone does NOT set the title (it defaults to the bare tag name, e.g. "v0.1.8" with no theme). The tag subject already omits the version number per the git-wrapup skill, so prepending v<VERSION>: produces the correct display title.
bun run bundle # produces dist/<name>.mcpb (stable filename, no version)
SUBJECT=$(git tag -l --format='%(contents:subject)' v<VERSION>)
gh release create v<VERSION> --verify-tag --notes-from-tag --title "v<VERSION>: $SUBJECT" dist/*.mcpb
The stable filename matters: it lets the README "Install in Claude Desktop" badge point at releases/latest/download/<name>.mcpb and always resolve to the most recent release. The bundle script in the templates outputs dist/{{PACKAGE_NAME}}.mcpb for this reason.
If the release already exists (re-invocation after a prior partial run), gh release create exits with "release already exists" — fall back to uploading the asset to the existing release:
gh release upload v<VERSION> dist/*.mcpb --clobber
Deterministic download URLs:
https://github.com/<OWNER>/<REPO>/releases/download/v<VERSION>/<name>.mcpbhttps://github.com/<OWNER>/<REPO>/releases/latest/download/<name>.mcpbIf server.json includes an MCPB packages[] entry, its identifier should match this URL and fileSha256 should match shasum -a 256 <bundle> — keep these in sync during wrapup, not here.
Halt on any error other than "release already exists" (handled via the upload fallback above).
Only if Dockerfile exists at the repo root (otherwise skip).
Derive:
OWNER/REPO from the origin remote URL — use your git tools to read it; strip .git, handle both https://github.com/<owner>/<repo> and git@github.com:<owner>/<repo> formsVERSION from package.json (step 1)docker buildx build --platform linux/amd64,linux/arm64 \
-t ghcr.io/<OWNER>/<REPO>:<VERSION> \
-t ghcr.io/<OWNER>/<REPO>:latest \
--push .
If the project uses a non-GHCR registry or a custom image name, respect the project's convention. If push fails with a 401/403, prompt the user to authenticate (echo $GITHUB_TOKEN | docker login ghcr.io -u <OWNER> --password-stdin) and retry. Halt on build failure or non-auth push failure.
Print clickable URLs for every destination that succeeded:
https://www.npmjs.com/package/<package.json#name>/v/<version>https://registry.modelcontextprotocol.io/v0.1/servers/<mcpName>/versions/<version> — mcpName is the name field from server.json (URL-encode the / as %2F)https://github.com/<OWNER>/<REPO>/releases/tag/v<VERSION> (with .mcpb asset attached)ghcr.io/<OWNER>/<REPO>:<VERSION>Skip any destination that was skipped in its step.
Confirm each published artifact is actually live — don't rely on a successful push exit code alone. For each destination that succeeded:
npm view <package.json#name>@<version> version — must return the version stringcurl -s "https://registry.modelcontextprotocol.io/v0.1/servers/<mcpName>/versions/<version>" — must return HTTP 200 with server.version matching <version> (mcpName is the name field from server.json; URL-encode / as %2F). The search endpoint (/v0.1/servers?search=) paginates and may not include the latest version for packages with many releases — always use the direct version lookup.gh release view v<VERSION> -R <OWNER>/<REPO> --json assets --jq '.assets[].name' — must list the .mcpb filecurl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" "https://ghcr.io/v2/<OWNER>/<REPO>/manifests/<VERSION>" — must return HTTP 200If any check fails, halt and report which destination is unreachable. A successful docker push or bun publish exit code does not guarantee the artifact is queryable — registry propagation delays, auth scoping, and partial failures all exist.
v<version>; current branch name noted for pushbun run devcheck passesbun run rebuild succeedsbun run test:all (or test) passesbun publish --access public succeedsbun run publish-mcp succeeds (if server.json present)bun run bundle + gh release create --verify-tag --notes-from-tag --title succeeds (if manifest.json present)Dockerfile present)