MCP server#

TaskCloud provides a Model Context Protocol server. With it, an agent such as Claude Code, Claude Desktop, Cursor or Codex can create sandboxes, run commands, edit files, expose ports and drive a desktop, with no glue code.

Mode How Auth
Local (stdio) npx -y @taskcloud/mcp or taskcloud mcp env TASKCLOUD_API_KEY (optional TASKCLOUD_API_URL)
Self-hosted (Streamable HTTP) npx -y @taskcloud/mcp --http --port 8787, or the Docker image Authorization: Bearer <api key> on every request
Hosted (Streamable HTTP), beta, coming soon https://mcp.task.cloud/mcp Authorization: Bearer <api key> on every request

Every mode calls the public REST API, so each tool call is subject to the scopes of the API key you use. The hosted endpoint runs the same server as the self-hosted HTTP mode; it is not live yet. During the private beta @taskcloud/mcp is shared with beta participants directly; publication on npm is coming soon.

Use a restricted key#

Don't give an agent your admin key. Create a key limited to sandbox work and, optionally, to sandboxes with a given label:

curl -X POST https://api.task.cloud/v1/api-keys \
  -H "Authorization: Bearer $TASKCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "mcp-agent", "preset": "full-sandbox", "restrict": {"labels": {"owner": "mcp"}}, "expires_in_days": 30}'

Sandboxes created with this key automatically receive the label owner=mcp, and the key can only see and act on sandboxes with that label.

Setup#

Claude Code#

Local (stdio):

claude mcp add taskcloud --env TASKCLOUD_API_KEY=$TASKCLOUD_API_KEY -- npx -y @taskcloud/mcp

Remote (Streamable HTTP), against your own server or, once it is live, the hosted endpoint:

claude mcp add --transport http taskcloud http://127.0.0.1:8787/mcp \
  --header "Authorization: Bearer $TASKCLOUD_API_KEY"

# hosted (beta, coming soon)
claude mcp add --transport http taskcloud https://mcp.task.cloud/mcp \
  --header "Authorization: Bearer $TASKCLOUD_API_KEY"

Claude Desktop / Cursor#

Add the local server to the client's MCP configuration (claude_desktop_config.json, or .cursor/mcp.json):

{
  "mcpServers": {
    "taskcloud": {
      "command": "npx",
      "args": ["-y", "@taskcloud/mcp"],
      "env": { "TASKCLOUD_API_KEY": "taskcloud_sk_..." }
    }
  }
}

Cursor can also connect to an HTTP server with a static header:

{
  "mcpServers": {
    "taskcloud": {
      "url": "http://127.0.0.1:8787/mcp",
      "headers": { "Authorization": "Bearer taskcloud_sk_..." }
    }
  }
}

Claude Desktop's remote connectors sign in with OAuth, which the HTTP mode does not support yet (see Authentication). Use the local server there for now.

VS Code#

.vscode/mcp.json:

{
  "servers": {
    "taskcloud": {
      "type": "http",
      "url": "http://127.0.0.1:8787/mcp",
      "headers": { "Authorization": "Bearer ${input:taskcloud-key}" }
    }
  },
  "inputs": [{ "id": "taskcloud-key", "type": "promptString", "description": "TaskCloud API key", "password": true }]
}

Codex and other clients#

Codex (~/.codex/config.toml) reads the key from an environment variable:

[mcp_servers.taskcloud]
url = "http://127.0.0.1:8787/mcp"
bearer_token_env_var = "TASKCLOUD_API_KEY"

Any other MCP client works if it supports either stdio (run npx -y @taskcloud/mcp with TASKCLOUD_API_KEY in its environment) or Streamable HTTP with a custom Authorization header.

Self-hosting the HTTP server#

npx -y @taskcloud/mcp --http --port 8787                       # listens on 127.0.0.1:8787, endpoint /mcp
npx -y @taskcloud/mcp --http --host 0.0.0.0 --read-only        # all interfaces, read-only tools

The server has no API key of its own. Each MCP request must send Authorization: Bearer <api key>, and the tools of that request call the API with that key only. TASKCLOUD_API_KEY in the server's environment is ignored in this mode, so two users of one server never share a key. The server only checks that the header is present. The API decides whether the key is valid, so a revoked key gets tool results with isError: true and 401 unauthorized.

Docker#

The image is built from the SDK sources (build context sdks/):

docker build -f sdks/mcp/Dockerfile -t taskcloud-mcp sdks
docker run --rm -p 8787:8787 taskcloud-mcp                     # add flags after the image name, e.g. --read-only

The container listens on 0.0.0.0:8787, runs as a non-root user and has a HEALTHCHECK on /healthz. Because it binds to all interfaces, the Host check is off unless you pass --allowed-hosts (or TASKCLOUD_MCP_ALLOWED_HOSTS) with the host name clients use. Put it behind a reverse proxy that terminates TLS, because API keys travel in a header.

Configuration#

Flag Environment Default Meaning
--http off (stdio) Serve MCP over Streamable HTTP
--port N PORT 8787 Listen port (0 picks a free port)
--host H HOST 127.0.0.1 (0.0.0.0 in Docker) Bind address
--read-only off Only the 5 read-only tools
--tools a,b all 15 Tool allowlist
--max-body-bytes N 1048576 Larger request bodies get 413
--request-timeout S 660 Deadline for one MCP request, in seconds; a request over it gets 504 (exec waits up to 600 s)
--allowed-origins a,b TASKCLOUD_MCP_ALLOWED_ORIGINS none Browser origins that may call /mcp; a request with any other Origin header gets 403
--allowed-hosts a,b TASKCLOUD_MCP_ALLOWED_HOSTS localhost, 127.0.0.1, [::1] when bound to loopback; no check otherwise Host header values the server answers (DNS rebinding protection); any other Host gets 403. Set it to your public host name when you bind to 0.0.0.0 (Docker)
TASKCLOUD_API_URL https://api.task.cloud/v1 API base URL

Endpoints and behavior#

  • POST /mcp: MCP messages (Streamable HTTP, protocol versions up to the current spec). The server is stateless: it issues no Mcp-Session-Id, every request is handled independently, and replicas behind a load balancer need no sticky sessions. Responses are plain JSON, not SSE streams.
  • GET /mcp and DELETE /mcp return 405, because a stateless server has no server-initiated stream and no session to end. Clients handle this per the spec.
  • GET /healthz: {"status":"ok", ...} for liveness and readiness probes.
  • Per-request narrowing: /mcp?read_only=1 and /mcp?tools=exec,read_file shrink the tool list for that connection. They never widen what the server-wide --read-only or --tools allows.
  • Timeouts: 15 s to receive headers, 30 s to receive the whole request, then the per-request deadline above.
  • Logs: one JSON object per line on stderr, with request_id (taken from X-Request-Id or generated, and echoed in the response), status, duration, JSON-RPC methods, tool names and key_fp, the first 12 hex characters of the key's SHA-256. API keys, tool arguments and tool output are never logged.

Authentication#

Today the only credential is a TaskCloud API key in the Authorization header. MCP's OAuth 2.1 flow (protected resource metadata, browser sign-in, audience-bound tokens) is planned for the hosted endpoint. It needs an authorization server on the TaskCloud side, which does not exist yet. Until then, clients that only support OAuth for remote servers should use the local stdio server.

Tools#

Tool Key parameters API endpoint
create_sandbox template, size, name, labels, auto_pause, always_on, priority POST /sandboxes
list_sandboxes state, label, limit, cursor GET /sandboxes
get_sandbox sandbox_id GET /sandboxes/{sandbox_id}
pause_sandbox sandbox_id POST /sandboxes/{sandbox_id}/pause
resume_sandbox sandbox_id POST /sandboxes/{sandbox_id}/resume
fork_sandbox sandbox_id, name, labels, count (1–50, all-or-nothing; >1 returns {"data": [...]}), priority POST /sandboxes/{sandbox_id}/fork
delete_sandbox sandbox_id DELETE /sandboxes/{sandbox_id}
exec sandbox_id, command, cwd, timeout_seconds, background POST /sandboxes/{sandbox_id}/exec
read_file sandbox_id, path GET /sandboxes/{sandbox_id}/files/content
write_file sandbox_id, path, content PUT /sandboxes/{sandbox_id}/files/content
list_files sandbox_id, path, recursive GET /sandboxes/{sandbox_id}/files
expose_port sandbox_id, port, visibility POST /sandboxes/{sandbox_id}/ports
desktop_stream_url sandbox_id, ttl_seconds, read_only POST /sandboxes/{sandbox_id}/desktop
screenshot sandbox_id, scale, region GET /sandboxes/{sandbox_id}/screenshot (returned as an MCP image)
computer_input sandbox_id, actions (same shape as input actions) POST /sandboxes/{sandbox_id}/input

Behavior notes:

  • Truncated output. exec in sync mode returns exit_code, stdout and stderr. Output larger than what fits in a tool result is truncated, and the tool says where the full log can be read (GET .../commands/{command_id}/stream).
  • Text files only. read_file and write_file handle text. For binary files, use the REST API or signed URLs.
  • Wake on request. Tools that need a running VM wake a paused sandbox automatically.

Limiting what the agent can do#

The server accepts flags that remove tools from the list the agent sees:

npx -y @taskcloud/mcp --read-only                      # only list_sandboxes, get_sandbox, read_file, list_files, screenshot
npx -y @taskcloud/mcp --tools create_sandbox,exec,read_file,write_file,list_files,expose_port

For example, --tools without delete_sandbox stops the agent from deleting sandboxes. --read-only and --tools can be combined. API key scopes still apply.

The same flags work with --http. Over HTTP a client can also narrow its own tool list with ?read_only=1 or ?tools=a,b in the endpoint URL, for example https://mcp.task.cloud/mcp?tools=exec,read_file once the hosted endpoint is live.

Docs for agents#