An MCP server is just another process that installs packages. If one of those packages is malicious, the attacker inherits every secret, tool, and network path the server has. Supply-chain attacks against npm and PyPI are not theoretical anymore, and MCP makes the blast radius larger because the model can call whatever the compromised server exposes.
This article walks through how those attacks land on an MCP host, then the controls that actually reduce risk.
Why MCP servers are attractive targets
MCP servers often hold more than a typical microservice:
- Secrets: API keys, database credentials, OAuth client secrets, and cloud identity tokens.
- Privileged tools: file reads, SQL, shell-ish helpers, ticket systems, email.
- Trust from the agent: tool results are fed straight into the model context.
A malicious postinstall script, a trojaned transitive dependency, or a typosquat of a popular MCP helper can run with the same privileges as your server process. From there it can exfiltrate tokens, wrap legitimate tools, or open a reverse channel while looking "healthy" to the agent.
How the attack usually unfolds
- Publish or hijack. The attacker publishes a package with a confusing name, compromises a maintainer account, or slips malware into a popular dependency via a PR or stolen token.
- Land in your install.
npm install/pip installpulls it in—often as a transitive dependency you never named yourself. - Execute early. Lifecycle scripts (
preinstall,postinstall) or import-time code run before your ownmainstarts. - Abuse trust. The malware reads env vars, steals tokens from disk, phones home, or patches tool handlers so every agent call is logged or altered.
Because agents retry tools and dump results into prompts, even subtle tampering (injecting extra rows, rewriting URLs) can steer the model without crashing anything.
Defenses that work in practice
Pin and hash everything. Commit lockfiles (package-lock.json, pnpm-lock.yaml, poetry.lock, uv.lock). Prefer installs that verify hashes (npm ci, pip install --require-hashes, Poetry/uv with locked hashes) so a silent upstream replace fails the build.
Audit before you ship. Run npm audit / pip-audit / OSV in CI, and fail on high severity for anything that ends up in the MCP image. Treat "dev only" deps carefully—build tooling still runs on your CI runners.
Prefer provenance and signed releases. Where available, require packages with attestations (npm provenance, Sigstore) and pin to a known publisher. Maintain a short allowlist of packages your MCP servers may depend on; new names need a review.
Disable install scripts when you can. For Node, --ignore-scripts during CI installs (then build only what you trust) removes a common execution path. For Python, avoid packages that require arbitrary setup hooks when a wheel will do.
Separate build from runtime. Build the image in CI from a locked lockfile, scan it, then run the MCP server from that immutable image. Do not npm install or pip install on the production host at startup.
Runtime controls after install
Even a clean install today can be compromised tomorrow via a dependency update. Assume breach at the process boundary:
- Verify the publisher before adding a new MCP SDK, transport, or helper library.
- Generate an SBOM for every release and keep it with the image digest.
- Install and run in a sandbox (container, non-root, read-only root FS)—see the sandbox article in this series.
- Allowlist egress. An MCP server that only needs one API should not be able to POST to arbitrary hosts. Block unexpected DNS and destinations.
Quick checklist
- Lockfile committed; CI installs with hash verification.
- Dependency audit gates the merge.
- Allowlist (or careful review) for new packages and major upgrades.
- No production
installat boot; images are immutable. - Non-root process, read-only filesystem, egress allowlist.
- Secrets from a vault or managed identity—not baked into the image layers that deps can scrape casually.
Your MCP server's security starts before the first tool call: it starts at install. Treat every dependency as code you chose to run with your credentials.
Related reading
- Securing MCP Server Database Connections: Direct, API, and Beyond: how to keep database credentials away from the model and apply least privilege to MCP data access—the same least-privilege mindset applies to what a compromised dependency can reach.
About the author
I write about Applied AI, MCP, Azure AI Search, and RAG. Follow me on Dev.to and Hashnode for more.



