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 packageInstalled releaseEcosystem
@cloudflare/mcp-server-cloudflare0.2.0npm
@stripe/agent-toolkit0.9.0npm
firecrawl-mcp3.15.0npm
mongodb-mcp-server1.10.0npm
mcp-server-kubernetes3.5.1npm
exa-mcp-server3.2.1npm
perplexity-mcp0.2.3npm
puppeteer-mcp-server0.7.2npm
@browserbasehq/mcp3.0.0npm
@elastic/mcp-server-elasticsearch0.3.1npm
mcp-server-qdrant0.8.1PyPI

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.

TargetInstalled packages checkedInstalled-tree matchesTop-level matches
Cloudflare25130
Stripe Agent Toolkit178250
Firecrawl174310
MongoDB39620
Kubernetes318282
Exa Search13230
Perplexity4410
Puppeteer (community)14720
Browserbase231130
Elasticsearch30240
Qdrant7680
Total2,0231302

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.0 advisories through whoami@0.0.3; both are fixed in shelljs@0.8.5. It also records a Windows-specific @hono/node-server@1.19.17 path traversal fixed in 2.0.5.
  • Qdrant #173 records 14 raw OSV records on the exact fastmcp==2.7.0 pin. Alias normalization reduces those to eight underlying advisories. Their fix boundaries are 2.13.0, 2.14.0, 2.14.2, and 3.2.0;3.2.0 clears the measured set.
  • Cloudflare #442 records 12 advisories on undici@5.29.0. The fixes differ by advisory: four are cleared by 6.24.0, eight by 6.27.0, eleven by 6.28.0, and all twelve by 7.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 express 4.22.2 as 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.

ApproachWhat it commonly scansWhose artifactIn the agent call path?
DependabotEnabled repository and dependency graphMaintainer'sNo
Snyk Open SourceProject manifests and resolved dependency graphOperator'sNo
OSV-ScannerSource manifests, lockfiles, or a supplied imageOperator'sNo
MCP tool-description scannersTool schemas and descriptionsServer'sPartially
This measurementFreshly resolved install tree of a published packageSomeone else's, running locallyBefore 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.

GatewayDocumented center of gravityPackage-supply-chain path relevant to this measurement
Kong MCP Traffic GatewayRemote MCP proxying, API-to-tool conversion, authentication, ACLs, traffic control, logs, and metricsNo installed-tree advisory workflow is described on the cited MCP gateway overview.
Microsoft MCP GatewayKubernetes routing, session affinity, authorization, server lifecycle management, telemetry, and observabilityNo installed-tree advisory workflow is described in the cited project overview.
IBM ContextForgeRegistry and proxy federation, governance, discovery, protocol enforcement, health monitoring, and observabilityNo installed-tree advisory workflow is described on the cited product overview.
Docker MCP GatewayContainerized server lifecycle, routing, credentials, access control, isolation, logging, and call tracingDocker 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.
BindfortPublic local installed-tree scanning, plus guided evaluation of pre-call allow or deny policy and verifiable receiptsThe 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.

Bindfort Research