DOCSMCP servers
DOCS

MCP servers

caas.sh runs your MCP server in its own sandbox and serves it at an authenticated endpoint, https://mcp.caa.sh/<endpoint-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 can run:

  • A stdio command on the Default image (Alpine Linux with bash, coreutils, Node and Docker). This is the same command your MCP client runs locally, such as npx search-mcp. caas.sh's bridge serves it to the gateway over HTTP.

  • Code from a Git repository, cloned into /workspace and started with a stdio command.

  • Your own image from registry.caas.sh, speaking MCP over stdio, or over HTTP on a port.

Sandboxes run on a prepaid balance, so add funds before you deploy.

Deploy from the console

  1. Open MCP servers and choose Deploy server.

  2. Under 1. Choose an image, choose Default or Your image.

    With Default, enter the server's Command, such as npx search-mcp. To run code from a repository, also enter its Repository, and its Branch if it isn't the default one. caas.sh clones the repository into /workspace and installs its package.json dependencies before the command runs. If the command runs your own files, such as node server.js, and you don't give a repository, attach a Source archive: a .tar.gz of those files.

    With Your image, enter the image's digest reference, registry.caas.sh/<tenant-id>/my-server@sha256:…, with the digest that docker push prints. Command is optional. It replaces the image's entrypoint and command. Keep Speaks MCP over stdio checked for a stdio server. For a server that serves MCP over HTTP, uncheck it and enter the Port it listens on.

  3. Under 2. Configure, you can enter a Name: lowercase letters, digits and hyphens, unique in the workspace. 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.

  4. In Allowed outbound hosts, list the hosts the server calls, one per line. Everything else is blocked. If the command starts with npx, add registry.npmjs.org so it can download the package.

  5. Leave Keep state between restarts checked to keep /workspace between restarts, with nightly snapshots. A server that runs a source archive must keep state.

  6. 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 caas CLI with an API key, as The CLI describes.

A stdio command

shell
caas mcp deploy --stdio "npx -y @modelcontextprotocol/server-filesystem /workspace"

When the command starts with npx, the CLI allows registry.npmjs.org for you. Add every other host the server calls with --egress-host, once per host:

shell
caas mcp deploy --stdio "npx search-mcp" --name search-mcp --egress-host api.search.example.com

To run your own code, run the deploy at the root of its Git repository and add --from .. The CLI uploads the files committed at HEAD, so it refuses if you have uncommitted changes or untracked files, and it never uploads ignored files. If the code has a package.json, the sandbox installs its dependencies before the command runs. Upload your code lists the other rules.

shell
caas mcp deploy --stdio "node server.js" --from .

A Git repository

shell
caas mcp deploy --repo github.com/acme/server --ref main --command "node server.js"

On the first start, caas.sh clones the repository into /workspace and installs its package.json dependencies. Then it runs --command as a stdio server, or npm start if you leave --command out.

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. The CLI resolves a tag to its digest and deploys that digest.

A stdio server runs behind caas.sh's bridge, which runs on Node, so the image needs Node:

shell
caas mcp deploy --image server:1.2.3

For a server that serves MCP over Streamable HTTP, give the port it listens on. The gateway sends requests to the path /mcp on that port.

shell
caas mcp deploy --image server:1.2.3 --transport http --port 3000 --command "node http.js"

The port must be from 1024 to 65535, other than 8788. --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.

Options

OptionWhat it does
--name NAMEA name unique in the workspace: lowercase letters, digits and hyphens.
--size SIZExs, s, m (the default), l, xl, 2xl, or custom with --vcpu, --memory and --disk. See Sandboxes.
--egress-host HOSTA host the server may reach. Repeat it for each host.
--idle-stop MINUTESStop after this many minutes without activity, from 5 to 240. The default is 15.
--wake-on-requestLet a request start the server even after you stopped it yourself.
--ephemeralKeep nothing in /workspace between restarts, and take no automatic or nightly snapshots. Can't be combined with --from.

The CLI prints the new server, including:

  • id: the server's sandbox ID, which the token commands take.

  • endpointUrl: the endpoint.

  • clientToken: the secret of the Initial client token. This is the only time it's shown.

From a server file

One YAML or JSON file can declare a whole server, client tokens included:

yaml
server: search-mcp
source:
  stdio: npx search-mcp
size: m
state:
  persistent: true
  snapshots: nightly at 02:00
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]
shell
caas mcp deploy -f search-mcp.yaml
FieldValue
serverRequired. The server's name, unique in the workspace.
sourceExactly one of three: stdio: COMMAND; repo: URL, with optional ref and command; or image: IMAGE, with optional command, and transport: http with port for an HTTP server.
sizeA size name, xs to 2xl, or "<vCPU> vCPU · <GiB> GiB · <GB> GB". caas.sh rounds a written size up to one it can run, and the CLI prints a note when it does.
statepersistent: true (the default) or false, and snapshots: nightly at HH:MM or off.
idleHow long the server runs idle before it stops, such as stop after 30m or stop after 2h: 5 minutes to 4 hours.
tokensUp 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).
egressThe hosts the server may reach. A stdio command that starts with npx also gets registry.npmjs.org.

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 uploads 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 --from . for a stdio source with persistent: true, and --wake-on-request. The other deploy options can't be combined with -f.

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, and a command that runs your own files can't be uploaded. Deploy such a file with the CLI.

Connect a client

You can find the endpoint URL in three places:

  • under Endpoint on the server's page

  • in the CLI's endpointUrl

  • in endpointUrl in the API's view of the server

The endpoint ID is opaque: it doesn't reveal your workspace or the server's ID.

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:

json
{
  "mcpServers": {
    "search-mcp": {
      "url": "https://mcp.caa.sh/<endpoint-id>",
      "headers": { "Authorization": "Bearer $CAAS_MCP_TOKEN" }
    }
  }
}

Put the client token in place of $CAAS_MCP_TOKEN, unless your client fills in environment variables there. In Claude Code, for example:

shell
claude mcp add --transport http search-mcp https://mcp.caa.sh/<endpoint-id> --header "Authorization: Bearer <client-token>"

To check the endpoint from a terminal, send an initialize request:

shell
curl -i https://mcp.caa.sh/<endpoint-id> \
  -H "Authorization: Bearer $CAAS_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 Origin header other than https://mcp.caa.sh gets 403, so a web page on another site can't call the endpoint from a browser. MCP clients that aren't browsers send no Origin.

What the stdio bridge supports

The bridge starts one process of your command for each MCP session and ties the session to the client token that opened it. A session ends when the client closes it, or after 30 minutes without requests. Requests that use the 2026-07-28 protocol without a session go to one process per client token. Clients that share a token share that process. The bridge runs up to 32 of these processes at once.

Your command must write only JSON-RPC messages to stdout, one per line. The bridge stops a process that writes anything else, or a line over 4 MiB. Write logs to stderr instead. When the server stops running, the end of its stderr shows under Runtime output on its page.

The bridge answers each request with one JSON response and carries no server-sent stream. Messages your server sends on its own, outside a response, don't reach the client. The bridge doesn't support 2026 discovery, subscriptions/listen or multi-round-trip input requests. If your server needs these, serve MCP over HTTP from your own image.

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. caas.sh stores only a hash of it.

  • A deploy from the console's form, or with --stdio, --repo or --image, creates one token, Initial client. It can call every tool and never expires.

  • A deploy from a server file creates the tokens its tokens list 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:

  1. On the server's page, under Client tokens, choose New token.

  2. Enter a Client name. Under Allowed tools, list the tools it may call, one per line, or leave it empty to allow every tool.

  3. Choose Create token, and copy the token. It expires in 90 days.

With the CLI:

shell
caas mcp tokens create <sandbox-id> --name "Claude Desktop"
caas mcp tokens create <sandbox-id> --name ci-pipeline --expires-days 30 --allow-tool search.query
caas mcp tokens list <sandbox-id>
caas mcp tokens revoke <sandbox-id> <token-id>

<sandbox-id> is the server's ID. It's id in the deploy output, the first column of caas sandboxes list, and ID on the server's page. --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 and expired tokens are deleted 30 days later.

With the API, a token can expire up to a year ahead, or never:

shell
curl https://api.caas.sh/v1/tenants/<tenant-id>/sandboxes/<sandbox-id>/client-tokens \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"label": "ci-pipeline", "allowedTools": ["search.query"]}'

The response holds the secret and the token. expiresAt takes a time in milliseconds since the Unix epoch. Without it, the token never expires. A GET to the same path lists the tokens, and a POST to .../client-tokens/<token-id>/revoke 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 gets 403, 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. They're kept for 7 days.

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 and Log archives on its page, or run caas sandboxes logs <sandbox-id>.

Idle stop and wake

By default, a server stops after 15 minutes without activity. You can set from 5 minutes to 4 hours, with --idle-stop or the server file's idle. 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 up to a minute for the sandbox to start. If it's still starting after that, the client gets 503 with Retry-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 503 with Retry-After for 1 minute, or for 4 minutes after a second failure in a row. After three failed starts in a row, they get 409 until you start the server yourself.

A stopped server doesn't keep processes or sessions. If you kept state, /workspace is restored, but clients start new MCP sessions. On a stdio server, a request for a session the bridge doesn't know gets 404, which tells an MCP client to start a new session.

If you stop a server yourself, requests don't start it again. They get 409 until you start it, unless you deployed it with --wake-on-request. You can stop, start or delete a server from its row's menu in MCP servers, or with the CLI:

shell
caas sandboxes stop <sandbox-id>
caas sandboxes start <sandbox-id>
caas sandboxes delete <sandbox-id>

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 outbound policy allows. You set these hosts when you deploy, and you can change them later under Outbound policy on the server's 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 in the policy itself. To call an API that needs a key, don't put the key in the server. Instead, have caas.sh add the key to the server's outbound requests. Credentials and outbound access shows how.

Pricing

The MCP Gateway bills $0.005 per 1,000 requests, which is $5 per million. 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:

MeterPriceWhen
Active vCPU$0.15 / vCPU-hourOnly while computing
Memory$0.018 / GiB-hourWhile the server runs
Disk$0.001 / GB-hourWhile the server runs
Egress$0.06 / GBOutbound data, including the responses your server sends to clients
Snapshot storage$0.025 / GB-monthFor each snapshot you keep

For example, an M server (1/2 vCPU, 4 GiB of memory, 8 GB of disk) costs $0.08 for each hour it runs: 4 × $0.018 for memory plus 8 × $0.001 for disk. On top of that it pays for the vCPU it uses: up to $0.075 for an hour of full use. 100,000 requests through the gateway add $0.50. A server that stops when idle pays nothing for memory and disk while it's stopped.

See pricing for every rate, and Billing and usage for how usage draws down the balance.