npxc
Sandboxed npm execution for MCP servers.
Runs Node.js / npm-based Model Context Protocol servers
inside an isolated Linux VM via Apple container,
with dynamic per-request filesystem scoping to the host process's working directory.
Installation
From source
Or build a release binary:
# binary at: ./target/release/npxc
Prerequisites
- macOS (Apple Silicon — M-series chip required)
- Apple
containerCLI installed (releases). The runtime flags and isolation guarantees below were verified againstcontainer0.12.3. - Rust toolchain ≥ 1.87 (only required to build from source)
After installing container, run:
This verifies the CLI is on your PATH and fully configures the container
system for you:
- Checks whether the
containersystem service is running. - If not running, starts it with
container system start --enable-kernel-install(which also installs the default kernel on first run). - If the service is already running but no default kernel is configured, runs
container system kernel set --recommendedto download and install one.
Running npxc doctor once after installation is all that is normally needed.
Usage
Drop-in replacement for npx
npxc is a transparent stdio proxy. Any tool or editor that lets you configure
an MCP server as a command works unchanged — just replace npx with npxc:
# Before
# After — same interface, sandboxed
In any MCP client config that accepts a command + args, substitute accordingly:
The MCP client sees the server as if it were a local process; the package actually runs inside an isolated VM with no network access and filesystem access scoped to the current working directory.
Live example
The repo includes examples/mcp_probe.rs, an interactive probe that runs
three scenarios against @sylphx/pdf-reader-mcp:
- Probe —
initialize+tools/list - Read PDF —
tools/callwith a local file (must be within CWD) - Scope test — attempt to read
/etc/passwd, expect a-32602rejection
# Build the binary first, then run the example
&&
# Or pass a specific PDF path
&&
Subcommands
| Command | Description |
|---|---|
npxc <pkg-spec> [-- args...] |
Build (if needed) and run the MCP server |
npxc build <pkg-spec> |
Build the image without running |
npxc rebuild <pkg-spec> |
Force a --no-cache rebuild |
npxc list |
List all cached npxc/… images |
npxc clean <pkg-spec> |
Remove a specific cached image |
npxc clean --all |
Remove all cached images |
npxc inspect <pkg-spec> |
Print resolved config, image tag, mount plan, then exit |
npxc doctor |
Check that all prerequisites are present |
Flags
--config <path> Alternate config file (default: ~/.config/npxc/npxc.toml)
--cwd <path> Override the CWD scope (default: process working directory)
--no-isolate Disable path scoping; mount CWD read-only instead (escape hatch, warns loudly)
--log-level <lvl> trace | debug | info | warn | error (default: warn; to stderr only)
--dry-run Resolve config and print the plan, then exit (does not build or run)
Exit codes
| Code | Meaning |
|---|---|
0 |
Normal shutdown (client closed stdin) |
1 |
Configuration or argument error |
2 |
Container runtime not available |
3 |
Image build failure |
4 |
Runtime error (container died unexpectedly) |
130 |
Interrupted (Ctrl-C) |
Configuration
Configuration files follow XDG conventions. On macOS the default location is
~/.config/npxc/.
~/.config/npxc/
├── npxc.toml # global defaults
└── packages/
├── sylphx-pdf-reader-mcp.toml # per-package overrides
└── ...
Per-package filenames are derived from the npm package name: lowercase,
replace @ and / with -, strip a leading -.
@sylphx/pdf-reader-mcp → sylphx-pdf-reader-mcp.toml.
Global config — npxc.toml
[]
= "node:lts-slim" # base image for built images
= "container" # CLI name or path
= "none" # "none" | "bridge"
= "512m"
= "1"
= "ro" # "ro" (recommended) | "rw"
= "warn"
[]
# Order matters: strategies are tried in sequence; results are unioned.
= ["config", "schema", "heuristic"]
[]
= true # args starting with "/" are treated as paths
= true # args starting with "~/" are treated as paths
= ["file://"]
Per-package config — packages/<name>.toml
= "@sylphx/pdf-reader-mcp"
= "0.4.2" # pinned; "latest" is allowed but discouraged
# Declare which arguments are filesystem paths, keyed by tool name.
# "*" applies to all tools.
[]
= ["path", "file", "filename", "input"]
= ["path"]
= ["path"]
# Declare arguments that must never be treated as paths (false-positive suppression).
[]
= ["url", "query", "pattern"]
# Optional per-package resource overrides.
[]
= "1g"
Security model
What npxc protects against
- Malicious package code. Runs inside an Apple
containerLinux VM with--network none, a read-only root filesystem (--read-only, with only atmpfsat/tmp), every Linux capability dropped (--cap-drop ALL), nonpm/npxat runtime, and a non-root user (USER node:node). - Broad filesystem access. The container's
/workspaceis populated dynamically: only files explicitly named in MCP tool calls (and only if they resolve within the host CWD) are ever visible to the package. The mount is read-only, so a package cannot write back through the published hard links to the host originals. (This is the default;--no-isolateinstead mounts the whole CWD read-only.) - Network exfiltration.
--network noneremoves all network interfaces (verified: outbound connections fail withENETUNREACH). - Persistence. Containers are ephemeral (
--rm). Nothing survives session end.
The filesystem boundary is the container mount, not the path heuristics: a file that
npxcfails to identify as a path is simply never published, so it stays invisible to the package. Path identification is a usability layer on top of a fail-closed boundary.
What npxc does not protect against
- Stdio exfiltration. A malicious package can include arbitrary content in MCP responses. The proxy does not filter output.
- LLM-driven enumeration. An LLM that calls a tool repeatedly to read many files under CWD is a behavioral problem outside the proxy's scope.
- Container / VM escape.
npxctrusts Applecontainer's isolation boundary. - TOCTOU on published files. The window between
canonicalizeand the hard link is not defended (requires a local attacker with write access).
License
MIT