MCP servers
Sandbox Router runs your MCP server in a sandbox and serves it at an authenticated endpoint, https://mcp.sandboxrouter.io/<server-id>. MCP clients connect with a client token. For each request, the gateway checks the token and the tools it may call, records a trace, and passes the request to your server. The server stops when it's idle and starts again on the next request.
A server serves MCP over Streamable HTTP on a port of its sandbox. It can run:
A command on the Default image (Alpine Linux with bash, coreutils, Node and Docker).
Your own image from
registry.caas.sh, with its own entrypoint or a command you give.
The server's sandbox is an ordinary sandbox of your workspace: you manage its outbound hosts, labels, files and snapshots like any other (see Sandboxes), and a connector its create names gives the server an API key without it ever holding the key. You can also add a server to a sandbox you already have.
Sandboxes run on a prepaid balance, so add funds before you deploy.
Deploy from the console
Open MCP servers and choose Deploy server.
Under 1. Choose an image, choose Default or Your image.
With Default, enter the server's Command, which serves MCP over HTTP on the port you give below.
With Your image, enter the image's digest reference,
registry.caas.sh/<tenant-id>/my-server@sha256:…, with the digest thatdocker pushprints. Command is optional. It replaces the image's entrypoint and command.Enter the Port the server listens on for MCP over HTTP.
Under 2. Configure, you can enter a Name: 1 to 63 lowercase letters, digits and hyphens, unique among the workspace's servers. Without one, the server is named
mcp-and 12 hex digits. Choose a Size. The default is M (1/2 vCPU, 4 GiB of memory, 8 GB of disk), and a server on your own image needs M or larger.In Allowed outbound hosts, list the hosts the server calls, one per line, or
*for every host. Everything else is blocked, and an empty list reaches no host.Choose Deploy server.
The server's page opens with its Initial client token. Copy it now, because it isn't shown again. This token can call every tool and never expires. To give each client its own token, see Client tokens.
Deploy with the CLI
First set up the sandboxrouter CLI with an API key, as The CLI describes. sandboxrouter mcp create creates a server in a new sandbox of its own.
Your own image
Push the image to your workspace's namespace of registry.caas.sh first, as Images and the registry describes. --image takes a name with a tag or a digest, or default for the Default image. The CLI resolves a tag to its digest and deploys that digest.
Give the port the server listens on with --http. The gateway sends requests to the path /mcp on that port, or to --path.
sandboxrouter mcp create --image server:1.2.3 --http 3000 --command "node http.js"Without --egress, the server's sandbox reaches every host. To allow only the hosts it calls, pass --egress once per host.
The port must be from 1024 to 65535; it needn't be one of the sandbox's ports, which the gateway doesn't use. Listing it with --port as well also gives it its provider's own URL, where the provider's access control applies and client tokens don't guard it. --command replaces the image's entrypoint and command. Without it, the server runs the image's own entrypoint and command. A server on your own image needs size M or larger.
In a sandbox you already have
--sandbox adds a server to an existing sandbox of the workspace, by its ID or name, which runs it itself:
sandboxrouter mcp create --sandbox web --http 3000 --path /mcp --name web-toolsThe sandbox keeps its own configuration, so the sandbox options below don't apply. A sandbox hosts at most one server. Deleting such a server leaves the sandbox.
Options
--name NAME names the server: 1 to 63 lowercase letters, digits and hyphens, unique among the workspace's servers. A new sandbox takes the sandbox options of sandboxrouter sandboxes create (see Fields), such as:
| Option | What it does |
|---|---|
--size SIZE | xs, s, m (the default), l, xl, 2xl, or custom with --vcpu, --memory and --disk. |
--egress HOST | A host the server may reach. Repeat it for each host. |
--env NAME=VALUE | A variable for the server. |
--idle-stop MINUTES | Stop after this many minutes without activity, from 5 to 240. The default is 15. |
--no-volumes | Keep nothing in /workspace between restarts. |
The CLI prints the endpoint, the sandbox the server runs in, and the secret of each token it minted: Initial client, unless a server file declares its own. This is the only time a secret is shown.
From a server file
One YAML or JSON file can declare a whole server, client tokens included:
server: search-mcp
source:
image: search-mcp:1
port: 8080
size: m
idle: stop after 30m
tokens:
- name: laptop-claude
tools: [search.query, search.fetch]
- name: ci-pipeline
tools: [search.query]
expires: 30d
egress: [api.search.example.com]sandboxrouter mcp create -f search-mcp.yaml| Field | Value |
|---|---|
server | Required. The server's name, unique in the workspace. |
source | image: IMAGE, an optional command, and the port the server serves MCP over HTTP on. |
size | A size name, xs to 2xl, or "<vCPU> vCPU · <GiB> GiB · <GB> GB". Sandbox Router rounds a written size up to one it can run, and the CLI prints a note when it does. |
idle | How long the server runs idle before it stops, such as stop after 30m or stop after 2h: 5 minutes to 4 hours. |
tokens | Up to 20 client tokens. Each has a name, optional tools (every tool when left out) and an optional expires from 1d to 365d (never when left out). |
egress | The hosts the server may reach: ["*"] for every host, [] for none. Without it, the server reaches every host. |
A file that starts with { is read as JSON with the same fields. The YAML can't use tabs, anchors, block scalars or more than one document.
The CLI checks the whole file before it sends anything. Then it creates the server and its tokens in one request, and prints the endpoint and each token's secret, once. You can add sandbox options the file doesn't declare, such as --label and --env.
In the console, Deploy from a server file on the Deploy a server page takes the same file, pasted or chosen. There, an image must be a digest reference; deploy a file that names a tag with the CLI, which pins it.
Connect a client
You can find the endpoint URL under Endpoint on the server's page, in the CLI's output (sandboxrouter mcp get <server>), and as url in the API's view of the server. The ID in it is opaque: it doesn't reveal your workspace.
Clients connect over Streamable HTTP and send a client token as a bearer token:
Authorization: Bearer cmcp_…The server's page shows a configuration to add to an MCP client:
{
"mcpServers": {
"search-mcp": {
"url": "https://mcp.sandboxrouter.io/<server-id>",
"headers": { "Authorization": "Bearer $SANDBOXROUTER_MCP_TOKEN" }
}
}
}Put the client token in place of $SANDBOXROUTER_MCP_TOKEN, unless your client fills in environment variables there. In Claude Code, for example:
claude mcp add --transport http search-mcp https://mcp.sandboxrouter.io/<server-id> --header "Authorization: Bearer <client-token>"To check the endpoint from a terminal, send an initialize request:
curl -i https://mcp.sandboxrouter.io/<server-id> \
-H "Authorization: Bearer $SANDBOXROUTER_MCP_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'If the server is stopped, the first request starts it and can take a while, as Idle stop and wake explains.
The gateway applies these limits:
A request body can be up to 4 MiB.
Each endpoint takes about 1,200 requests a minute. Beyond that, the gateway answers
429.The gateway waits up to 100 seconds for your server to start answering a request. After that, the client gets
504.A request that sends an
Originheader other thanhttps://mcp.sandboxrouter.iogets403, so a web page on another site can't call the endpoint from a browser. MCP clients that aren't browsers send noOrigin.
Client tokens
A client token gives access to one server's endpoint and nothing else. Its secret starts with cmcp_ and is shown only once, when the token is created. Sandbox Router stores only a hash of it.
A deploy from the console's form, or with
--imageor--sandbox, creates one token, Initial client. It can call every tool and never expires.A deploy from a server file creates the tokens its
tokenslist declares instead, or Initial client if the list is empty or missing.
Give each client its own token. Each client's calls then show under its name, and you can revoke one client's token without affecting the others.
To create a token in the console:
On the server's page, under Client tokens, choose New token.
Enter a Client name. Under Allowed tools, list the tools it may call, one per line, or leave it empty to allow every tool.
Choose Create token, and copy the token. It expires in 90 days.
With the CLI:
sandboxrouter mcp tokens create search-mcp --name "Claude Desktop"
sandboxrouter mcp tokens create search-mcp --name ci-pipeline --expires-days 30 --allow-tool search.query
sandboxrouter mcp tokens list search-mcp
sandboxrouter mcp tokens revoke search-mcp <token-id>Each command takes the server's name or ID. --expires-days takes 1 to 365, and the default is 90. tokens list shows each token's ID, name, state (active, expired or revoked), allowed tools, and counts of its calls and errors. On the server's page, the Client tokens list shows when each token was created and last used, and how many requests it made.
Revoking a token takes effect at once: its next request gets 401. It doesn't stop the server or affect other tokens. To revoke a token in the console, choose Revoke beside it.
A server can have up to 20 tokens in force at once. Revoked tokens stay listed for 30 days.
With the API, a token can expire up to a year ahead, or never:
curl https://api.sandboxrouter.io/v1/mcp-servers/search-mcp/tokens \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{"label": "ci-pipeline", "allowedTools": ["search.query"]}'The answer is the token with its secret. expiresAt takes a time in milliseconds since the Unix epoch, from a minute to a year ahead. Without it, the token never expires. A GET to the same path lists the tokens, and DELETE …/tokens/<token-id> revokes one.
Tool grants
A token can be limited to the tools you name: up to 32 exact, case-sensitive tool names. A limited token:
Can call only those tools with
tools/call. A call to any other tool gets403, and its trace reads Tool not granted.Sees only those tools in
tools/list.Can still use resources, prompts and the other methods that aren't tool calls.
Can't use the
tasks/methods.
A token without a list can call every tool. In a server file or the API, an empty list allows no tool calls. The gateway checks a grant before a request reaches your server or wakes it. This works the same for every kind of server, your own image included.
You can't change a token's grant. Instead, create a token with the grant you want, then revoke the old one.
Traces and health
On a server's page, Request traces lists its newest requests. Each one shows:
the time
the tool called, or the MCP method
the caller: the name of the client token
the duration, highlighted at 1 second or more
the result: OK, Error with the HTTP status, Tool not granted, Timed out, or Blocked at gateway
Blocked at gateway is a request refused for its token: it had no token, an invalid one, or an expired or revoked one. Its caller reads no token or invalid token, or names the expired or revoked token. During a burst of refused requests, only a sample of them is traced, but the blocked count includes them all. Traces record the names of methods and tools, never their arguments or results. sandboxrouter mcp traces <server> prints the newest 100, and sandboxrouter mcp stats <server> the last 24 hours.
MCP servers shows the last 24 hours across all your servers: requests, requests blocked at the gateway, and the cost of the gateway requests. For each server it shows requests, median latency, and the share of requests that failed with a 5xx status.
A running server's status is Healthy, or Slow while its latency is up. A server is slow when both of these hold:
Its median latency over the last hour is at least twice the median of the day before, and at least 500 ms.
The last hour and the day before each had at least 20 requests.
The server's page then names the tool, or the method, with the most slow calls.
For what the server process itself prints, see Runtime output on its page, or run sandboxrouter sandboxes logs <sandbox> with its sandbox's name.
Idle stop and wake
By default, a server's sandbox stops after 15 minutes without activity. You can set from 5 minutes to 4 hours, with --idle-stop, the server file's idle, or sandboxrouter sandboxes update <sandbox> --idle-stop. Each request that passes the gateway's checks and reaches your server resets the timer. A response that's still streaming keeps the server running until the response ends. While the server is stopped, it bills no vCPU, memory or disk, only the storage of its snapshots.
The next message a client sends starts the server again:
The request waits for the sandbox to start, however long that takes. If the sandbox stops again before the request reaches it, the client gets
503withRetry-After: 5, and should retry.Requests refused for their token never start the server, and neither do calls to tools the token isn't granted.
If the server's process exits, the next request starts it again. A server process runs for a day at most, and the next request after that starts it again too.
After a failed start, requests get
503withRetry-Afterfor 1 minute, or for 4 minutes after a second failure in a row. After three failed starts in a row, they get409until you start the server yourself.
A stopped server doesn't keep processes or sessions: what its sandbox keeps across a stop is what its provider keeps, and clients start new MCP sessions.
If you stop a server yourself, requests don't start it again. They get 409 until you start it. You can stop, start or delete a server from its row's menu in MCP servers. With the CLI, stop and start its sandbox, and delete the server:
sandboxrouter sandboxes stop search-mcp
sandboxrouter sandboxes start search-mcp
sandboxrouter mcp delete search-mcp
sandboxrouter mcp delete search-mcp --keep-sandboxDeleting a server ends its endpoint and tokens, and deletes the sandbox its create made, unless --keep-sandbox. A server added to an existing sandbox always leaves the sandbox. Deleting the sandbox deletes its server.
Servers also stop when the workspace's balance reaches $0, and stay stopped until funds arrive. See Billing and usage.
Outbound access
A server reaches only the hosts its sandbox's outbound hosts allow. You set them when you deploy, and you can change them later with Edit on the server page's Outbound policy card, which opens its sandbox's network page. While a fresh workspace sets up, cloning a repository and installing package.json dependencies, the server can also reach the repository's host and registry.npmjs.org. A command that runs npx needs registry.npmjs.org among the hosts itself. To call an API that needs a key, don't put the key in the server. Instead, add the API as a connector whose Reaches picks the server's sandbox (sandboxrouter.io/mcp-server=<server ID>, or a label of your own), and Sandbox Router adds the key to the server's outbound requests.
Sandbox Router's own servers in your sandboxes
The AI tools in your sandboxes can also call MCP servers you don't deploy: each connector's that has one, such as GitHub's or Linear's (see What a sandbox sees), and Sandbox Router's own, browser for the Browser connector, whose tools drive a web browser signed in with the sandbox's user's logins. They take no client token: Sandbox Router adds the sandbox's access to each on its way out, so nothing in the sandbox holds a key, and they serve only your sandboxes, never clients elsewhere.
Change a server
sandboxrouter mcp update renames a server, or changes the port and path it serves on.
sandboxrouter mcp update search-mcp --name search
sandboxrouter mcp update web-tools --http 3001 --path /rpcA server's port and path apply at the next request. A server's sandbox can't change. With the API, PATCH /v1/mcp-servers/<server> with { "name" } or { "transport" }.
Use the API
Every path below is under https://api.sandboxrouter.io/v1, with an API key as Authorization: Bearer <api-key>. <server> is a server's ID or name.
| Request | Does |
|---|---|
POST /mcp-servers | Creates a server: { "name", "sandbox" or "sandboxId", "transport", "tokens" }. Send an Idempotency-Key. |
GET /mcp-servers | Lists servers, newest first, with nextCursor |
GET /mcp-servers/<server> | Reads one: its url, sandbox, transport, state and stats |
PATCH /mcp-servers/<server> | Renames it or changes its transport |
DELETE /mcp-servers/<server> | Deletes it, and its own sandbox unless ?keepSandbox=true |
GET, POST /mcp-servers/<server>/tokens | Lists or mints client tokens |
DELETE /mcp-servers/<server>/tokens/<token-id> | Revokes a token |
GET /mcp-servers/<server>/traces | The newest 100 request traces |
GET /mcp-servers/<server>/stats | The gateway's last 24 hours |
sandbox is a new sandbox's create, exactly as POST /v1/sandboxes takes it. transport is { "http": { "port": 3000, "path": "/mcp" } }, the port the sandbox's server listens on and the path the gateway sends requests to (/mcp by default).
curl https://api.sandboxrouter.io/v1/mcp-servers \
-H "Authorization: Bearer $SANDBOXROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "name": "search-mcp", "sandbox": { "image": "registry.caas.sh/<tenant-id>/search-mcp@sha256:…", "egress": ["api.search.example.com"] },
"transport": { "http": { "port": 8080 } } }'The answer, 201, is the server with tokens, each minted token with its secret, once. A repeat with the same key answers the server without its tokens.
Pricing
The MCP Gateway bills per request. That price includes the endpoint, authentication, routing, persistence and observability. A request with a valid client token counts whatever its result, including a tool call the grant denied. Requests the gateway refuses for their token, their size or the rate limit are free.
The server's sandbox bills like any sandbox. On Native:
| Meter | Price | When |
|---|---|---|
| CPU | On the pricing page | While the server runs, whether it's busy or waiting |
| Memory | On the pricing page | While the server runs |
| Disk | On the pricing page | While the server runs |
| Egress | On the pricing page | Outbound data, including the responses your server sends to clients |
| Snapshot storage | On the pricing page | For each snapshot you keep |
A server that stops when idle pays nothing for CPU, memory and disk while it's stopped. On your own Daytona, Freestyle, Maritime or E2B account, that provider bills the server's compute and egress instead, and the gateway's requests still bill here.
See pricing for every rate, and Billing and usage for how usage draws down the balance.