MCP Server Environment Dependencies and Packaging
Your packaging choice determines whether your MCP server behaves consistently and securely.

The distribution format chosen for an MCP server, whether that's npx, uvx, Docker, a native binary, or a Desktop Extension, decides how dependencies get resolved, whether the server behaves the same way twice, and how exposed it is to a supply chain attack. Developers usually pick a format because it's what the quickstart README shows, not because they've worked through what that format promises and what it quietly fails to promise. As of early 2026, more than 12,000 public MCP servers sit across various registries, and MCP is fast becoming the default way AI agents plug into tools and data. That means a packaging decision made casually today, by one developer, on one afternoon, ends up baked into thousands of downstream installs before anyone notices it was a decision at all. This piece is not about crowning a winner among formats; it's about laying out what each one actually guarantees, so the choice gets made on purpose.
How npx and uvx resolve dependencies at runtime, and what that costs
npx and uvx share a basic design: both are ephemeral runners. Neither requires the user to set up or maintain a persistent environment. Instead, they fetch a package, install it, and run it, all in one motion, at the moment the command executes.
npx resolves the package name against the npm registry right then, at invocation. It pulls that package and everything it declares as a dependency, transitive graph and all, then runs from a temporary or cached location. No lockfile governs that resolution at runtime. So two developers typing the identical npx command on two different days can end up running different transitive dependencies, simply because something upstream got updated or swapped out in between.
uvx behaves similarly, with one wrinkle. Inside a project, uv can resolve from PyPI using its own lockfile. But the mcp dev CLI runs the server against whatever happens to be sitting in the active virtual environment at that moment. Anthropic's own guidance is to pass the project directory or explicit --with flags so Claude Desktop picks up the right packages; that's a manual step someone has to remember to take, not something the tool enforces on its own. If a developer's local venv has drifted from whatever was actually tested, the server's behavior drifts with it, silently, with no error to flag the mismatch.
None of this is accidental laziness on the part of npm or PyPI; it's the trade being made. Both formats are popular precisely because they remove friction: no Docker daemon to start, nothing to compile, no environment to provision by hand. But that convenience is bought by handing control of what actually runs to the registry, at the moment of execution, rather than to the developer who wrote and tested the code.
What the npm supply chain attacks of 2025 revealed about runtime-fetched MCP servers
The September 2025 npm attack landed inside the MCP ecosystem, and how much damage it did depended almost entirely on how the affected servers had been packaged and deployed. The November wave was worse. It hit packages with combined monthly downloads in the hundreds of millions, and it specifically went after packages with "mcp-server" in the name, which is about as clear a signal as the industry has gotten that MCP servers are now a high-value target, not an obscure corner of the npm registry.
By then, AI coding agents had become the most active installers of obscure npm packages across the whole ecosystem. That detail matters more than it sounds like it should: the thing consuming a compromised package was often another automated system, not a human sitting at a terminal who might notice something odd happening on screen.
The Postmark case from September 2025 is worth sitting with. An unofficial MCP server, with real weekly download numbers behind it, got quietly modified to BCC every outbound email to an address controlled by the attacker. Anyone with auto-update turned on started leaking email content, and nothing about the server's visible behavior changed at all. It kept working exactly as expected, which was the whole point.
What ties these incidents together structurally is a gap: the gap between what a developer expects to run and what the registry actually serves the moment the command fires. Runtime-fetched formats, npx and uvx without strict version pinning, have no way to detect that a package's contents changed since the last time anyone looked at it. Postmark is the clean illustration that the danger isn't always a server that breaks. Sometimes it's a server that works perfectly while quietly doing one thing extra.
OWASP has since given this a formal name, listing software supply chain attacks and dependency tampering as a named threat category for MCP deployments, and recommending continuous inventory of component versions against CVE, NVD, and OSV. OX Security tested how seriously the registries themselves take this: researchers submitted a proof-of-concept malicious package to eleven MCP registries. Nine accepted it without any challenge. The distribution infrastructure, in other words, has not caught up to the threat it's now carrying.
The Docker packaging model and what container boundaries actually guarantee
Docker is becoming the default for production MCP deployments for a specific reason: dependencies, runtimes, and configuration all get bundled into the image at build time. What ships to production is the exact artifact that was tested, not a dependency graph resolved fresh on someone else's machine.
That buys three things. The application's own dependencies are frozen the moment the image is built. The runtime environment, meaning OS userland, interpreters, shared libraries, stays consistent across every host that runs that image. And versioning plus provenance tracking become available at the image level, something npx and uvx simply don't offer.
What Docker does not hand a developer automatically is a clean base image. Standard public base images typically ship carrying 50 to 60 known CVEs out of the box; minimal, source-built images can cut that down to single digits. Choosing the base image is, arguably, the single highest-leverage security decision inside the whole Docker packaging model, and it's also the decision most likely to get made on autopilot. Beyond the base image, if the Dockerfile pulls from npm or PyPI at build time without a lockfile pinning those versions, the resulting image can contain a different set of transitive dependencies on every single build, which defeats a good chunk of the reproducibility Docker is supposed to deliver in the first place. And an image that sits untouched for months accumulates CVEs in its base layer as new vulnerabilities get disclosed against packages that were clean the day the image was built.
Docker's own MCP Catalog is a response to exactly this problem: a curated collection of verified MCP server images on Docker Hub, with versioning, provenance tracking, and automated security updates attached. It's a real step toward managed hygiene at the distribution layer, though it only covers what's listed there.
Container isolation by itself is not the whole defense. Restricting outbound network access to only the destinations a server actually needs proved effective against the 2025 npm attack wave; a container with no network controls around it still leaves an open door for exfiltration, even if the process inside is sandboxed. There's also a practical wrinkle worth naming: most MCP servers are built to run over stdio, as a local subprocess, not as a network service. Getting one into a production HTTP deployment usually means a bridge like mcp-proxy, and serverless setups need something like mcp-bridge instead. Each bridge is another link in the trust chain, and another thing that has to be kept current.
CVE-2025-6514 and what a 9.6-severity MCP vulnerability looks like from the inside
CVE-2025-6514 hit the mcp-remote package and stands as the first documented case of full remote code execution pulled off against an MCP client in a real-world setting. It scored 9.6 out of 10 on the CVSS scale, which sits about as high as these ratings go.
The exposure here was wide. mcp-remote had been downloaded more than 437,000 times by the time the vulnerability came to light, and it had shown up in integration guides published by Cloudflare, Hugging Face, and Auth0. That's reach flowing through official documentation, not just word of mouth in some forum. mcp-remote works as a bridge: it lets tools like Claude Desktop, VS Code, and Cursor connect out to external MCP servers, which puts it directly in the trust path between a client and every remote server it touches.
Packaging format decided how exposed any given user actually was. Developers who'd installed mcp-remote through npx or uvx had no way to confirm that the package running on their machine, right then, matched whatever version they'd last reviewed. Developers running pinned Docker images, or self-contained binaries, were only at risk if they'd explicitly built or updated to the vulnerable release, which makes both the exposure narrower and the fix easier to verify.
2025 produced four named CVEs at the MCP layer in total: this one, CVE-2025-49596 in MCP Inspector, CVE-2025-54136 in Cursor, and CVE-2025-54994 in create-mcp-server-stdio. Four in a single year, for a protocol this young, says its security history is still being written in real time, not settled. Separate from any of these package-level bugs, tool poisoning remains its own threat: a malicious MCP server can embed instructions inside tool descriptions and schemas that stay invisible in the IDE's interface while the underlying language model reads and acts on them anyway. That risk exists no matter which packaging format a server uses.
Desktop Extensions and native binaries as self-contained packaging approaches
Anthropic launched Desktop Extensions, the.dxt format, in June 2025. A single.dxt file bundles an MCP server's code, its dependencies, and its configuration together, installable in Claude Desktop with one click. Everything gets resolved and packed in at the moment the author builds the package, not later, when a user installs or runs it.
That shifts where the risk sits. The user's attack surface at install time shrinks, since there's no runtime fetch happening at all. But the author's responsibility grows correspondingly: whoever built that.dxt file controls, completely, what ships inside it. The open question is patching. How fast a security fix in some bundled dependency reaches an installed instance depends entirely on the author publishing a new.dxt and the user actually going and installing it; there's no automatic update pathway forcing that along.
Native binaries take a related approach from a different direction. Microsoft's guidance for.NET MCP servers recommends self-contained packaging, without AOT compilation, specifically to remove any dependency on whatever.NET runtime version happens to be installed on the host. Framework-dependent packaging only makes sense when a compatible runtime can be guaranteed on the target machine, which in practice is a hard guarantee to make. The general idea carries across languages: a statically linked or self-contained binary brings its own runtime along, so the host machine contributes nothing to how dependencies get resolved.
Both approaches trade one thing for another. Binaries are locked to a specific OS and architecture, so shipping to multiple platforms means building separate artifacts for each one, and each binary carries the full weight of its bundled runtime when a security patch is needed. The dependency graph gets locked in at compile time, which makes it auditable before it ever reaches a user, but opaque to anyone who isn't going to open up the binary and look..dxt and self-contained binaries both trade runtime flexibility for build-time certainty: the tradeoff is knowing exactly what will run, in exchange for the author carrying more of the burden of keeping what's bundled inside actually current.
SBOMs and provenance across packaging formats — what each format makes easy or hard
A software bill of materials, an SBOM, is a machine-readable list of every component, library, and dependency inside an MCP server, complete with version numbers, origins, and licenses, typically in SPDX or CycloneDX format. This isn't just good practice anymore; U.S. Executive Order 14028 requires federal software vendors to produce them, and NIST SP 800-218 lays out the broader secure development framework around it. For any server touching an enterprise or government environment, an SBOM is becoming table stakes.
Packaging format shapes how good that SBOM can actually be. For Docker images, scanning tools can generate an SBOM straight from the image's layer graph, but the quality of that document depends on whether the Dockerfile used a lockfile at build time. Skip the lockfile, and the SBOM just reflects whatever happened to get resolved on that particular build, not what the author intended to ship. For npx and uvx, an SBOM can only be generated if the dependency graph gets captured at some known point in time; without pinning, there may not even be a single authoritative graph to take a snapshot of. Native binaries and.dxt files freeze the dependency graph at build time, which is in principle fully auditable, though tooling for extracting an SBOM out of a compiled artifact still varies a lot depending on the language ecosystem involved.
An SBOM is more useful once it's enriched with repository URLs, commit SHAs, and build provenance attestations, such as in-toto or SLSA, so a binary can actually be traced back to the source that produced it; open standards are worth favoring here specifically to avoid getting locked into one vendor's tooling. Most SBOMs generated today, though, get produced at the end of a build, filed away somewhere, and never looked at again. The real value isn't in the snapshot; it's in checking that snapshot continuously against CVE, NVD, and OSV as new vulnerabilities get disclosed. And there's a newer wrinkle worth naming: by 2026, the model behind an MCP-enabled agent is itself a third-party dependency, one that a conventional scanner can't read at all. That's pushing ML-BOMs, documents covering training data, architecture, and safety benchmarks, toward becoming the next real frontier in MCP supply chain governance.
How reproducible environment tooling changes the dependency problem for MCP server authors
Underneath the packaging question sits a plainer problem: environments drift. Development, testing, and production diverge from each other in small ways that compound, and the debugging cost of chasing those differences down scales with the size of the team. For a large organization running around 100 developers, that drift alone can represent something on the order of $1 million a year in wasted engineering time.
The MCP Docker server's own repository offers a small but telling example. It ships a devbox.json alongside the project so contributors get a reproducible environment, and its test and lint suite can run entirely through uv, with no Docker daemon required at all. That detail matters: reproducible contributor environments and production packaging aren't competing concerns. They solve adjacent problems and can live side by side in the same project.
Four tooling approaches dominate this space as of 2026, and each gives MCP server authors something a little different, tools like Flox, a single-manifest environment manager built on Nix, also fit here, keeping dependencies identical across laptops, CI, and production from one manifest.toml. Nix and Nix Flakes pin dependencies all the way down to the C library level and work cross-platform without touching Docker at all; it's the deepest reproducibility guarantee available today, though some packages on Apple Silicon still need workarounds. Devbox wraps Nix behind a plain JSON configuration file and a lockfile that pins every package to a specific Nixpkgs commit; a team that checks both files into version control gets Nix-level reproducibility without anyone having to actually learn Nix, and it supports containerless service orchestration as an alternative to something like Docker Compose. Dev Containers define tooling and configuration as code, though their common failure mode is floating image tags that quietly resolve to different content over time, echoing the exact npx and uvx problem this piece opened with, just one layer up the stack.
The throughline across all of it: reproducibility is never free, and it's never automatic. It has to be built in deliberately, at whichever layer a team decides to fight for it, whether that's the interpreter, the container image, or the contributor's own laptop.


