# new-dependency-evaluation

Adopt this skill: https://proskills.md/skills/other/salemalem-npmscan-mcp-plugin-skills-new-dependency-evaluation

Page: https://proskills.md/skills/other/salemalem-npmscan-mcp-plugin-skills-new-dependency-evaluation
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: skills/new-dependency-evaluation/SKILL.md
size: 14908
sha: 3bc53f6cb9a73742f77d2c43aee66d8dbe70062e
upstream: https://github.com/salemalem/npmscan-mcp-plugin/blob/main/skills/new-dependency-evaluation/SKILL.md

```text
---
name: new-dependency-evaluation
description: Help decide what to add as a NEW npm dependency — comparing 2-5 named candidates for the same job, evaluating one named candidate against its real peers, or shortlisting candidates from a described need, before anything is installed. Use for "which should we use for X," "axios vs got vs node-fetch," "is X a good pick for Y," "what should we use to do Z," "should we add X or is there something better." Not for auditing packages already in the project (see dependency-audit), not for a deep single-package compromise/trust investigation (see package-trust-check), and not for a plain factual question with no choice being made ("what does X do").
---

# NPMScan new dependency evaluation

Use this skill when the user is making a **forward-looking choice** about
what to add to a project — not investigating something already installed.
It orchestrates two tools that already do the hard work individually:
`suggest_alternative` (turns one candidate name into a real shortlist of
peers) and `compare_packages` (fans out enrichment across 2-5 named
candidates and returns a structured side-by-side with a deterministic pick).
This skill's only job is routing to the right combination of the two based
on how many candidates the user actually named, then presenting the result.

If the user instead asks whether a package **already in their project** is
safe to keep, use `package-trust-check` (single package) or `dependency-audit`
(a pasted inventory) — those investigate what's already there; this skill
evaluates what to add next. If the question has no choice or decision angle
at all ("what does X do," "what's the latest version of X"), just answer
directly with `get_package` — don't invoke this skill's tool chain for that.

## Connecting the npmscan tools

This skill calls the npmscan MCP server's tools (listed under "Tools used"
below). They only read public package data: no API key or account, nothing is
installed or changed on the user's machine, and the only server contacted is
`https://npmscan.com/api/mcp`.

- **Claude Code, Codex, Gemini CLI, Kimi, Grok:** the npmscan plugin connects
  the server for you.
- **OpenClaw:** `openclaw mcp add npmscan --url https://npmscan.com/api/mcp --transport streamable-http`,
  then `openclaw mcp doctor npmscan --probe` to check it.
- **Any other MCP client:** add a remote (streamable HTTP) server with that URL.

Some clients prefix tool names with the server name (for example
`npmscan__get_package`). If the tools aren't available at all, tell the user
how to connect them for their client instead of answering from memory.

## Determining what to compare

Count how many named candidates the user actually gave, then route:

1. **2-5 named candidates for the same job** (e.g. "axios vs got vs
   node-fetch," "should we use dayjs or date-fns") — go straight to
   `compare_packages({ packages })`. This is the common case and needs no
   extra tool call first.
2. **Exactly 1 named candidate** ("should we add uuid," "is left-pad a good
   choice," "can we use X for Y") — a single package has nothing to be
   compared against yet. Call `get_package({ name })` for the named
   candidate's own `description` (`suggest_alternative`'s `source` object
   doesn't include one), then `suggest_alternative({ name, reason:
   "general", limit: 4 })` to generate real peers (never invent
   plausible-sounding package names yourself). Screen the suggestions
   against that description per
   [Screening candidates before comparing](#screening-candidates-before-comparing-routing-cases-2-3)
   below, then call `compare_packages` with `[name, ...screened
   suggestions, up to 4]` so the original candidate is scored against real
   competition instead of judged in isolation. If `suggestions` comes back
   with fewer than 1 usable peer after screening (e.g. a built-in
   replacement fully covers the case, like `left-pad` →
   `String.prototype.padStart()`), skip `compare_packages` — there's
   nothing left to compare — and report the `nonPackageAlternatives` plus
   the `get_package` baseline on the named candidate instead.
3. **0 named candidates, just a described need** ("what should we use to
   parse dates," "we need something for HTTP retries") — call
   `search_packages({ query })` using the user's own description as the
   query. Build a 2-5 name shortlist from the results, preferring the
   highest `popularityTier`/`maintenanceTier` matches, skipping any result
   carrying `possibleTyposquatOf`, and screening each result's
   `description` against the user's *stated need* (their own words are the
   comparison anchor here — no extra fetch needed) the same way as case 2
   — then run `compare_packages` on that shortlist. If nothing in the
   search results looks like a real contender (all very-low
   popularity/stale, or nothing actually matches the described need), say
   so rather than forcing a comparison, and ask the user for a starting
   name or two.
4. **More than 5 named candidates** — `compare_packages` caps at 5. Don't
   silently drop candidates without saying so: run it on the first 5 and
   name which were left out, or ask the user to narrow the list if the
   ones dropped seem like they'd matter to the decision.

### Screening candidates before comparing (routing cases 2-3)

`suggest_alternative`'s `categoryOverlap` and `search_packages`'s result
ordering are both keyword/token-overlap signals, not semantic relevance —
and a single shared generic word (e.g. both packages' descriptions mention
"guid") combined with high popularity can rank an unrelated package above
genuinely relevant ones. This was confirmed directly: comparing candidates
for `uuid` (an RFC9562 UUID *generator*, `description: "RFC9562 UUIDs"`)
surfaced `win-guid` (`description: "Windows legacy GUID parser"` — an
unrelated Windows binary-format tool) as the top suggestion, ranked above
the real UUID library `@paralleldrive/cuid2`, purely because `win-guid`
happens to have very high download counts and shares the single word
"guid." Reading the two descriptions side by side makes the mismatch
obvious immediately ("generates RFC-standard UUIDs" vs. "parses legacy
Windows binary GUID structures") in a way `categoryOverlap`'s token count
alone does not catch. So for every candidate before it enters
`compare_packages`:

- Actually read and compare full description text, not just token
  overlap — the source's own `description` (from `get_package` in case 2,
  or the user's stated need in case 3) against the candidate's
  `description`. Ask in plain terms: does this candidate do the same job,
  or does it just share vocabulary with something that does? Drop
  candidates that fail this even if their `categoryOverlap` list looks
  populated — shared words are a hint to go check, not a verdict on their
  own.
- Treat a `categoryOverlap` of exactly one generic word (`"guid"`,
  `"data"`, `"util"`, etc.) as weak supporting evidence at best; two or
  more overlapping tokens, or overlap on a specific/technical term, is
  stronger — but the description comparison above is the actual decision,
  not the token count.
- Don't let a high `weeklyDownloads`/`popularityTier` on its own excuse a
  poor description match — a package can be extremely popular as a
  transitive dependency of something unrelated to the job at hand.

## Steps

1. Route per the table above and make the tool call(s).
2. Read `differentiators` before `recommendation` — it names which
   candidate(s) stand out on downloads, GitHub stars, TypeScript support,
   known vulnerabilities, deprecation, typosquat flag, install-script risk,
   and install-size footprint. This is what makes the comparison legible; don't just report the
   final pick with no supporting detail.
3. Report `recommendation.pick`, `runnerUp`, and `rationale` verbatim —
   don't substitute your own judgment for the deterministic score unless a
   `candidates[]` entry shows something the score can't see (e.g. the user
   already said they need TypeScript-first and two candidates are close).
   Always state `confidence` too — a `"low"` confidence pick between two
   close candidates is a materially different answer than a `"high"`
   confidence one.
4. Surface every `found: false` candidate with its `resolutionError`
   (typo? unpublished? malformed name?) rather than silently dropping it
   from the comparison you present.
5. Note that `installScriptRisk` here is the **lifecycle-scripts-only**
   signal (`scanScope: "lifecycle-scripts-only"`) — it scans the command
   strings, not the tarball. If the recommended pick has a nonzero
   `installScriptRisk.totalScore` and the user is about to actually install
   it, mention that `analyze_install_script` (via `package-trust-check`)
   gives the deeper, tarball-aware scan before they commit — don't run it
   automatically as part of this skill, just point at it. A candidate whose
   only lifecycle script is `prepare` (uuid's `lefthook install`) scores 0:
   npm never runs a dependency's `prepare` on install, so it is not a risk
   to weigh against it.
6. If the user's decision also turns on license policy (they mention a
   license constraint, or ask "which of these is safe to use license-wise"),
   follow up with `check_license_compliance` on the shortlist — it's not
   part of the default flow, only pull it in when license is actually in
   play.
7. Sanity-check `githubStars` against `weeklyDownloads` per candidate:
   `githubStars` is attributed to whatever repository the candidate's own
   `package.json` declares, unverified — a tiny, low-download package
   showing a huge star count (e.g. thousands of stars on a package with
   under a thousand weekly downloads) can mean its declared `repository`
   field points at a different, unrelated project's repo rather than its
   own (confirmed directly: `@aigne/uuid`, ~850 weekly downloads, declares
   `repository: github.com/uuidjs/uuid` — the real `uuid` package's repo,
   not its own — and so inherits that repo's star count). Flag a large
   downloads/stars mismatch like this as worth independent verification
   before adopting the package, rather than reporting the star count at
   face value as a credibility signal; this skill's tools don't run the
   deeper source-attestation check that would confirm or rule this out
   (`check_package_provenance`, via `package-trust-check`).
8. Treat `downloadTrend.changePercent` with caution when
   `weeklyDownloads` is low (roughly under a few thousand) — a small
   absolute change produces a large, noisy percentage (e.g. a candidate
   with 850 weekly downloads showing `"growing"` at 800%+ off a tiny prior
   base is statistical noise, not real momentum). Lead with the absolute
   download figure, not the percentage, for any low-volume candidate.
9. Note that `installSize.transitive.transitiveUnpackedSize` (a rollup
   across the candidate's resolved dependency tree, up to depth 2 / 60
   nodes) is often the more useful figure than the candidate's own
   `unpackedSize` — a small package can still drag in a large tree.

## Output requirements

- Always include each candidate's `npmscanUrl` so the user can read the full
  write-up.
- Present the comparison as a table (or clearly separated per-candidate
  summary) with at minimum: downloads/trend, popularity/maintenance tier,
  deprecated status, latest-version vulnerability status, TypeScript
  support, GitHub stars, and install-script risk tier — then the pick and
  rationale below it, not interleaved.
- When `suggest_alternative` was used to generate the peer set (routing
  case 2), say so explicitly ("compared against N real peers in the same
  category, not just this one package in isolation") so the user
  understands the comparison isn't limited to what they originally named.
- State plainly that this is a point-in-time snapshot (downloads,
  vulnerabilities, and maintenance activity all change) — not a permanent
  verdict, especially if the user's decision is time-sensitive.

## Do not

- Do not invent candidate package names yourself when the user gave 0 or 1
  — `search_packages`/`suggest_alternative` exist specifically so the
  shortlist is real, current registry data instead of names recalled from
  training knowledge that may be renamed, abandoned, or gone since.
- Do not call `compare_packages` with fewer than 2 or more than 5 packages
  — dedupe first (case-insensitive; the tool itself rejects exact
  duplicates with a 400), and route through cases 2-4 above instead of
  forcing a single name through it.
- Do not treat a deprecated or typosquat-flagged candidate as excluded from
  the comparison — `compare_packages` still returns them (with `deprecated`/
  `possibleTyposquatOf` set) so the user can see exactly why they lost; only
  `recommendation.pick` is guaranteed to skip them, not the `candidates`
  list itself.
- Do not silently narrow a >5-candidate list without telling the user which
  names were dropped and why.
- Do not use this skill to re-investigate a package the user already has
  installed and is worried about — that's `package-trust-check` or
  `dependency-audit`; this skill's tools are tuned for choosing among
  healthy-looking options, not for compromise/takeover forensics.
- Do not present `recommendation.pick` as a security clearance — it's a
  weighted popularity/maintenance/vulnerability/typosquat/install-script
  score, not a guarantee the package is free of issues the lighter scan
  can't see.
- Do not pass every `suggest_alternative`/`search_packages` result straight
  into `compare_packages` on the strength of `categoryOverlap` alone — a
  single generic shared token plus high popularity can rank an unrelated
  package first (verified: `win-guid`, a Windows GUID *parser*, outranked
  a real UUID library when comparing alternatives to `uuid`). Read each
  candidate's `description` and drop ones that aren't actually the same
  tool for the job before comparing them.
- Do not report a candidate's `githubStars` as a plain credibility signal
  without checking it against `weeklyDownloads` first — a low-download
  package with implausibly high stars likely has a `repository` field
  pointing at a different project's repo, not evidence of its own
  popularity.
- Do not attempt to install, upgrade, or publish packages yourself; this
  skill only reads data through NPMScan's read-only MCP tools.

## Tools used

`compare_packages`, `suggest_alternative`, `search_packages`, `get_package`
(for the source description in routing case 2), and optionally
`check_license_compliance` — all provided by the `npmscan` MCP server bundled with this plugin
(`.mcp.json`). See [references/test-prompts.md](references/test-prompts.md)
for prompts to manually verify this skill after installing or editing it.
```

## File: skills/new-dependency-evaluation/references/test-prompts.md
size: 10495
sha: 9727fc2f435222512c9fbffd4b71560533d61a4b
upstream: https://github.com/salemalem/npmscan-mcp-plugin/blob/main/skills/new-dependency-evaluation/references/test-prompts.md

```text
# Test prompts for `new-dependency-evaluation`

Run these manually with the plugin loaded (`claude --plugin-dir ./npmscan-mcp-plugin`)
before publishing a change to this skill. Each case names the exact tool calls and fields the response
should surface — not just "did a tool get called," but "did the routing
logic pick the right case and did the real data survive into the answer."

1. **Three named candidates for the same job (routing case 1)** — ask:
   > "Should we use axios, got, or node-fetch for our new HTTP client?"
   Expect `compare_packages({ packages: ["axios", "got", "node-fetch"] })`
   directly, no `suggest_alternative`/`search_packages` call first. All
   three resolve (`found: true`), and `recommendation.pick` is non-null.
   Expect the response to lead with `differentiators` (downloads, GitHub
   stars, TS support) before stating the pick and its `rationale`/
   `confidence`.

2. **A deprecated candidate is named alongside healthy ones** — ask:
   > "We're picking between axios, request, and got — which one?"
   `compare_packages({ packages: ["axios", "request", "got"] })` returns
   `request` with `deprecated` set (it really is deprecated) and
   `differentiators.deprecated` including `"request"`.
   `recommendation.pick` must NOT be `"request"`. Expect the response to
   still list `request` in the comparison table (not silently drop it) while
   clearly stating why it lost.

3. **A typosquat stub named alongside its real target** — ask:
   > "Comparing lodash, lodahs, and underscore for utility functions — any
   > concerns?"
   `compare_packages({ packages: ["lodash", "lodahs", "underscore"] })`
   returns `lodahs` with `possibleTyposquatOf: { name: "lodash", ... }`.
   `recommendation.pick` must NOT be `"lodahs"`. Expect the response to
   flag the typosquat explicitly and prominently — not bury it in the
   table — as a real supply-chain risk to verify, matching the tool's own
   hedged framing.

4. **Exactly one named candidate (routing case 2)** — ask:
   > "Should we add uuid to the project, or is there something better?"
   Expect `suggest_alternative({ name: "uuid", reason: "general", limit: 4
   })` first — its `suggestions` exclude `"uuid"` itself and exclude any
   deprecated package — then `compare_packages` with `uuid` plus up to 4 of
   those suggestion names (5 total, at the tool's max). Expect the response
   to say explicitly that `uuid` was compared against real peers in its
   category, not evaluated alone, before giving the pick.

4b. **Screening catches a real false-positive match (routing case 2)** —
    same prompt as case 4. `suggest_alternative({ name: "uuid", reason:
    "general", limit: 4 })` returns `win-guid` in its `suggestions` with
    `description: "Windows legacy GUID parser"` and `categoryOverlap:
    ["guid"]` (a single generic token) — a real result, confirmed live: it
    is not a UUID-generation library, it's an unrelated Windows binary-
    format parser that happens to have very high weekly downloads. Expect
    the response to exclude `win-guid` from the `compare_packages` call (or
    if it slipped through, to flag it explicitly as not actually
    comparable rather than silently including it in the pick) — not treat
    its high popularity as making it a legitimate contender for a UUID
    library comparison.

5. **One named candidate whose only real alternative is a language
   built-in (routing case 2, no-compare fallback)** — ask:
   > "Do we need the left-pad package, or is there a better option?"
   `suggest_alternative({ name: "left-pad", reason: "general", limit: 4 })`
   returns `nonPackageAlternatives` including
   `"String.prototype.padStart()"` and an empty (or near-empty)
   `suggestions` array. Expect the response to skip `compare_packages`
   (nothing left to compare against) and instead recommend the built-in
   directly, naming it exactly — not a vague "there might be a native way
   to do this."

6. **A described need with zero named candidates (routing case 3)** — ask:
   > "What should we use to format dates? We don't have a preference yet."
   Expect `search_packages({ query: "date formatting" })` (or an equivalent
   close paraphrase of the described need) first, a 2-5 name shortlist built
   from results that excludes anything with `possibleTyposquatOf` set or
   very-low popularity, then `compare_packages` on that shortlist. Expect
   the response to name which candidates were shortlisted and why before
   giving the comparison.

7. **Two comparably healthy candidates — a real "too close to call"
   result** — ask:
   > "dayjs or date-fns for our new date-handling code?"
   `compare_packages({ packages: ["dayjs", "date-fns"] })` returns both
   `found: true`, a non-null `recommendation.pick` among the two, and a
   valid `confidence`. Expect the response to report `confidence` plainly —
   if it comes back `"low"` or `"medium"`, say the two are close rather than
   overstating the pick as a clear winner.

8. **A legitimate low-traffic package should not be misflagged** — ask:
   > "Comparing is-number against lodash for a small numeric check — worth
   > adding a whole extra dependency?"
   `compare_packages({ packages: ["is-number", "lodash"] })` returns
   `is-number` with `found: true`, `deprecated: null`, and
   `possibleTyposquatOf: null` despite its much lower download count.
   Expect the response to not treat low popularity alone as a red flag —
   only `deprecated`/`possibleTyposquatOf`/vulnerability findings are.

9. **Non-triggering — a plain factual question, no decision being made** —
   ask:
   > "What does the axios package do?"
   Expect this skill NOT to activate — at most one `get_package` call, no
   `compare_packages`/`suggest_alternative`/`search_packages` calls.

10. **Non-triggering — investigating something already installed** — ask:
    > "We already have `event-stream` in our lockfile — is it safe?"
    Expect `package-trust-check` to activate instead of this skill (a
    single already-installed package's trust question), not
    `compare_packages`/`suggest_alternative`.

11. **More than 5 named candidates (routing case 4)** — ask:
    > "We're choosing an HTTP client — considering axios, got, node-fetch,
    > undici, superagent, and ky. Thoughts?"
    `compare_packages` accepts at most 5 names. Expect the response to
    either run it on the first 5 and explicitly name `ky` (or whichever was
    dropped) as excluded, or ask the user to narrow the list to 5 — not a
    silent truncation with no mention of what was left out.

12. **License-constrained comparison excludes a copyleft candidate even if
    competitive (step 6)** — ask:
    > "Legal says nothing GPL — is graphviz an option compared to lodash
    > for our use case?"
    Expect `check_license_compliance({ packages: [{ name: "graphviz" },
    { name: "lodash" }] })` to run alongside/after the comparison,
    returning `graphviz` as `rawLicense: "GPL-3.0"`, `category:
    "copyleft"`, `isCompliant: false`, and `lodash` compliant. Expect the
    response to explicitly rule graphviz out on license grounds, not just
    report scores side by side and leave the license violation buried in a
    table cell.

13. **A non-SPDX license stays "needs review," not silently pass or fail
    (step 6)** — ask:
    > "Is nocodb's license going to be a problem if we only allow
    > MIT-licensed dependencies?"
    Expect `check_license_compliance({ packages: [{ name: "nocodb" }],
    policy: { allow: ["MIT"] } })` to return `rawLicense: "Sustainable Use
    License"`, `category: "unknown"`, `needsReview: true`, `isCompliant:
    false`. Expect the response to distinguish "flagged because it's
    unproven under your allow-list" from "flagged because it's confirmed
    copyleft" — nocodb's category is genuinely `unknown`, not a known-bad
    one.

14. **`compare_packages` rejects a duplicate name outright** — ask:
    > "Compare axios against Axios for us"
    (the same package, different casing — simulating a user accidentally
    naming the same package twice). Expect `compare_packages` to either be
    called with the names deduped first, or, if called with the duplicate,
    to come back as `tool_error` with a detail mentioning "duplicate" — not
    a comparison of a package against itself presented as a real result.

15. **A genuinely nonexistent candidate name (distinct from the typosquat-
    stub case in scenario 3)** — ask:
    > "Compare axios, got, and reqeust-lib-xyz for our HTTP client"
    (a name that's simply unpublished, not a typo of a real popular
    package). Expect `compare_packages` to still return all 3 entries, the
    unresolved one flagged `found: false` with a `resolutionError`, and
    `recommendation.pick` landing on `axios` or `got`. Expect the response
    to describe this candidate as "not found / doesn't appear to be a real
    package," not conflate it with scenario 3's "impersonating a real
    package" typosquat framing.

16. **A malformed/garbled candidate name doesn't abort the whole call** —
    ask:
    > "Compare axios against `%` for our HTTP client"
    (simulating a corrupted copy-paste). Expect
    `compare_packages({ packages: ["axios", "%"] })` to return a normal
    response where `axios` resolves fully and the `%` candidate comes back
    `found: false` with a `resolutionError` mentioning percent-encoding —
    not a total failure of the tool call.

17. **A `prepare`-only candidate is not an install-script risk** — ask:
    > "nanoid vs uuid vs @paralleldrive/cuid2 for generating ids?"
    uuid (`prepare: lefthook install`) and cuid2 (`prepare: husky`) used to
    get an install-script penalty that decided the pick. Verified live: all
    three now report `installScriptRisk.riskTier: "none"`. Expect the
    comparison not to cite install scripts against uuid or cuid2.

18. **A question in Russian** — ask:
    > "Какую библиотеку выбрать для генерации уникальных ID в Node.js?"
    Expect the skill to trigger, `search_packages` to be called with English
    terms (e.g. "unique id"), the answer to come back in Russian, and no
    package names that the tools didn't return.

To confirm the skill loaded and is namespaced correctly, run `/help` and
check the **Custom commands** tab for `/npmscan:new-dependency-evaluation`, or just
invoke it directly with that name.
```

## File: README.md
upstream: https://github.com/salemalem/npmscan-mcp-plugin/blob/main/README.md

```text
# NPMScan plugin for Claude Code

A [Claude Code plugin](https://code.claude.com/docs/en/plugins) that gives
Claude read-only npm package and vulnerability lookups, backed by
[npmscan.com](https://npmscan.com)'s free, unauthenticated MCP server.

NPMScan is also officially listed for both ChatGPT and Claude on
[npmscan.com's MCP page](https://npmscan.com/mcp), so users can verify that
this plugin points at the same public service.

## Official listings

- **Claude** (Anthropic's official Claude directory):
  [claude.ai/customize/skills/…/npmscan](https://claude.ai/customize/skills/id/aa8fcafe-0bcd-41b6-9e07-6c7ebbb71153%40anthropic-plugin-directory)
- **ChatGPT** (install npmscan as an app for one-click setup):
  [chatgpt.com/plugins/…/npmscan](https://chatgpt.com/plugins/plugin_asdk_app_6a6a699e6f3481918d5e6034432894f2?q=npm)
- **mcpHQ**: [mcphq.org](https://github.com/mcpHQ/awesome-mcp-servers)
- **TensorBlock MCP Index**: [tensorblock.co/mcp](https://github.com/TensorBlock/awesome-mcp-servers)
- **Awesome Gemini CLI**: [Piebald-AI/awesome-gemini-cli](https://github.com/Piebald-AI/awesome-gemini-cli)
- **Awesome Gemini CLI Extensions**: [Piebald-AI/awesome-gemini-cli-extensions](https://github.com/Piebald-AI/awesome-gemini-cli-extensions)
- **Awesome Codex Plugins**: [hashgraph-online/awesome-codex-plugins](https://github.com/hashgraph-online/awesome-codex-plugins)
- **Setup docs for other clients**: [npmscan.com/mcp](https://npmscan.com/mcp)

Pending:

- **Grok Build** (xAI's official plugin marketplace): submitted in
  [xai-org/plugin-marketplace#1506](https://github.com/xai-org/plugin-marketplace/pull/1506),
  awaiting xAI review. See [Using it with Grok Build](#using-it-with-grok-build).
- **GitHub Copilot** (Awesome Copilot marketplace): submitted in
  [github/awesome-copilot#5142](https://github.com/github/awesome-copilot/issues/5142).
- **Gemini CLI** (extension gallery): indexed automatically from this repo. See
  [Using it with Gemini CLI](#using-it-with-gemini-cli).

## What it adds

- **MCP server** (`npmscan`, `https://npmscan.com/api/mcp`) with
  twenty-three tools — see [Tools](#tools) below. Every tool result includes
  an `npmscanUrl` linking back to the full write-up on npmscan.com. No API
  key or auth required — same public data as the website. The server is
  stateless and rate-limited to 30 requests/minute per IP. Every
  vulnerability finding carries `isMalware`, so a confirmed-malicious
  package is reported as malware to remove, never as an ordinary bug.

- **`/npmscan:dependency-audit` skill** — chains `batch_query_vulnerabilities`
  → `get_package`/`get_package_version` → `analyze_transitive_dependencies`
  → the maintainer/provenance/license/remediation/alternative tools into one
  dependency-audit report, instead of leaving that tool sequencing to Claude
  each time. Also handles PR-style before/after diffs via
  `diff_dependencies`, raw `npm audit --json` output via `enrich_npm_audit`,
  and a bare GitHub repo URL via `audit_github_repository`. Triggers
  automatically when you paste a `package.json`/lockfile/SBOM, give a GitHub
  repo URL, or ask to check/audit your dependencies, or invoke it directly.
  See [`skills/dependency-audit/SKILL.md`](skills/dependency-audit/SKILL.md).

- **`/npmscan:package-trust-check` skill** — a deep, single-package
  investigation for "is X safe / was X compromised" questions: a malware
  check (including versions npm has since removed for being malicious),
  maintainer add/remove history (account-takeover patterns),
  publish-provenance cross-checks, and install-script scanning, in one
  report. Triggers
  automatically on a trust question about one named package; not for
  auditing a whole dependency list (that's `dependency-audit`). See
  [`skills/package-trust-check/SKILL.md`](skills/package-trust-check/SKILL.md).

- **`/npmscan:new-dependency-evaluation` skill** — for a forward-looking
  choice about what to *add*, not what's already installed: comparing 2-5
  named candidates ("axios vs got vs node-fetch"), evaluating one candidate
  against its real peers, or shortlisting candidates from a described need.
  Orchestrates `compare_packages`/`suggest_alternative`/`search_packages`
  into a structured side-by-side with a deterministic pick. See
  [`skills/new-dependency-evaluation/SKILL.md`](skills/new-dependency-evaluation/SKILL.md).

- **`/npmscan:incident-response` skill** — turns an existing finding, or
  just a vague symptom description ("npm install did something weird"),
  into concrete `get_remediation_playbook` steps: real incident references,
  severity, and prevention tips, not improvised advice. See
  [`skills/incident-response/SKILL.md`](skills/incident-response/SKILL.md).

- **`/npmscan:ci-pr-gate` skill** — turns a dependency change (a
  before/after snapshot, or one or more named "bump X from A to B"
  upgrades) into one deterministic PASS/WARN/FAIL verdict formatted for a
  CI check or PR-comment bot, applying a fixed policy on top of
  `diff_dependencies`/`simulate_dependency_upgrade` — not a conversational
  report (that's `dependency-audit`'s job). See
  [`skills/ci-pr-gate/SKILL.md`](skills/ci-pr-gate/SKILL.md).

## Tools

NPMScan provides twenty-three tools:

| Tool | Description |
|---|---|
| `search_packages` | Search the npm registry by name or keywords. Each result includes weekly/monthly download counts, dependent-package counts, and rank among npmscan's own top-100k-by-downloads snapshot, so you can tell an established package from an abandoned or squatted one that merely matches the query text — flags `possibleTyposquatOf` when a low-popularity result's name is one typo away from a top-5,000 package. |
| `get_package` | Latest version, install scripts (`preinstall`/`install`/`postinstall`/`prepare`), maintainers, license, recent version history, weekly downloads, GitHub stars, TypeScript support, days since last publish, ecosystem-wide download rank, a 3-month download trend, and an `isLatestVersionVulnerable`/`highestSeverity` verdict (with fixed versions and `isMalware` per finding) — plus a rule-based (not model-generated) maintenance/popularity summary and typosquat flag computed from those numbers. |
| `get_package_version` | Metadata for one exact version plus an OSV.dev check scoped to that version — `isVulnerable`/`highestSeverity` as a direct safe/not-safe answer, with each finding's severity, summary, fixed version and `isMalware`. For checking a version pinned in a lockfile. A version npm has removed but OSV still has advisories for (usually one pulled for being malicious) comes back with `versionExists: false` and those findings, not a bare "not found". |
| `get_maintainer_profile` | Packages an npm username currently maintains via npm's own `maintainer:<username>` search index, plus precomputed download/dependent totals across all of them. Lists the most-downloaded `limit` packages (default 50, max 250); totals still cover every package found. A plain info lookup, not a security check — pair it with `check_maintainer_blast_radius` for the actual compromised-account signal. |
| `query_vulnerabilities` | OSV.dev lookup for known vulnerabilities affecting a package, optionally scoped to a version, with `isVulnerable`/`highestSeverity` as a direct verdict and each finding's severity, summary, fixed version and `isMalware`. For npm it also cross-checks the name/version against the registry: a name or version that isn't on npm still returns its OSV data, with `packageExists: false` and an `existenceCheckNote` — so a typo doesn't read as "clean", and a version removed for malware still shows its findings. Accepts `packageName` as an alias for `name`, and other ecosystems via `ecosystem` (e.g. `"PyPI"`). |
| `batch_query_vulnerabilities` | OSV.dev lookup across a whole dependency inventory at once — pass a flat `packages` list, or paste raw `package.json`/lockfile/CycloneDX JSON/SPDX JSON content via `content` and it parses that for you. Chunks larg
```
