On this page
MCP Sandbox: How to Sandbox MCP Servers on Your Machine (Nov 2026)

To sandbox an MCP server, launch it somewhere it can't reach your whole machine: a container with no host folders and no network, as a separate user, or behind an allowlist of the hosts it needs. Your AI client usually won't do this for you. A local server runs as you, with your files, keys and network.

People keep asking how. On r/mcp, someone running Claude Code in a sandbox with a two-rule network policy asked whether an MCP server should need the network just to start. Another asked whether anyone uses MCP firewalls; one answer was to wrap stdio servers in a small proxy that blocks write calls. On r/AI_Agents, how do you sandbox your AI coding agent asks about network allowlists, and r/ClaudeCode asks why so few people bother sandboxing and how best to sandbox Claude Code on macOS (the answers: Docker, or a separate user account). Sandboxes have holes too: see the reports on a Docker Sandboxes copy command following symlinks out of its folder and breaking the AWS MCP Python sandbox.

This guide covers servers that run on your machine (stdio, or local HTTP). A hosted server runs on someone else's machine; your limit there is the token and its scopes, covered in how to limit MCP tools.

Why a local MCP server runs with your full rights

With the stdio transport, the MCP spec says "the client launches the MCP server as a subprocess." A subprocess gets the same user, files, environment variables and network as the app that started it. So a server you added for one folder can, unless something stops it, read ~/.ssh, your cloud credentials and every other project, and send them anywhere.

The spec knows this. Its security page lists "Local MCP Server Compromise" as an attack, with risks including "arbitrary code execution" and "data exfiltration". The client guidance there is a list of SHOULDs, not MUSTs:

  • "Warn that MCP servers run with the same privileges as the client"
  • "Execute MCP server commands in a sandboxed environment with minimal default privileges"
  • "Launch MCP servers with restricted access to the file system, network, and other system resources"
  • "Use platform-appropriate sandboxing technologies (containers, chroot, application sandboxes, etc.)"

The one MUST in that section is narrower: a client that offers one-click setup must show the exact command and ask before running it. A consent dialog isn't a sandbox. Once you click yes, the server runs as you.

Does Claude Code's sandbox cover MCP servers?

No. Claude Code's sandboxing docs say: "The sandbox covers shell commands only. Claude's file tools, MCP servers, and hooks run outside it." They list "local MCP servers" among the processes that "run with your full access".

What /sandbox (or sandbox.enabled: true in settings) does cover is still useful: Bash and the processes it starts get OS-level limits (Seatbelt on macOS, bubblewrap on Linux and WSL2), writes only to the working directory and paths you add, and network only to hosts in sandbox.network.allowedDomains, which starts empty. It's off by default, and on native Windows commands run unsandboxed. It just doesn't reach the MCP servers in your config.

VS Code launches stdio servers as child processes without a sandbox, and in Cursor and Claude Desktop a local server is also just the command in your config. So the sandbox has to be in that command. That's the trick every method below uses: you change "command" from npx or uvx to something that wraps the server.

Three levels of MCP sandbox

Level What it stops What it doesn't Effort
1. A separate OS user Reading your home folder, keys and other projects Network access; world-readable files Medium
2. A container with no host disk Reading or writing anything you didn't mount; with --network none, all network Misuse of a token you pass in Low if you have Docker
3. A network allowlist Sending data to hosts you didn't list Misuse of the hosts you did list Medium to high

They stack. A container with one read-only folder, no network or a short allowlist, and a narrow token is about as tight as a local server gets.

Level 1: run the server as a separate OS user

Create a standard account with no admin rights (System Settings on macOS, useradd on Linux), give it only the folders the server needs, and start the server as that user. Your client launches it through sudo:

{ "mcpServers": { "filesystem": {
    "command": "sudo",
    "args": ["-n", "-u", "mcp-runner", "-H",
             "npx", "-y", "@modelcontextprotocol/server-filesystem",
             "/srv/mcp-share"] } } }

-u runs the command as another user, -H sets HOME to that user's home (so its npm cache and config live there, not in yours), and -n makes sudo fail instead of waiting for a password the client can't type. For that you need a sudoers rule letting your account run only that command as mcp-runner without a password. Test the setup with the MCP Inspector before you trust it, since sudo settings vary by system.

This is the "separate user account" answer from the Reddit threads. It keeps the server out of your files and keychain. It doesn't limit the network, and the new user can still read anything on the machine that's readable by everyone.

Level 2: run the MCP server in Docker with no host disk

A container starts with its own filesystem and sees none of yours unless you mount it. Many servers publish an image, and their READMEs show a docker run config. The Filesystem server's README, for example, uses "command": "docker" with run -i --rm, a --mount per folder, and the mcp/filesystem image. Here is that config with the extra limits added:

{ "mcpServers": { "filesystem": {
    "command": "docker",
    "args": ["run", "-i", "--rm",
             "--network", "none",
             "--read-only", "--tmpfs", "/tmp",
             "--cap-drop", "ALL",
             "--security-opt", "no-new-privileges",
             "--memory", "512m", "--cpus", "1",
             "--mount", "type=bind,src=/Users/you/project,dst=/projects/project,ro",
             "mcp/filesystem", "/projects"] } } }

What each flag does, from Docker's docs:

Flag Effect
-i "Keep STDIN open even if not attached". Required: stdio MCP talks over stdin and stdout
--rm Removes the container when the client closes it
--network none Only a loopback device is created: no network at all. Stdio still works, because it doesn't use the network
--read-only "Mount the container's root filesystem as read only"
--tmpfs /tmp A throwaway writable /tmp for servers that need scratch space
--cap-drop ALL Drops Linux capabilities the server shouldn't need
--security-opt no-new-privileges Stops processes inside from gaining new privileges
--memory, --cpus Caps a runaway server
--mount ...,ro (or -v src:dst:ro) Mounts one folder, read-only. Drop ro only if the server must write there

Don't mount your home folder, ~/.ssh, ~/.aws or the Docker socket (/var/run/docker.sock): the socket lets a container start other containers with any mounts it likes. If a server won't start with --read-only, try without it before removing anything else.

For a server that needs a credential, pass that one variable with -e NAME (the GitHub MCP server's README does this with -e GITHUB_PERSONAL_ACCESS_TOKEN) instead of handing it your whole environment. Docker isolates the disk; the token still opens whatever it opens.

Docker's own MCP Gateway runs catalog servers this way for you, with no host disk by default and per-server secrets. Its network is open by default, though; see what it doesn't isolate for the flags that close it.

Level 3: a network allowlist for MCP servers

--network none is the easy answer for servers that don't need the network. For one that needs a single API, give it a route to that API and nothing else:

  1. Create a network with no route out: docker network create --internal mcp-egress. Docker's docs say containers on an internal network "may communicate between each other, but not with any other network".
  2. Run an HTTP(S) forward proxy that supports a domain allowlist, attached to both mcp-egress and a normal network, and allow only the hosts the server needs (for the GitHub server, api.github.com).
  3. Start the MCP server with --network mcp-egress and -e HTTPS_PROXY=http://<proxy>:<port>.

If the server's HTTP library ignores HTTPS_PROXY, its calls fail, because the internal network has no other way out. That's the safe way to fail. Watch the proxy's log for a day: it's also the easiest way to learn what a server really calls.

On a single machine without Docker, the same idea needs an OS firewall that filters by app or by user. That's harder to keep right, which is why most people pick a container.

Claude Code's sandbox.network.allowedDomains is this kind of allowlist, but for shell commands only, as above. If your agent runs curl through Bash, it applies; if an MCP server makes the call, it doesn't.

Which MCP servers need the network?

Ask two questions: does it need the network to start, and does it need it to work?

To start, a config like npx -y package or uvx package downloads the server from npm or PyPI when it's not cached. That's why some servers "need the network just to start". Install a pinned version once, or bake it into an image, and the server can start offline. A server that still refuses to start offline is phoning home, and you should know why.

To work:

Server type Network it needs Sandbox setting
Local files (Filesystem) None. Its README says it "only accesses the local filesystem within its allowed directories" --network none
One SaaS API (GitHub, Stripe, Linear) That API's host, plus your sign-in host if it uses OAuth Allowlist that host
A database (DBHub) The database host and port only Put the database on the same internal network
A browser (Playwright) Every site you ask it to open Allowlist only if you browse a fixed set of sites
A URL fetcher (Fetch) Any URL, by design An allowlist defeats the point; never allow private IPs
A hosted server you reach by URL Not yours to sandbox Limit the token and its scopes instead

A server that only reads local data and still asks for the open internet should be able to tell you why in its README. If it can't, that's a reason not to install it.

An MCP firewall: a proxy that refuses write calls

An "MCP firewall" usually means something that sits between the client and the server and drops calls it doesn't like. For a stdio server, that can be a few dozen lines. The stdio transport is one JSON-RPC message per line, so a wrapper can start the real server, pass messages both ways, and answer tools/call for any tool not on its list itself:

#!/usr/bin/env python3
"""mcp-allow.py: run a stdio MCP server, refuse tools/call for tools not in ALLOW."""
import json, subprocess, sys, threading

ALLOW = {"read_text_file", "list_directory", "search_files", "get_file_info"}

lock = threading.Lock()
def send(line):
    with lock:
        sys.stdout.write(line if line.endswith("\n") else line + "\n")
        sys.stdout.flush()

child = subprocess.Popen(sys.argv[1:], stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE, text=True, encoding="utf-8")

def pump():
    for line in child.stdout:
        send(line)
reader = threading.Thread(target=pump, daemon=True)
reader.start()

for line in sys.stdin:
    try:
        msg = json.loads(line)
    except ValueError:
        msg = {}
    if isinstance(msg, dict) and msg.get("method") == "tools/call" \
            and (msg.get("params") or {}).get("name") not in ALLOW:
        send(json.dumps({"jsonrpc": "2.0", "id": msg.get("id"), "result": {
            "resultType": "complete", "isError": True,
            "content": [{"type": "text", "text": "Refused by local policy."}]}}))
        continue
    child.stdin.write(line)
    child.stdin.flush()

child.stdin.close()
child.wait()
reader.join()

Then point the client at the wrapper, with the real command after it: "command": "python3", "args": ["/path/mcp-allow.py", "docker", "run", "-i", ...]. It returns a tool error with isError: true, the form the spec uses for errors the model can read and recover from. Treat this as a sketch for macOS and Linux, not a product: it allows by name, so list read tools you've checked, and test it with the MCP Inspector.

Three things to know before you rely on a proxy like this:

  • Allow by name, not by label. Tools can carry a readOnlyHint, but the spec says clients "MUST consider tool annotations to be untrusted unless they come from trusted servers". A proxy that blocks "anything not marked read-only" trusts the server it's meant to guard.
  • Your client may already do this. Claude Code's deny rules refuse a call by tool name, see Claude Code: deny rules are the hard block. A proxy helps when you use several clients or one without a hard block.
  • The proxy is now part of your trust chain. The spec says MCP proxy services that spawn stdio servers SHOULD "implement sandboxing or containerization for spawned processes" and "restrict file system access for spawned MCP servers". So run the proxy, and what it starts, inside the container, not around it. For a team, this job grows into an MCP gateway.

We don't name firewall products here. If you pick one, read its source first: it sees every call and every result.

What an MCP sandbox doesn't stop

  • Misuse of access you granted. A GitHub server in a locked container with a token that can push can still push. Sandboxing limits the machine; the token limits the account. Use a read-only token or the server's read-only mode (how to limit MCP tools).
  • Prompt injection. Text in an issue, page or file can steer the model into calling tools you allowed. The container doesn't read prompts. See MCP prompt injection and the lethal trifecta: private data, untrusted content and a way out.
  • Leaks through the hosts you allowed. If a server can reach api.github.com, it can write your data into a gist or an issue. An allowlist narrows the exits; it doesn't close the one you left open.
  • The client and the model. The server is sandboxed; the agent that calls it may not be. In Claude Code, file tools and hooks run outside the sandbox too.
  • Sandbox bugs. Containers and OS sandboxes have had escapes, as the threads above show. Keep Docker and your client updated.

Sandbox an MCP server in 5 steps

Tick these off for each local server.

1. Check whether it needs the network

Read its README and its tool list. No API, no browser, no fetching? Then it gets --network none.

2. Run it in a container with only the folders it needs

Switch the config's command to docker run -i --rm with one --mount per folder, read-only unless it must write. Never mount your home folder or the Docker socket.

3. Pass only its own credential

Use -e NAME for the one variable it needs, with a token scoped to the job (read-only where you can).

4. Limit the network to the hosts it calls

Internal Docker network plus an allowlisting proxy, or the Docker MCP Gateway's network settings. Watch the log, then trim the list.

5. Block the tools you never want called

Deny them in your client, turn on the server's read-only mode, or put an allow-by-name proxy in front. Re-check the tool list after each update; our MCP security checklist covers what to look for.

If your team needs servers that run sandboxed by default, with their own narrow credentials and a log of every call, that's the kind of setup we build. Tell us what you need to connect.

Frequently asked questions

What is an MCP sandbox?

A limit around an MCP server's process so it can't use everything your account can: a container, a separate user, or an OS sandbox, usually with limits on files and network. Most clients don't add one; you add it in the command that starts the server.

Does Claude Code sandbox MCP servers?

No. Its docs say the sandbox "covers shell commands only" and that local MCP servers run "with your full access". Wrap the server's command in docker run (or another sandbox) yourself.

How do I run an MCP server in Docker?

Set "command": "docker" and "args": ["run", "-i", "--rm", ..., "<image>"]. The -i keeps stdin open, which stdio needs. Add --network none if it doesn't need the network, and mount only the folders it needs, read-only where you can.

Does an MCP server in Docker still work over stdio with no network?

Yes. With -i, Docker connects the container's stdin and stdout to the client. That isn't network traffic, so --network none doesn't affect it.

What is an MCP firewall?

Usually a proxy between the client and the server that inspects calls and refuses some, for example any tool not on an allowlist. It can also log calls. It doesn't stop a server's own network traffic; a container or network allowlist does that.

Should an MCP server need the network just to start?

Only to download itself, when you launch it with npx -y or uvx and it isn't cached. Install a pinned version or use an image and it should start offline. If it still won't, ask why before you trust it.

Does the Docker MCP Gateway sandbox MCP servers?

Partly. It runs catalog servers in containers with no host disk by default and per-server secrets, but their network is open by default. See what it doesn't isolate for the flags that close it.

Is a sandbox enough to make an MCP server safe?

No. It protects your machine, not the accounts the server can reach. Pair it with a narrow token, the server's read-only mode where it has one, and care about what untrusted text the model reads.