Topic: mcp server dependency security

MCP server dependency security — what to check in package.json and requirements.txt before installing

Most MCP servers are thin wrappers over existing npm or PyPI packages — a fetch client, an API SDK, a parsing library. That package graph is the dependency surface, and it carries security risk that travels with the server when it's installed into your agent environment. In the 101-server corpus, 8% had at least one dependency with an open CVE at the time of audit — all of them fixable, none of them patched, because no one had looked. Here's what to look for before you connect a community MCP server to your agent.

TL;DR

The MCP server dependency surface is the same as any Node.js or Python process — npm packages and PyPI distributions with their transitive dependency trees. A CVE in a package that's not on the code path from any tool handler is lower risk than one that is. Floating semver ranges (^1.0.0) break agent-install reproducibility because the resolved tree can change between installs with no change to the manifest. Four quick manual checks: run npm audit or pip-audit after cloning, confirm a committed lockfile exists, look for floating ranges on direct dependencies used in tool handlers, and check the last-modified date on the manifest. SkillAudit's maintenance axis automates all four, plus adds reachability analysis to distinguish CVEs on the handler path from inert transitive ones.

What the dependency surface looks like

An MCP server's dependency surface has three layers:

Direct dependencies — the packages listed in package.json dependencies or requirements.txt. These are what the author explicitly chose. They typically include the MCP SDK (@modelcontextprotocol/sdk or mcp), one or more API client libraries, and utility packages like zod, axios, or httpx.

Transitive dependencies — the packages that the direct dependencies depend on, and so on recursively. For a typical MCP server with 10–15 direct deps, the transitive tree often reaches 150–300 packages. Most of these are never directly invoked by the server code; they exist because a library needed them internally.

Dev dependencies — build tools, type declarations, test runners. These are only installed in development environments and don't ship in production containers; their CVEs are typically lower priority unless the dev environment is used as a build step that produces artifacts included in the published package.

A CVE scanner sees all three layers. The question that matters for risk prioritization is which layer a vulnerable package sits in, and whether the vulnerable code is actually called by any code path that a tool handler can reach.

CVE vs. execution path — why they're not the same thing

A dependency audit report listing 12 findings doesn't mean 12 exploitable vulnerabilities. The relevant question for each finding is: can an attacker reach the vulnerable code path by sending a crafted argument to one of the MCP server's tool handlers?

Consider a hypothetical: your server depends on lodash (for utility functions in a helper module) and axios (for HTTP calls in the fetch tool handler). An advisory lands against a specific lodash prototype-pollution function that your server never calls — _.merge on untrusted objects. That finding has a CVSS of 7.5 and will appear in any SCA scan. But if your server calls _.cloneDeep exclusively on internal config objects, the exploitation path doesn't exist through the MCP interface.

Now consider an advisory against axios for a request-smuggling vulnerability in its redirect-following logic. If your fetch tool handler uses axios.get(url) where url comes from a tool argument, and the handler follows redirects, that vulnerability is directly reachable via a crafted URL argument — possibly delivered by an attacker who embedded an instruction in a page the agent fetched earlier.

Generic SCA tools don't make this distinction; they report both findings at the same priority level. SkillAudit's maintenance axis adds reachability analysis: it traces from tool handler argument sinks through the call graph to identify which CVEs sit on reachable code paths.

Floating semver ranges and why they break in agent contexts

When a package.json specifies "axios": "^1.6.0", it means "install any 1.x.x version >= 1.6.0." This is standard practice for human-development workflows where you run npm install once, commit the lockfile, and all subsequent installs use the locked version.

The problem in agent contexts is that MCP servers are often installed on-demand into ephemeral environments — a container that spins up to handle a session, a fresh VM provisioned by an agent platform. If the committed lockfile is absent or stale, npm will re-resolve the dependency tree at install time. What was axios@1.6.0 when the author last ran npm install might resolve to axios@1.7.4 today. The behavior change may be benign; it may be a regression; it may include a new CVE.

This isn't hypothetical. The nine archived servers in our corpus all had floating ranges and no committed lockfile. Every one of them resolves to a different dependency tree today than when the author last tested them. An agent that installs such a server into a fresh environment is running code the author never tested.

The fix is lockfile discipline: commit package-lock.json, yarn.lock, or pnpm-lock.yaml alongside the manifest and keep it current with automated PRs (Dependabot, Renovate). For Python, commit poetry.lock or pin exact versions in requirements.txt.

Four quick checks before installing a community MCP server

These checks take under five minutes and surface the majority of dependency-security risk:

Check 1 — Run npm audit or pip-audit

# Node.js server
git clone https://github.com/author/some-mcp-server
cd some-mcp-server
npm install
npm audit --audit-level=moderate

# Python server
pip install pip-audit
pip-audit -r requirements.txt

Any HIGH or CRITICAL finding in a direct dependency warrants investigation. Cross-reference the finding with the tool handler code: does the vulnerable package get called from a handler that accepts external input?

Check 2 — Confirm a lockfile exists and is committed

ls package-lock.json yarn.lock pnpm-lock.yaml  # for Node.js
ls poetry.lock                                   # for Python

No lockfile means no reproducible installs. If the server has no lockfile, the dependency tree you install may differ from what the author tested.

Check 3 — Check for floating ranges on key direct dependencies

# Look for ^ or ~ ranges in package.json dependencies (not devDependencies)
cat package.json | python3 -c "import sys,json; d=json.load(sys.stdin)['dependencies']; [print(k,v) for k,v in d.items() if v.startswith(('^','~','>=','*'))]"

Floating ranges on direct dependencies that handle network I/O or argument parsing are the highest-risk entries. An author who pins exact versions in dependencies and uses floating ranges only in devDependencies is following good practice.

Check 4 — Check last-modified date on key dependency files

Visit the repository and look at the commit history for package.json and the lockfile. A manifest last touched 18+ months ago with floating ranges means the installed tree may have diverged substantially from the last tested state. Compare the current advisory landscape against the packages listed — a quick search on https://osv.dev/ for each direct dependency name will surface any advisories published since the last manifest update.

How SkillAudit's maintenance axis grades dependencies

The maintenance axis is one of six axes in the SkillAudit score. It evaluates: lockfile committed and current, semver pinning discipline on direct dependencies, open advisories in the dependency tree (with reachability weighting), presence of a CI SCA gate, and evidence of active maintenance (release recency, response to dependency Dependabot PRs). A server that scores well on security and permissions but poorly on maintenance is graded down on the overall score — because a clean server today can become a vulnerable server in six months if it stops tracking its dependencies.

The 8% CVE-at-audit rate in our corpus understates the risk for servers that aren't in the top 101 by install count. Less-visible community servers with no Dependabot configuration and no recent releases are exposed to every advisory that lands against their dependency tree with no remediation signal whatsoever.

Run a dependency audit on any MCP server repo

Related