From dad9eb8d35576d32e5913a89648060bb4a213130 Mon Sep 17 00:00:00 2001 From: m1ngshum <140998506+m1ngshum@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:06:49 +0800 Subject: [PATCH] docs: distribution install matrix + name-collision decision (D6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the D6 name-collision blocker with data: `mcpm` is already the homebrew-core formula for the unrelated mcpm.sh project (and it squats PyPI), with its own `mcpm` bin. Decision: don't fight for `brew install mcpm` or the `mcpm` mise short-name — distribute through collision-free scoped channels. - README Quick start: an install matrix (npm / npx / pnpm / mise) all resolving @getmcpm/cli. mise uses its built-in npm: backend (mise use -g npm:@getmcpm/cli), verified against mise docs — no registry entry needed. - docs/DISTRIBUTION.md: documents the matrix, the name-collision decision, and the deferred external-repo work (homebrew tap, mise registry short-name, devcontainer). Docs-only. The homebrew tap + mise registry PR are outward-facing / cross-repo and left for owner authorization, documented as deferred (not blocking). --- CHANGELOG.md | 11 ++++++++++ CLAUDE.md | 1 + README.md | 17 +++++++++++++-- docs/DISTRIBUTION.md | 46 ++++++++++++++++++++++++++++++++++++++++ docs/ROADMAP-ADOPTION.md | 16 ++++++++++---- 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 docs/DISTRIBUTION.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 94900af..a4a10b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Documentation + +- **Install matrix + distribution doc (D6)** — the README now lists npm / npx / pnpm / + **mise** (`mise use -g npm:@getmcpm/cli`, via mise's built-in `npm:` backend — no + registry entry needed) as install channels, all resolving the scoped `@getmcpm/cli` + package. New `docs/DISTRIBUTION.md` documents the deliberate **name-collision + decision**: the `mcpm` Homebrew formula (and PyPI name) belong to the unrelated + [mcpm.sh](https://mcpm.sh) project, so mcpm ships through the collision-free scoped + channels rather than fighting for `brew install mcpm`. A Homebrew tap + mise + registry short-name are deferred (documented). + ### Added - **`mcpm audit --sarif` (D3)** — emit a SARIF 2.1.0 report for GitHub diff --git a/CLAUDE.md b/CLAUDE.md index df75b8b..e3dd8f1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -500,6 +500,7 @@ the same entry shape). | 2026-07-03 | v0.17.0 released (Wave-0 adoption credibility floor) + started Wave 1 with the Claude Code adapter (D1) | Following the new **docs/ROADMAP-ADOPTION.md**: v0.17.0 shipped the four Wave-0 items (E3 supply-chain evidence pack, B1a macOS CI leg, E11 `docs/CONTRACTS.md`, E9a registry-delisting gate). Then D1 (#117) added **Claude Code** as a 5th first-class client (`~/.claude.json`, user-global `mcpServers`) — a rootKey-only `BaseAdapter` subclass; **user-global scope only**, per-project `projects[].mcpServers` deliberately deferred. D1 is on `main`, ships next tag. | | 2026-07-03 | Publish workflow: attach the release SBOM at `gh release create` time, never via a follow-up `gh release upload` | The repo has GitHub **immutable releases** enabled: uploading an asset to an already-created release returns HTTP 422. v0.17.0's publish succeeded on npm but failed the release-asset step (SBOM), so its GitHub release is asset-less and can't be amended. Fix (#116): pass `mcpm.cdx.json` as a positional asset to `gh release create` so it's sealed atomically. Correct from v0.18.0 on. GOTCHA for any future release-pipeline work. | | 2026-07-03 | Wave-1 D4a: Gemini CLI as a 6th first-class client (`~/.gemini/settings.json`, user-global `mcpServers`) | Structural clone of D1 — another rootKey-only `BaseAdapter` subclass; verified the format against Google's gemini-cli docs (top-level `mcpServers`, home-relative on all platforms). Detector auto-enumerates it, so every client-iterating command works day one. **User-global scope only**, per-project `.gemini/settings.json` deliberately out of scope (same as D1). Caveat: Gemini reads `url`=SSE / `httpUrl`=HTTP; mcpm writes `url`, the existing non-Cursor URL-transport caveat. Merged to `main` (#120), ships next tag. | +| 2026-07-03 | Wave-1 D6: distribution — install matrix + `docs/DISTRIBUTION.md`; name-collision resolved by NOT fighting for `mcpm` | Verified with data: `mcpm` is ALREADY the homebrew-core formula for the unrelated **mcpm.sh** (Path Integral, v2.15.0, homepage mcpm.sh) + it squats PyPI; its bin is also `mcpm`. DECISION: don't publish a colliding `brew install mcpm` / claim the `mcpm` mise short-name (would fight a name we don't own + conflict on the `mcpm` bin). Distribute via collision-free SCOPED channels: README install matrix = npm/npx/pnpm + **mise `npm:@getmcpm/cli`** (mise's built-in npm backend, verified vs mise docs — NO registry PR needed). `docs/DISTRIBUTION.md` documents it. DEFERRED (external repos / outward-facing → need owner auth, NOT blocking): `getmcpm/homebrew-mcpm` tap (non-colliding formula name + bin decision) + mise registry short-name PR + devcontainer (needs project-scope adapter). Docs-only. On `main`, ships next tag. | | 2026-07-03 | Wave-1 D3: `mcpm audit --sarif` — SARIF 2.1.0 for GitHub code-scanning | Pure `src/output/sarif.ts` mapper beside the `--json` branch. One rule per real `Finding.type` (the union is **8** now, not the roadmap's "7"; the rule catalog is a TS-exhaustive `Record` so a new type fails the build). Each finding → a SARIF result anchored **file-level** to `mcpm.yaml` (audit scans INSTALLED servers → no source line; a fake line number would be a lie), + a `logicalLocation` naming the server, severity→`error`/`warning`/`note`, and a stable `partialFingerprints` (`server:type:sha256(msg)[:12]`) so GitHub tracks the same alert across runs. Report-only (never fixes, even with `--fix`); exit matches `audit` (risky→1) — README documents the `upload-sarif` + `if: always()` pattern. `__PKG_VERSION__` (tsup define) guarded with a `typeof` fallback since it's undefined under vitest. On `main`, ships next tag. | | 2026-07-03 | Wave-1 D2: `mcpm verify` (repo-only, client-free CI gate) + a composite GitHub Action; `classifyIntegrity`/`frozenVerdict` extracted to `src/stack/frozen-verify.ts` | The critique premise held: NO existing command runs on a hosted runner (`up` hard-fails at Step 3 on zero detected clients; `sync --check`/`audit` are vacuously green). So `mcpm verify` runs the SAME fail-closed integrity pass as `up --frozen` — extracted `classifyIntegrity` + a NEW pure `frozenVerdict` (structured pass/block decision) into a shared module both consume — but client-free (no detection, no `~/.mcpm`, no writes). Same BLOCK semantics + exit codes (1 on drift/unverifiable/format-mismatch/mixed-missing-baseline; benign refuse on lock-wide no-baseline; pypi/oci/url = unenforceable notice; exit 1 on no-lock, and verify NEVER auto-locks). `up`'s output/block-matrix is byte-identical (13 frozen tests unchanged — the refactor's safety net). `--json` emits a VerifyModel (`schemaVersion:1`). Composite Action `.github/actions/mcpm-verify` (step summary from `--json`, static shields badge, pre-commit snippet). **ONE verb**: B3 later EXTENDS `mcpm verify` with Sigstore provenance, never forks it. v1 = npm `dist.integrity` only; stack-vs-lock staleness deferred. On `main`, ships next tag. | | 2026-07-03 | Wave-1 D7: `doctor --json` / `--report` via a pure `DoctorModel` builder + redacted report; fixes the `mcpm_doctor` `issues: []` bug | `doctorHandler` split into `buildDoctorModel` (pure, structured — `schemaVersion:1`, clients w/ server+guarded counts, runtimes, advisory drift, typed issues, `ok`) → renderers. `--json` emits the model; the MCP `handleDoctor` now REUSES the model (was returning hardcoded `issues: []` + detected-clients-only). `--report` = a redacted paste-for-bug-reports snapshot: OS/arch, mcpm+node versions, per-client server **counts**, runtime availability, confine + secret-store backend, issue **counts** — deliberately **no server names/args** (issue messages embed names → reduced to counts; the tested security invariant). New `.github/ISSUE_TEMPLATE/bug.yml` requires a pasted report (telemetry-free friction channel). D7 is the FIRST of the four structured-output mappers (D3/D7/E2/E6) to land: the shared "one model" is the `schemaVersion` + JSON convention, NOT a monolithic type — doctor issues stayed a doctor-specific typed list, deliberately NOT forced into the audit `Finding` shape (different domain). doctor `--json`/`--report` shapes UNSTABLE (added to CONTRACTS). On `main`, ships next tag. | diff --git a/README.md b/README.md index 5098bf4..e996ec6 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,22 @@ The risky part of an MCP server doesn't show up at install -- it shows up while ## Quick start -```bash -npm install -g @getmcpm/cli +Install with the package manager you already use: + +| Method | Command | +|---|---| +| **npm** (global) | `npm install -g @getmcpm/cli` | +| **npx** (no install) | `npx @getmcpm/cli ` | +| **pnpm** | `pnpm add -g @getmcpm/cli` | +| **mise** | `mise use -g npm:@getmcpm/cli` | +The binary is `mcpm`. **Heads up:** the `mcpm` Homebrew formula is a *different, +unrelated* project ([mcpm.sh](https://mcpm.sh)) — install this mcpm via +npm/npx/pnpm/mise above (all resolve the scoped `@getmcpm/cli` package, so there's +no name collision). A dedicated Homebrew tap is deferred; see +[`docs/DISTRIBUTION.md`](docs/DISTRIBUTION.md). + +```bash mcpm search filesystem mcpm info io.github.domdomegg/filesystem-mcp mcpm install io.github.domdomegg/filesystem-mcp diff --git a/docs/DISTRIBUTION.md b/docs/DISTRIBUTION.md new file mode 100644 index 0000000..a9fc0b7 --- /dev/null +++ b/docs/DISTRIBUTION.md @@ -0,0 +1,46 @@ +# Distribution + +mcpm ships as the scoped npm package **`@getmcpm/cli`** (bin: `mcpm`). Every install +channel below resolves that one package, so there is no ambiguity about what you get. + +## Install matrix + +| Method | Command | Notes | +|---|---|---| +| **npm** | `npm install -g @getmcpm/cli` | global bin `mcpm` | +| **npx** | `npx @getmcpm/cli ` | no install; always latest | +| **pnpm** | `pnpm add -g @getmcpm/cli` | | +| **mise** | `mise use -g npm:@getmcpm/cli` | via mise's built-in `npm:` backend — no registry entry needed | + +`npx @getmcpm/cli` is what the CI surfaces (`mcpm verify`, `mcpm audit --sarif`) and +the GitHub Action use, so it is the most-exercised path. + +## The `mcpm` name collision (why no `brew install mcpm`) + +An **unrelated** project — Path Integral's [mcpm.sh](https://mcpm.sh) — already owns +the short name `mcpm`: + +- it is the **`mcpm` formula in Homebrew core** ("Open source, community-driven MCP + server and client manager", homepage `mcpm.sh`), and +- it squats the `mcpm` name on PyPI. + +Its bin is also called `mcpm`. So we deliberately **do not** publish a colliding +`brew install mcpm` or claim the `mcpm` short-name on registries that already point at +mcpm.sh — that would fight over a name we don't own and would conflict on the `mcpm` +binary if a user had both installed. + +**Resolution:** distribute through the scoped, collision-free channels above +(`@getmcpm/cli` on npm; `npm:@getmcpm/cli` on mise). The `getmcpm` scope and the +`getmcpm/cli` repo are the canonical identity. + +## Deferred + +- **Homebrew tap** (`getmcpm/homebrew-mcpm`) — a tap could ship a non-colliding + formula (e.g. `getmcpm`), but it needs a separate repo under the org and a decision + on the installed bin name (it can't shadow core `mcpm`). Deferred until there's + demand the npm/npx/mise channels don't already cover. +- **mise registry short-name** (`mise use mcpm`) — would require a PR to the mise + registry for a name that resolves to mcpm.sh's world; the `npm:@getmcpm/cli` backend + form works today without it. +- **devcontainer feature** — deferred until a project-scope adapter exists (inside a + container there are no host GUI client configs to manage). diff --git a/docs/ROADMAP-ADOPTION.md b/docs/ROADMAP-ADOPTION.md index 34daa83..079a7c6 100644 --- a/docs/ROADMAP-ADOPTION.md +++ b/docs/ROADMAP-ADOPTION.md @@ -202,11 +202,19 @@ The completeness critic's verdict: the candidate set over-indexed enterprise for `mcpm.yaml` (file-level, no fake line numbers) + a `logicalLocation` for the server name + a stable `partialFingerprints`. Report-only, exit matches `audit` (risky→1). GitHub code-scanning upload documented in the README. **Cut:** guard-events SARIF. -- **D6 · Distribution (trimmed).** mise registry PR (near-zero — npm backend alias) + - `getmcpm/homebrew-mcpm` tap + README install matrix. Framed honestly as - convenience, not enterprise unblock. **Cut:** devcontainer feature until a +- **D6 · Distribution (trimmed). ✅ SHIPPED** (the in-repo slice; on `main`, ships next + tag). **Name-collision blocker RESOLVED with data**: `mcpm` is already the + homebrew-core formula for the unrelated [mcpm.sh](https://mcpm.sh) (v2.15.0) and it + squats PyPI — its bin is also `mcpm`. **Decision:** do NOT fight for `brew install + mcpm` / the `mcpm` mise short-name; distribute through the collision-free scoped + channels. Shipped: README install matrix (npm / npx / pnpm / **mise** via the + built-in `npm:@getmcpm/cli` backend — verified against mise docs, **no registry PR + needed**) + `docs/DISTRIBUTION.md` documenting the decision. **Deferred (external + repos / outward-facing, need owner action):** the `getmcpm/homebrew-mcpm` tap (a + non-colliding formula name + bin decision) and a mise registry short-name PR — both + documented as deferred, not blocking. **Cut:** devcontainer feature until a project-scope adapter exists (inside a container there are no host GUI configs to - manage). **Blocker:** resolve the mcpm.sh name collision first. + manage). **Awareness (non-code, runs alongside Wave 1):** submit to the MCP-security roundups/awesome-lists that currently omit mcpm; a comparison page (mcpm vs