We resolved the installed dependency trees of 11 published MCP server packages and matched the exact installed versions against OSV. The corrected pass produced 130 advisory matches. Querying OSV for only the 11 top-level package coordinates produced 2.
That is not a comparison between scanners. It is a measurement of two different questions. The first asks what a published package installs on a machine. The second asks whether an advisory names the package itself. For this set, those questions returned substantially different answers.
Method
We installed pinned releases of ten npm packages and one PyPI package in isolated working directories on August 3, 2026. On August 24, after fixing the matching and alias handling described below, we re-queried OSV using the preserved installed trees.
| Published package | Installed release | Ecosystem |
|---|---|---|
@cloudflare/mcp-server-cloudflare | 0.2.0 | npm |
@stripe/agent-toolkit | 0.9.0 | npm |
firecrawl-mcp | 3.15.0 | npm |
mongodb-mcp-server | 1.10.0 | npm |
mcp-server-kubernetes | 3.5.1 | npm |
exa-mcp-server | 3.2.1 | npm |
perplexity-mcp | 0.2.3 | npm |
puppeteer-mcp-server | 0.7.2 | npm |
@browserbasehq/mcp | 3.0.0 | npm |
@elastic/mcp-server-elasticsearch | 0.3.1 | npm |
mcp-server-qdrant | 0.8.1 | PyPI |
Each package was measured through two paths.
For the installed-tree path, we enumerated every installed package and exact version. For npm, that meant walking the recursive output of npm ls --json --all from the isolated installation. For PyPI, it meant reading the installed environment with pip inspect. We removed Python packaging tools (pip, setuptools, and wheel) because they belong to the test environment rather than the MCP server's runtime tree. We then queried OSV by ecosystem, package name, and exact installed version.
For the top-level path, we constructed a Package URL, or PURL, for only the published MCP package and its installed version, such as pkg:npm/mcp-server-kubernetes@3.5.1. That produced one OSV query per target and did not recurse into dependencies.
The top-level release was pinned, but its transitive dependency ranges were resolved when the isolated environment was created. A fresh install later can therefore produce a different tree when a package publishes a new compatible release. OSV also changes as advisories are added, amended, or assigned aliases. These results are a dated observation, not a permanent property of the packages.
We counted a match once per installed package version in each target. When OSV returned separate records whose identifiers overlapped through aliases, such as a GHSA and PYSEC identifier for the same FastMCP advisory, we collapsed them before counting. The same underlying advisory can still appear once in several MCP server trees because each target independently installs the affected dependency.
The work ran on Windows. One platform caveat matters for reproduction: for stdio servers launched through npx, uvx, or another shim, a process fingerprint identifies the launcher, not necessarily the package build behind it. The dependency evidence here comes from the isolated installed environment, not from treating the launcher executable as the package artifact.
Evidence
The dated records used for the tables are available as a tree summary, direct-package controls, and a full JSON bundle. The bundle contains one normalized installed-tree report per target plus both summaries.
One OSV record we did not count
The first pass appeared to say that @browserbasehq/mcp@3.0.0, the current release at the time of installation, contained malware. We nearly turned that result into a public maintainer report. It was wrong.
OSV record MAL-2025-191195 names @browserbasehq/mcp and contains a SEMVER range with introduced: 0, but no fixed or last_affected boundary. Taken alone, that range matches every version. The same affected entry also has an explicit versions array containing only 2.1.1.
We excluded the malformed match and changed the matcher. When an OSV malware entry combines an exact version list with an otherwise unbounded range, the exact list now constrains the result. A regression test verifies both sides: 3.0.0 is excluded and 2.1.1 remains matched.
This correction changes the Browserbase installed-tree result from 14 raw matches to 13 and its top-level result from 1 to 0. It is also why the provisional 136 / 3 split from the first pass is not the number published here.
Results
The installed-tree path found matches in every target. The top-level path found two, both on mcp-server-kubernetes@3.5.1.
| Target | Installed packages checked | Installed-tree matches | Top-level matches |
|---|---|---|---|
| Cloudflare | 25 | 13 | 0 |
| Stripe Agent Toolkit | 178 | 25 | 0 |
| Firecrawl | 174 | 31 | 0 |
| MongoDB | 396 | 2 | 0 |
| Kubernetes | 318 | 28 | 2 |
| Exa Search | 132 | 3 | 0 |
| Perplexity | 44 | 1 | 0 |
| Puppeteer (community) | 147 | 2 | 0 |
| Browserbase | 231 | 13 | 0 |
| Elasticsearch | 302 | 4 | 0 |
| Qdrant | 76 | 8 | 0 |
| Total | 2,023 | 130 | 2 |
The normalized installed-tree set contained 2 critical, 54 high, 63 medium, and 11 low matches. The two direct Kubernetes advisories were fixed in mcp-server-kubernetes 3.6.0 and 3.7.0, respectively.
Four follow-up scans led to public maintainer issues:
- Exa #407 traces two
shelljs@0.3.0advisories throughwhoami@0.0.3; both are fixed inshelljs@0.8.5. It also records a Windows-specific@hono/node-server@1.19.17path traversal fixed in2.0.5. - Qdrant #173 records 14 raw OSV records on the exact
fastmcp==2.7.0pin. Alias normalization reduces those to eight underlying advisories. Their fix boundaries are2.13.0,2.14.0,2.14.2, and3.2.0;3.2.0clears the measured set. - Cloudflare #442 records 12 advisories on
undici@5.29.0. The fixes differ by advisory: four are cleared by6.24.0, eight by6.27.0, eleven by6.28.0, and all twelve by7.18.2. A separate SDK match in the same tree is fixed in@modelcontextprotocol/sdk@1.24.0. - Kubernetes #359 traces five Express-path advisories in a follow-up release and records
express4.22.2as the version that clears that set.
These issues show why the installed tree is useful evidence: it identifies the exact dependency path and makes a proposed fix testable. The result describes published artifacts, not maintainer intent.
The category gap
The established tools in this area answer useful questions about artifacts supplied by their users. Dependabot builds a dependency graph from a repository's default branch and alerts when that graph or the advisory database changes. Snyk Open Source builds direct and transitive dependency trees from project manifests. OSV-Scanner scans source manifests and lockfiles and can also inspect artifacts such as node_modules in a container image. They work as designed.
| Approach | What it commonly scans | Whose artifact | In the agent call path? |
|---|---|---|---|
| Dependabot | Enabled repository and dependency graph | Maintainer's | No |
| Snyk Open Source | Project manifests and resolved dependency graph | Operator's | No |
| OSV-Scanner | Source manifests, lockfiles, or a supplied image | Operator's | No |
| MCP tool-description scanners | Tool schemas and descriptions | Server's | Partially |
| This measurement | Freshly resolved install tree of a published package | Someone else's, running locally | Before execution |
The gap is on the consumer side. A repository scanner can tell a maintainer what their checked-in lockfile contains. It was not built to tell an agent operator what npx firecrawl-mcp resolves later on another machine, perhaps without that repository or its lockfile present.
Where Bindfort fits among MCP gateways
MCP gateways cover a different layer from repository scanners: they sit between clients and servers, or manage how servers are run. Their published scopes are not identical. The table below summarizes the primary documentation reviewed on August 24, 2026. “Not described” means only that the cited overview did not document an installed-tree advisory workflow; it is not a claim that the vendor cannot add one or has no adjacent control.
| Gateway | Documented center of gravity | Package-supply-chain path relevant to this measurement |
|---|---|---|
| Kong MCP Traffic Gateway | Remote MCP proxying, API-to-tool conversion, authentication, ACLs, traffic control, logs, and metrics | No installed-tree advisory workflow is described on the cited MCP gateway overview. |
| Microsoft MCP Gateway | Kubernetes routing, session affinity, authorization, server lifecycle management, telemetry, and observability | No installed-tree advisory workflow is described in the cited project overview. |
| IBM ContextForge | Registry and proxy federation, governance, discovery, protocol enforcement, health monitoring, and observability | No installed-tree advisory workflow is described on the cited product overview. |
| Docker MCP Gateway | Containerized server lifecycle, routing, credentials, access control, isolation, logging, and call tracing | Docker documents signed catalog images and SBOMs in its MCP Toolkit. Its gateway security model identifies dependency and malware scanning as a separate catalog publishing process. |
| Bindfort | Public local installed-tree scanning, plus guided evaluation of pre-call allow or deny policy and verifiable receipts | The public scanner resolves locally installed dependency trees and matches exact package versions against OSV. The policy gateway is available through guided technical evaluation, not as a generally available self-serve production gateway. |
Bindfort's current distinction is therefore narrow: it makes consumer-side installed package evidence a first-class input alongside a controlled policy and receipt path. It does not replace the routing, federation, container isolation, identity, or observability functions documented by the gateways above. See the current public scanner scope and availability boundaries.
Dependency matching also has a narrower interpretation than semantic tool-description scanning. An independent 2026 study of runtime MCP servers reported that fewer than half of sampled scanner alerts were true positives. Many semantic detections are difficult because a filesystem tool reading files or a database tool running queries may be intended behavior. An exact package fact is less ambiguous: shelljs@0.3.0 either appears in the resolved tree or it does not. That does not prove exploitability, but it makes the observed match falsifiable. See Rethinking MCP Security: A Large-Scale Study of Runtime MCP Servers and Security Scanner Reliability.
What this does not answer
An installed-tree scan tells us which package versions were present and which public advisories matched them at a particular time. It does not tell us whether vulnerable code was reachable, whether an exploit occurred, or what an agent did after starting the server.
The harder question begins there: which tools did the agent call, with what arguments, what data crossed the boundary, and can the resulting record be trusted? Dependency evidence is one layer. Runtime behavior and trustworthy call records are another.