Skip to content

feat(mcp): add npm wrapper and MCP Registry metadata - #339

Merged
bomly-guy merged 5 commits into
mainfrom
feat/mcp-registry-npm-wrapper
Jul 31, 2026
Merged

feat(mcp): add npm wrapper and MCP Registry metadata#339
bomly-guy merged 5 commits into
mainfrom
feat/mcp-registry-npm-wrapper

Conversation

@bomly-guy

@bomly-guy bomly-guy commented Jul 26, 2026

Copy link
Copy Markdown
Member

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 npx install.

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 thin bomly-mcp package, zero dependencies.
    • scripts/postinstall.js downloads bomly_<version>_<goos>_<goarch>.{tar.gz,zip} from the matching release, verifies it against that release's SHA256SUMS, and unpacks the binary plus the archive's LICENSE, NOTICE, and licenses/ into vendor/. A mismatch fails the install — there is no unverified fallback.
    • bin/bomly-mcp.js execs bomly mcp serve with stdio inherited, so stdout stays pure JSON-RPC, and forwards SIGINT/SIGTERM/SIGHUP so the child never orphans.
    • scripts/prepack.js copies the repo LICENSE and NOTICE into the package at pack time.
    • BOMLY_MCP_VERSION pins a version; BOMLY_MCP_SKIP_DOWNLOAD=1 skips the download for lint/CI installs.
  • server.json — registry metadata, schema 2025-12-11, naming io.github.bomly-dev/bomly-cli and the bomly-mcp npm package.
  • glama.json — display name, description, and keywords for Glama's indexer, so that listing reads correctly on day one.
  • release.ymlpublish-npm then publish-mcp-registry, split so either half can be re-run alone. npm auth is Trusted Publishing (OIDC); no token anywhere.
  • ci.yml — a npm wrapper job running the new tests and asserting npm/package.json, server.json, and mcpName agree.
  • auto-version.yml — bumps the version in npm/package.json and server.json alongside cmd/bomly/main.go.
  • Docs: an npx path in docs/MCP.md and the README's AI-agents section.

Ordering, and why it matters

publish-npm runs needs: publish. Releases stay drafts until the publish job finalizes them, and draft-release assets are not publicly downloadable — so publishing npm any earlier would ship a package whose postinstall 404s. publish-mcp-registry then waits for npm's CDN before calling mcp-publisher, because the registry verifies ownership by fetching the package.

Review round 1 — what changed

Finding Fix
NPM_TOKEN left in ~/.npmrc, readable by later steps including mcp-publisher Token removed entirely — switched to npm Trusted Publishing (OIDC) after npm's token screen flagged long-lived automation tokens. Nothing left to scope or leak
Not retryable after a partial success — npm publish of an immutable version blocks the registry step behind it Split into two jobs; the npm job treats an already-published exact version as success and continues
mcp-publisher streamed from releases/latest and executed unverified in a job with id-token: write Pinned to v1.8.0, verified twice before extraction: pinned SHA-256, and cosign verify-blob against the Sigstore bundle asserting the registry's own tagged release workflow as signer
Archive LICENSE/NOTICE/licenses/** discarded; npm tarball carried no license either Both fixed — prepack for the package, copyNotices for the vendored binary. Covered by tests
PowerShell path interpolation breaks on apostrophes and is an injection surface Extraction now runs a -File script with paths as real argv
No automated coverage for the JS installer/launcher 16 tests in npm/test, wired into CI
SHA256SUMS undefined at first use (CodeRabbit) Defined in docs/MCP.md
persist-credentials not disabled (CodeRabbit) false on both checkouts
Signal listener not removed before re-raising (CodeRabbit) Fixed; see below

The 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:

FIXED:   {"code":null,"signal":"SIGTERM"}
PRE-FIX: {"code":0,"signal":null}

Before this can run — needs you

No npm token. publish-npm uses 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-registry authenticates the same way. This replaced an earlier NPM_TOKEN design 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):

  1. Publish bomly-mcp once by hand, from a checkout at an already-published release tag:

    cd npm && npm publish --access public

    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.

  2. Set the trusted publisher at npmjs.com/package/bomly-mcp/access → repo bomly-dev/bomly-cli, workflow release.yml. It is on the package access page, not the account packages page.

  3. Turn the jobs on:

    gh variable set PUBLISH_MCP_REGISTRY -R bomly-dev/bomly-cli --body true

bomly-mcp is unclaimed on npm. The registry namespace io.github.bomly-dev/* comes from this repo's identity, no secret needed.

One thing to check: glama.json lists bomly-guy under maintainers, 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 version0.21.1, and vendor/ contains bomly, LICENSE, NOTICE, licenses/.
  • Packed tarball contains LICENSE, NOTICE, README.md, bin/, scripts/ — and no vendor/.
  • Real MCP handshake through ./node_modules/.bin/bomly-mcp: initialize{"name":"bomly","version":"0.21.1"}, tools/list → all four tools, bomly_scan → a schema_version: "mcp/1" result. All three stdout lines were JSON-RPC; the banner stayed on stderr.
  • npm test → 16/16.
  • server.json validates against the registry's 2025-12-11 schema (ajv).
  • The exact cosign verify-blob command from the workflow → Verified OK, and the pinned SHA-256 matches the published v1.8.0 artifact.
  • make generate produces no drift.

Not yet exercised: the publish jobs themselves, which cannot run until the secret and variable above exist.

🤖 Generated with Claude Code

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>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a bomly-mcp npm wrapper that downloads and verifies platform-specific binaries. It adds MCP metadata and usage documentation. Release workflows now version and publish the npm package and MCP Registry entry.

Changes

MCP distribution

Layer / File(s) Summary
npm wrapper installation and execution
npm/package.json, npm/scripts/postinstall.js, npm/bin/bomly-mcp.js
The package downloads and verifies a platform-specific Bomly binary, installs it under vendor/, and launches bomly mcp serve with forwarded arguments and signals.
MCP metadata and usage documentation
server.json, glama.json, npm/README.md, README.md, docs/MCP.md, .gitignore
The repository adds MCP server metadata, npm package documentation, npx usage instructions, and ignore rules for generated wrapper artifacts.
Release versioning and registry publication
.github/workflows/auto-version.yml, .github/workflows/release.yml
Release workflows synchronize versions across package metadata, publish bomly-mcp with provenance, wait for npm visibility, and publish the server to the MCP Registry when enabled.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes: an npm wrapper and MCP Registry metadata.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mcp-registry-npm-wrapper

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Bomly Diff Summary

Compared d4244568c0a1a2d98197bae25f428c4856042c4f to 5799865ee81004d63cf6811ee4d1d4584c6e211b.

Overview

Status Manifests Dependencies Findings Duration
✅ Pass +0 / ~0 / -0 0 added / 0 version changed / 0 detail changes / 0 removed 0 introduced / 0 persisted / 0 resolved 1m 21s

Dependency Changes

✅ No dependency changes.

Vulnerabilities

✅ No vulnerability changes.

License Changes

✅ No license changes.

Project Posture

✅ No project posture changes (--matchers +scorecard was not selected).

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>
@bomly-guy
bomly-guy marked this pull request as ready for review July 31, 2026 05:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d424456 and 91a0553.

📒 Files selected for processing (11)
  • .github/workflows/auto-version.yml
  • .github/workflows/release.yml
  • .gitignore
  • README.md
  • docs/MCP.md
  • glama.json
  • npm/README.md
  • npm/bin/bomly-mcp.js
  • npm/package.json
  • npm/scripts/postinstall.js
  • server.json

Comment thread .github/workflows/release.yml
Comment thread docs/MCP.md Outdated
Comment thread npm/bin/bomly-mcp.js Outdated

@bomly-guy bomly-guy left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread npm/scripts/postinstall.js
Comment thread npm/scripts/postinstall.js Outdated
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>
@bomly-guy

Copy link
Copy Markdown
Member Author

All six addressed in 9619fb0, plus the version bump to 0.21.1.

Credential scoping. You were right that env: does not scope a file. The token now goes to $RUNNER_TEMP/.npmrc selected by NPM_CONFIG_USERCONFIG, written under umask 077, removed by a trap ... EXIT so a failed publish still cleans up. Beyond that I split the job in two: publish-mcp-registry is now a separate job, so the step that runs the downloaded mcp-publisher binary never receives NPM_TOKEN in its environment at all.

Retryability. Split into publish-npmpublish-mcp-registry, and the npm job now checks the registry for the exact version first and exits 0 if it is already live. So a re-run after a partial success walks through the npm half and reaches the registry step instead of dying on the immutable version.

Pinning and verification. Pinned to v1.8.0 with two independent checks before anything is extracted or executed: the pinned SHA-256, and cosign verify-blob against the release's Sigstore bundle with --certificate-identity asserting modelcontextprotocol/registry/.github/workflows/release.yml@refs/tags/v1.8.0 and --certificate-oidc-issuer the GitHub Actions issuer. I ran that exact command locally against the published artifact — Verified OK. Version, digest, and identity are three env: values at the top of the job so a bump is one reviewed change.

Licenses and notices. Both halves fixed. scripts/prepack.js copies the repo LICENSE and NOTICE into the package at pack time (gitignored inside npm/, so there is no second copy to drift), and copyNotices preserves the archive's LICENSE, NOTICE, and licenses/ next to the vendored binary. Confirmed on a clean install: vendor/ now holds bomly, LICENSE, NOTICE, licenses/, and npm pack lists LICENSE and NOTICE. Both are covered by tests.

PowerShell quoting. Replaced with a -File script taking -Archive and -Destination as parameters, so the paths arrive as real argv entries and never touch a command string. The apostrophe case and the injection surface both go away.

Tests. Added 16 under npm/test, run by a new npm wrapper CI job, covering everything you listed: platform mapping including unsupported targets, SHA256SUMS parsing (prefix-colliding names, missing entry, empty file), checksum sensitivity, archive extraction, notice propagation, launcher argument pass-through, a clean stdout, exit-code propagation, both signal paths, and the packed tarball's contents. CI also asserts npm/package.json, server.json, and mcpName agree, which is the drift most likely to slip through.

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:

FIXED:   {"code":null,"signal":"SIGTERM"}
PRE-FIX: {"code":0,"signal":null}

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>
@bomly-guy
bomly-guy merged commit 2f903c9 into main Jul 31, 2026
15 checks passed
@bomly-guy
bomly-guy deleted the feat/mcp-registry-npm-wrapper branch July 31, 2026 06:30
@bomly-guy
bomly-guy restored the feat/mcp-registry-npm-wrapper branch July 31, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant