Skip to content

fix(mcp): only derive a package from executor subcommands#34

Open
alexviktorov wants to merge 1 commit into
perplexityai:mainfrom
alexviktorov:fix/mcp-run-script-detection
Open

fix(mcp): only derive a package from executor subcommands#34
alexviktorov wants to merge 1 commit into
perplexityai:mainfrom
alexviktorov:fix/mcp-run-script-detection

Conversation

@alexviktorov
Copy link
Copy Markdown

What

For npm/pnpm/yarn/bun, derive a package identity only from an executor
subcommand — dlx/exec/x (or the separate npx/bunx). Any other first token
runs a local package.json script or an initializer, not a configured package, so it
yields no spec and the record falls back to the server id at confidence=low — the
same way uv run <script> is already handled.

Why (the bug)

MCP servers launched through a package-manager script runner had their script
name
recorded as the package. The claude-plugins-official messaging plugins launch
their bundled server like this:

{ "command": "bun",
  "args": ["run", "--cwd", "${CLAUDE_PLUGIN_ROOT}", "--shell=bun", "--silent", "start"] }

A baseline scan of a machine with those plugins installed emitted:

server_name package_name (before) package_name (after)
discord start discord
imessage start imessage
telegram start telegram
fakechat start fakechat

start is a real npm package, so an exposure catalog entry for start would
spuriously match these servers. Bare yarn dev / pnpm build leaked dev / build
the same way.

This is the npm-family counterpart of a case the parser already guards for uv:
uv run <script> (without --from) returns no package identity because its argument
is a script or path, not a published package. The npm family was inconsistently
grouped with the package executors, so its run/bare-script argument leaked through.
This change applies the same rule.

Scope / safety

  • Package executors are unchanged: npx/bunx/dlx/exec/x, --package, the
    -- terminator, and credential-bearing value flags all keep their package identity.
  • Diffing a full-host baseline scan before/after this change, only mcp records
    differ
    go/npm/pypi/rubygems/editor/browser records are byte-identical.
    The changed records are exactly the script-launched servers.
  • MCP records are already confidence: low configured references; the server-id
    fallback replaces a misleading (and false-positive-prone) package guess with the
    configured server alias rather than inventing a package name.

Tests / docs

  • New rows in TestInferPackageFromArgs covering run <script>, bare scripts,
    create/init, and a documented flag-ordering limitation.
  • TestScanConfig_RealWorldCorpus proves the contrast end-to-end: real launchers
    (npx/uvx/docker) keep their identity while script/initializer launchers fall
    back to the server id.
  • Updated the inferPackageFromArgs doc comment and docs/inventory-sources.md.
  • go build, go test ./..., go test -race ./..., go vet ./..., gofmt -l .
    (clean), and bumblebee selftest all pass.

MCP servers launched through a package-manager script runner had their
script name recorded as the package. The bundled Claude plugins use
`bun run … start`, which was emitted as package_name "start" — a real
npm package, so an advisory for "start" could spuriously match. Bare
`yarn dev` / `pnpm build` leaked "dev" / "build" the same way.

This is the npm-family counterpart of a case the parser already guards
for uv: `uv run <script>` (without --from) returns no package identity
because its argument is a script or path, not a published package. The
npm family was inconsistently grouped with the package executors, so its
`run`/bare-script argument leaked through.

Apply the same rule. For npm/pnpm/yarn/bun, only an executor subcommand
names a published package — dlx/exec/x (or the separate npx/bunx); the
positional after it, or --package, is the spec. Any other first token
runs a local script or initializer, not a configured package (`run
<script>`, the npm lifecycle aliases start/stop/restart/test, bare
`yarn dev`, and the create/init verbs), so return no spec and fall back
to the server id at low confidence — exactly as the uv branch already does.

Package executors are unchanged: npx/bunx/dlx/exec/x, --package, the --
terminator, and credential-bearing value flags all keep their identity.
A full-host scan diff confirms only mcp records change; no other
ecosystem is affected.

Updates the inferPackageFromArgs doc comment and docs/inventory-sources.md,
and adds unit + real-world-corpus tests.
@alexviktorov alexviktorov force-pushed the fix/mcp-run-script-detection branch from a600a96 to b05327c Compare May 27, 2026 19:18
@alexviktorov
Copy link
Copy Markdown
Author

alexviktorov commented May 29, 2026

@adel-pplx Let me know if you need any context on this. Basically after scanning my Mac I saw several records listing "start" as the package, where it's evidently a command. Specifically it was for some Claude official plugins. This PR fixes the discrepancy and instead you get the actual names, similar to other places in code. Tested on my local config and generated synthetic unit tests from real world examples. Thank you!

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