feat(mcp): add npm wrapper and MCP Registry metadata - #339
Conversation
Bomly ships a Go binary, and the official MCP Registry only accepts npm, PyPI, NuGet, Cargo, OCI, or MCPB packages. That gap is the only thing keeping Bomly out of the registry and out of the community MCP directories that key off an `npx` install line. Adds a thin `bomly-mcp` npm package whose postinstall downloads the release archive for the host platform, verifies it against that release's SHA256SUMS, and unpacks the binary. `bin/bomly-mcp.js` execs `bomly mcp serve` with stdio inherited, so stdout stays pure JSON-RPC. A checksum mismatch fails the install; there is no unverified fallback. Adds `server.json` (schema 2025-12-11) naming the package, and a `mcp-registry` job that publishes both after the release is published — draft-release assets are not publicly downloadable, so an earlier npm publish would ship a package that cannot install. The job is inert until NPM_TOKEN and the PUBLISH_MCP_REGISTRY variable are set. Registry auth is GitHub OIDC and needs no secret. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR adds a ChangesMCP distribution
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MCPClient
participant NPM
participant Postinstall
participant GitHubRelease
participant BomlyBinary
MCPClient->>NPM: Run npx -y bomly-mcp
NPM->>Postinstall: Execute package installation
Postinstall->>GitHubRelease: Download release archive and SHA256SUMS
GitHubRelease-->>Postinstall: Return archive and checksum data
Postinstall->>Postinstall: Verify and install the binary
NPM->>BomlyBinary: Start mcp serve
BomlyBinary-->>MCPClient: Serve MCP requests over stdio
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bomly Diff SummaryCompared Overview
Dependency Changes✅ No dependency changes. Vulnerabilities✅ No vulnerability changes. License Changes✅ No license changes. Project Posture✅ No project posture changes ( Policy Findings✅ No policy differences were identified. |
Glama clones the repo and indexes tools and schemas from it. glama.json controls the display name, description, and keywords it shows, instead of letting it infer them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Around line 252-253: Harden the release workflow around mcp-publisher: use a
reviewed pinned release instead of streaming releases/latest, verify its archive
checksum or Sigstore bundle before extraction and execution, remove ~/.npmrc
immediately after npm publish, and set actions/checkout persist-credentials to
false when the checkout token is not needed later.
In `@docs/MCP.md`:
- Around line 38-40: Update the first mention of SHA256SUMS in the installation
description to identify it as the file listing the release archive’s SHA-256
checksums, using plain language while preserving the existing checksum
verification behavior.
In `@npm/bin/bomly-mcp.js`:
- Around line 38-45: Update the child exit handler around the signal listeners
and child.on("exit") so it removes the corresponding process signal listener
before re-sending the signal, then explicitly calls process.exit() to terminate
the wrapper. Preserve the existing behavior for non-signal exits.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c51a847f-cfa0-4748-a588-33339ef16fd6
📒 Files selected for processing (11)
.github/workflows/auto-version.yml.github/workflows/release.yml.gitignoreREADME.mddocs/MCP.mdglama.jsonnpm/README.mdnpm/bin/bomly-mcp.jsnpm/package.jsonnpm/scripts/postinstall.jsserver.json
bomly-guy
left a comment
There was a problem hiding this comment.
Found five actionable issues in the release/npm path; details are inline. The new JavaScript installer and launcher also have no automated coverage yet—please add focused tests for checksum parsing/failure, archive contents, platform mapping, npm package contents, and child-process shutdown behavior.
Review follow-ups on the npm wrapper, plus the version bump to 0.21.1 (npm/package.json and server.json; the CLI moved while this sat open). Release workflow - Split `mcp-registry` into `publish-npm` and `publish-mcp-registry`. npm versions are immutable, so a failure after a successful publish left the old single job unrerunnable — it died on the duplicate version before ever reaching the registry. The npm job now treats "this exact version is already live" as success, and the registry half can be re-run on its own. The registry job also never sees NPM_TOKEN. - Stop writing the token to ~/.npmrc. A step-scoped `env:` does not scope a file, so it stayed readable by every later step, including the downloaded mcp-publisher binary. It now goes to a job-temp npmrc selected by NPM_CONFIG_USERCONFIG, written with umask 077 and removed by a trap. - Pin mcp-publisher to v1.8.0 and verify it before execution, by pinned SHA-256 and by cosign against the Sigstore bundle, asserting the archive came from the registry's own tagged release workflow. `releases/latest` was a moving target executed inside a job holding an OIDC identity that can publish under our namespace. - persist-credentials: false on both checkouts. npm wrapper - Ship LICENSE and NOTICE in the package (copied at prepack), and keep the release archive's LICENSE, NOTICE, and licenses/ next to the vendored binary. Both were being dropped, which redistributed Bomly and its bundled components with the notices stripped. - Extract zips via a -File script with the paths as real argv. The old -Command string broke on any path holding an apostrophe (C:\Users\O'Brien\...) and let a path influence the command. - Remove our own signal listener before re-raising. Registering it replaced Node's terminate-on-signal default, so re-raising re-entered the handler, the loop drained, and the wrapper exited 0 — reporting a clean shutdown to its supervisor when the server had been killed. Tests - 16 tests under npm/test, run by a new `npm wrapper` CI job: platform mapping incl. unsupported targets, SHA256SUMS parsing (prefix-colliding names, missing entry, empty file), checksum sensitivity, archive extraction, notice propagation, launcher arg pass-through, a clean stdout, exit-code propagation, both signal paths, and the packed tarball's contents. The signal test was checked against the pre-fix code and fails there. - CI also asserts npm/package.json, server.json, and mcpName agree. docs/MCP.md now says what SHA256SUMS is on first use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
All six addressed in 9619fb0, plus the version bump to 0.21.1. Credential scoping. You were right that Retryability. Split into Pinning and verification. Pinned to Licenses and notices. Both halves fixed. PowerShell quoting. Replaced with a Tests. Added 16 under On CodeRabbit's signal comment — worth flagging, because the diagnosis in the thread is not quite what was happening. It is not a hang. With our listener still attached, re-raising re-entered our own handler, the event loop then drained, and the wrapper exited 0: a supervisor was told the server shut down cleanly when it had in fact been killed. Probed both ways: The test asserting that fails against the pre-fix code, so it is guarding the real behavior rather than just passing. |
npm's token screen warns against long-lived automation tokens, and it is right: the previous design put one in repo secrets and then spent real effort keeping it away from the rest of the release job. Trusted publishing removes the credential instead of containing it — npm exchanges the workflow's OIDC identity for publish rights, so there is no secret to scope, rotate, or leak. - Drops NPM_TOKEN entirely. `id-token: write` was already there for provenance and now does double duty. - Installs npm@^11.5.1: trusted publishing needs it and the runner image bundles an older npm with its Node. - Keeps --provenance explicit. Trusted publishing is documented to imply it, but the implicit path has been reported to no-op. - Publishes from `working-directory: npm` rather than `npm publish ./npm` so npm resolves publishConfig and the repository field unambiguously. Trusted publishing cannot bootstrap a package that does not exist yet — it is configured per package on npmjs.com. That first publish is a one-off manual `npm publish` from a maintainer's machine, which is also why no automation token needs to exist at any point. Documents the whole path in dev-docs/CI.md: the one-time setup, the npm version floor, why there are two jobs, and why mcp-publisher is pinned and signature-verified. The job-split comment claimed token isolation as a reason; with no token left, only the retryability argument stands, and it now says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unblocks L3-1 in the marketing listings tracker: publishing Bomly to the official MCP Registry.
Why
The registry accepts only npm, PyPI, NuGet, Cargo, OCI, or MCPB packages. Bomly ships a Go binary, so there was nothing to point a registry entry at. The same gap blocks the community MCP directories (mcpservers.org, PulseMCP, Glama, MCP.so), which all expect a one-line
npxinstall.Of the eligible paths, npm gives the best install story for MCP clients. OCI would force users to volume-mount the project being scanned; MCPB would mean one bundle carrying every platform's binary, with narrower client support.
Targets v0.21.1 (rebased onto main; the CLI released twice while this sat open).
What
npm/— a thinbomly-mcppackage, zero dependencies.scripts/postinstall.jsdownloadsbomly_<version>_<goos>_<goarch>.{tar.gz,zip}from the matching release, verifies it against that release'sSHA256SUMS, and unpacks the binary plus the archive'sLICENSE,NOTICE, andlicenses/intovendor/. A mismatch fails the install — there is no unverified fallback.bin/bomly-mcp.jsexecsbomly mcp servewith stdio inherited, so stdout stays pure JSON-RPC, and forwards SIGINT/SIGTERM/SIGHUP so the child never orphans.scripts/prepack.jscopies the repoLICENSEandNOTICEinto the package at pack time.BOMLY_MCP_VERSIONpins a version;BOMLY_MCP_SKIP_DOWNLOAD=1skips the download for lint/CI installs.server.json— registry metadata, schema2025-12-11, namingio.github.bomly-dev/bomly-cliand thebomly-mcpnpm package.glama.json— display name, description, and keywords for Glama's indexer, so that listing reads correctly on day one.release.yml—publish-npmthenpublish-mcp-registry, split so either half can be re-run alone. npm auth is Trusted Publishing (OIDC); no token anywhere.ci.yml— anpm wrapperjob running the new tests and assertingnpm/package.json,server.json, andmcpNameagree.auto-version.yml— bumps the version innpm/package.jsonandserver.jsonalongsidecmd/bomly/main.go.npxpath indocs/MCP.mdand the README's AI-agents section.Ordering, and why it matters
publish-npmrunsneeds: publish. Releases stay drafts until thepublishjob finalizes them, and draft-release assets are not publicly downloadable — so publishing npm any earlier would ship a package whose postinstall 404s.publish-mcp-registrythen waits for npm's CDN before callingmcp-publisher, because the registry verifies ownership by fetching the package.Review round 1 — what changed
NPM_TOKENleft in~/.npmrc, readable by later steps includingmcp-publishernpm publishof an immutable version blocks the registry step behind itmcp-publisherstreamed fromreleases/latestand executed unverified in a job withid-token: writecosign verify-blobagainst the Sigstore bundle asserting the registry's own tagged release workflow as signerLICENSE/NOTICE/licenses/**discarded; npm tarball carried no license eitherprepackfor the package,copyNoticesfor the vendored binary. Covered by tests-Filescript with paths as real argvnpm/test, wired into CISHA256SUMSundefined at first use (CodeRabbit)docs/MCP.mdpersist-credentialsnot disabled (CodeRabbit)falseon both checkoutsThe signal one was worth the dig. The bug was not a hang: with our listener still attached, re-raising re-entered our own handler, the event loop drained, and the wrapper exited 0 — telling a supervisor the server shut down cleanly when it had actually been killed. Verified by probe, and the test that covers it fails against the pre-fix code:
Before this can run — needs you
No npm token.
publish-npmuses npm Trusted Publishing — npm exchanges the workflow's OIDC identity for publish rights, so there is no secret to store, scope, or rotate.publish-mcp-registryauthenticates the same way. This replaced an earlierNPM_TOKENdesign after npm's own token screen flagged it.Trusted publishing is configured per package on npmjs.com, so it cannot create a package that does not exist yet. Setup is one-time (full version in
dev-docs/CI.md):Publish
bomly-mcponce by hand, from a checkout at an already-published release tag:From your machine with
npm login— not CI, so no automation token ever has to exist. The version must match a published GitHub release, since the postinstall downloads that release's archive.Set the trusted publisher at
npmjs.com/package/bomly-mcp/access→ repobomly-dev/bomly-cli, workflowrelease.yml. It is on the package access page, not the account packages page.Turn the jobs on:
bomly-mcpis unclaimed on npm. The registry namespaceio.github.bomly-dev/*comes from this repo's identity, no secret needed.One thing to check:
glama.jsonlistsbomly-guyundermaintainers, because that is the account with commits here. If you submit to Glama under a different handle, add it.Verified locally against v0.21.1
npm pack, then a clean install of the tarball into a scratch project: binary downloaded, checksum verified,bomly version→0.21.1, andvendor/containsbomly,LICENSE,NOTICE,licenses/.LICENSE,NOTICE,README.md,bin/,scripts/— and novendor/../node_modules/.bin/bomly-mcp:initialize→{"name":"bomly","version":"0.21.1"},tools/list→ all four tools,bomly_scan→ aschema_version: "mcp/1"result. All three stdout lines were JSON-RPC; the banner stayed on stderr.npm test→ 16/16.server.jsonvalidates against the registry's2025-12-11schema (ajv).cosign verify-blobcommand from the workflow →Verified OK, and the pinned SHA-256 matches the published v1.8.0 artifact.make generateproduces no drift.Not yet exercised: the publish jobs themselves, which cannot run until the secret and variable above exist.
🤖 Generated with Claude Code