Skip to content

MCP in Sandboxes ​

Preview

MCP support is in preview. The mcp option and the example gateway may change.

MCP (Model Context Protocol) is how AI agents discover and call tools such as web search, file access, or a paper index. Each tool is provided by an MCP server, usually a small program that talks over stdio.

The E2B-compatible mcp option lets you ask for a set of MCP servers when you create a sandbox. The servers run inside the sandbox, isolated from your own machine. You get back one URL and one token, and your agent connects to that URL to see and call the tools of every server you asked for:

python
from cubesandbox import Sandbox

sandbox = Sandbox.create(mcp={"time": {}, "fetch": {}})
url = sandbox.get_mcp_url()      # http://50005-<sandbox-id>.<domain>/mcp
token = sandbox.get_mcp_token()  # send as "Authorization: Bearer <token>"

The same code works with the stock E2B SDKs pointed at CubeAPI.

How it works ​

text
your agent ──(MCP over HTTP + token)──> 50005-<id>.<domain>/mcp
                                              │  CubeProxy
                                              ▼
                                    mcp-gateway in the sandbox
                                     ├── time server   (stdio)
                                     └── fetch server  (stdio)
  1. CubeAPI checks the shape of mcp and rejects malformed input with 400 before any sandbox is created. It does not store the value, forward it to the scheduler, or return it.
  2. Template: the SDK picks the template in the order described in Template priority. That template must contain an mcp-gateway program; the default sandbox image does not.
  3. SDK: after the sandbox starts, the SDK runs mcp-gateway --config '<json>' as root with a random token in GATEWAY_ACCESS_TOKEN. It waits up to 60 seconds, the same as the E2B SDK.
  4. Gateway: it starts the requested servers and serves their tools on port 50005. CubeProxy routes 50005-<id>.<domain> to that port, just like any other sandbox port.
  5. Failure: if the command fails or times out, the SDK kills the sandbox and raises Failed to start MCP gateway: ... with the gateway's output.

Cube itself only provides steps 1 and 3, plus routing. What runs inside the sandbox is decided by the template.

Template priority ​

When a sandbox is created with mcp, the Cube SDKs choose its template in this order:

  1. If template (TemplateID in Go) is passed, use that template.
  2. If template is not passed, use the template with the alias mcp-gateway.
  3. If no template with the alias mcp-gateway exists, use the template from the CUBE_TEMPLATE_ID environment variable.
  4. If neither the mcp-gateway template nor the CUBE_TEMPLATE_ID template exists, creation fails with a template-not-found error.

To change the alias mcp uses by default (mcp-gateway), set the CUBE_MCP_TEMPLATE_ID environment variable:

bash
export CUBE_MCP_TEMPLATE_ID=custom-mcp-gateway

Gateway contract ​

Any program can serve as the gateway as long as it is installed as mcp-gateway on PATH and behaves like this:

ItemRequirement
Commandmcp-gateway --config '<json>', run as root. <json> is the mcp object from the create call.
TokenRead from the GATEWAY_ACCESS_TOKEN environment variable. Write it to /etc/mcp-gateway/.token, which is how get_mcp_token() recovers it after Sandbox.connect.
ReadinessKeep serving after the command exits, and exit 0 only once the endpoint is ready. Exit non-zero with a readable message on failure. Finish within 60 seconds.
EndpointMCP streamable HTTP on port 50005, path /mcp. Reject requests without Authorization: Bearer <token>.
TemplateExpose port 50005 when creating the template.

Quick start with the example gateway ​

The repository ships a reference implementation in examples/mcp-gateway. It launches stdio MCP servers, merges their tools into one endpoint, and checks the token. Its template Dockerfile pre-installs a set of servers.

1. Build the template ​

bash
cd examples/mcp-gateway
docker build -f template/Dockerfile \
  --build-arg BASE_IMAGE=cube-sandbox-cn.tencentcloudcr.com/cube-sandbox/sandbox-code:latest \
  --build-arg MCP_SERVERS="duckduckgo fetch time arxiv" \
  -t <registry>/mcp-gateway:latest .
docker push <registry>/mcp-gateway:latest

cubemastercli tpl create-from-image \
  --image <registry>/mcp-gateway:latest \
  --alias mcp-gateway \
  --writable-layer-size 1G \
  --expose-port 49983 --expose-port 49999 --expose-port 50005 \
  --probe 49983 --probe-path /health
  • MCP_SERVERS lists the servers to pre-install. Pre-install every server you plan to use: the gateway waits at most 55 seconds, and servers that are not pre-installed are downloaded on first start.
  • BASE_IMAGE overrides the base image. The default, the CubeSandbox sandbox-code image, provides uv, node, and git.
  • To use an alias other than mcp-gateway, pass template when creating sandboxes, or set CUBE_MCP_TEMPLATE_ID for the Cube SDKs. See Template priority.

Wait until the template is READY.

2. Create a sandbox and call a tool ​

python
import asyncio
from cubesandbox import Sandbox
from mcp import ClientSession                                    # pip install mcp
from mcp.client.streamable_http import streamablehttp_client

async def main():
    with Sandbox.create(mcp={"time": {}, "fetch": {}}) as sandbox:
        headers = {"Authorization": f"Bearer {sandbox.get_mcp_token()}"}
        async with streamablehttp_client(sandbox.get_mcp_url(), headers=headers) as (read, write, _):
            async with ClientSession(read, write) as session:
                await session.initialize()
                print([tool.name for tool in (await session.list_tools()).tools])
                result = await session.call_tool("get_current_time", {"timezone": "Asia/Shanghai"})
                print(result.content[0].text)

asyncio.run(main())

If *.<domain> does not resolve on your machine, send the request to CubeProxy directly and set the Host header to the sandbox host, the same way the SDKs do with CUBE_PROXY_NODE_IP.

The other SDKs expose the same calls:

ts
const sandbox = await Sandbox.create({ mcp: { time: {}, fetch: {} } });
const url = sandbox.getMcpUrl();
const token = await sandbox.getMcpToken();
go
sandbox, err := client.Create(ctx, cubesandbox.CreateOptions{
    MCP: cubesandbox.MCPServers{"time": map[string]any{}},
})
url := sandbox.GetMCPURL()
token, err := sandbox.GetMCPToken(ctx)

Servers supported by the example gateway ​

mcp is an object keyed by server name. The example gateway understands three kinds of entries.

Catalog servers. The value holds the server's properties. Run mcp-gateway catalog in the template to list them.

NamePropertiesRuns
duckduckgouvx duckduckgo-mcp-server
arxivstoragePath (optional)uvx arxiv-mcp-server
fetchuvx mcp-server-fetch
timeuvx mcp-server-time
filesystempaths (required, list)@modelcontextprotocol/server-filesystem
memory@modelcontextprotocol/server-memory
sequentialthinking@modelcontextprotocol/server-sequential-thinking

You can add or replace entries with /etc/mcp-gateway/catalog.json in your template. It uses the same format as examples/mcp-gateway/internal/config/catalog.json.

GitHub servers. These are keyed github/<owner>/<repo>, as in E2B. The gateway clones the repository, runs installCmd in it, and starts runCmd:

python
Sandbox.create(mcp={
    "github/acme/weather-mcp": {
        "installCmd": "npm ci",
        "runCmd": "node dist/index.js",
        "envs": {"WEATHER_API_KEY": "..."},
    },
})

Local commands. Any other key with runCmd starts that command. Use this for servers already installed in your template.

When two servers expose tools with the same name, the first server by name keeps the plain name. Tools from later servers are renamed <server>_<tool>. Only tools are aggregated; MCP prompts and resources are not exposed. GET /health on port 50005 reports server and tool counts without a token.

Input validation ​

CubeAPI returns 400 when:

  • mcp is not an object;
  • an entry is not an object;
  • a GitHub key is not github/<owner>/<repo>;
  • runCmd is missing or empty for a GitHub server;
  • envs has an invalid name or a non-string value;
  • there are more than 64 servers.

Whether a server name is known is up to the gateway. The example gateway fails to start on unknown names, and the SDK reports that error.

Lifecycle ​

With the example gateway:

  • Pause / resume: the gateway and its servers are part of the memory snapshot and keep serving with the same token after resume.
  • Connect: get_mcp_token() reads the token file, so a new SDK handle can reuse the endpoint.
  • Clone and snapshot restore: the running gateway and its token are copied too. Every clone accepts the same token, so treat clones as one trust domain.
  • Crashes: if a server process exits, the gateway starts it again on the next call.

Security notes ​

  • The token protects the MCP endpoint only. CubeProxy authentication and network policies still apply, and servers inside the sandbox are subject to the sandbox's egress rules.
  • All MCP servers in a sandbox share one trust domain; they are not isolated from each other. The example gateway runs every server as root, so any server can read the other servers' envs (for example through /proc/<pid>/environ) and /etc/mcp-gateway/.token, and send them elsewhere. The installCmd and runCmd of a GitHub server are arbitrary commands run as root. Do not put untrusted servers and sensitive credentials in the same sandbox; use a separate sandbox for each trust domain.
  • As in the E2B contract, the SDKs pass the mcp config as a command-line argument (mcp-gateway --config '<json>'). While the gateway starts (up to about 60 seconds), the full config, including envs, is visible in that command's process arguments to any process in the sandbox.
  • The example gateway removes the token from its own environment and from every MCP server's environment, and sets envs only in the environment of the server they belong to. It does not write them to disk or logs. This prevents accidental leaks, but does not protect against a malicious server as described above.
  • The example gateway's log (/var/log/mcp-gateway.log) includes each server's stderr. A server that prints its own secrets will leak them there.

Bring your own gateway ​

You can replace the example with any program that meets the gateway contract. For example, you could put a thin launcher script named mcp-gateway in front of another gateway such as Docker MCP Gateway, which is what E2B's hosted mcp-gateway template is built on. Keep in mind that Docker MCP Gateway runs each server as a container, so the template would also need a working Docker daemon inside the sandbox. CubeSandbox has not validated that setup.