WBX Sandbox

SDKs & MCP

Three ways to use WBX Sandbox: the Python SDK for your own code, the MCP server to give Claude and other AI assistants sandboxes without writing code, and the REST API from any language.

Python SDK

A single small package with no dependencies – it uses only the Python standard library (Python 3.9+).

pip install https://sandbox.wbx.cz/download/wbxsandbox-sdk.tar.gz

Configure

Set two environment variables (or pass url= and api_key= to Sandbox.create):

export WBXSANDBOX_URL=https://sandbox.wbx.cz
export WBXSANDBOX_API_KEY=YOUR_API_KEY

The basics

from wbxsandbox import Sandbox

with Sandbox.create(template="base", timeout=300) as sb:
    # run a shell command and wait for the result
    r = sb.run("python3 --version")
    print(r.exit_code, r.stdout)

    # run a piece of code (python, node or bash)
    r = sb.run_code("print(sum(range(100)))")
    print(r.stdout)                                  # 4950

    # files
    sb.files.write("/home/user/input.txt", "hello")
    print(sb.files.read_text("/home/user/input.txt"))
    print(sb.files.list("/home/user"))
# the sandbox is destroyed when the "with" block ends

Reference

CallWhat it does
Sandbox.create(template="base", timeout=300, env=None, metadata=None, internet=True, allow=None, block=None)Start a new sandbox. timeout is its lifetime in seconds. env sets environment variables for every command (handy for API keys). internet=False switches the network off; allow=["pypi.org"] allows only listed domains.
Sandbox.connect(sandbox_id)Attach to a sandbox you created earlier – for example in the next turn of a conversation.
Sandbox.list()Your running and paused sandboxes.
sb.run(cmd, timeout=60, cwd=None, env=None, stdin=None, on_stdout=None, on_stderr=None)Run a shell command and wait. Returns a result with exit_code, stdout, stderr, duration_ms, timed_out. Pass on_stdout to receive output live.
sb.run_code(code, language="python")Run a piece of code; language is python, node or bash.
sb.stream(cmd)Generator of events (start, stdout, stderr, exit) while a command runs.
result.check()Raise an exception unless the command succeeded – sb.run("make").check().
sb.files.write(path, data) / read(path) / read_text(path)Upload and download files (bytes or text). Missing directories are created.
sb.files.list(path) / remove(path)List a directory, delete a file or directory.
sb.set_timeout(seconds)Extend (or shorten) the remaining lifetime.
sb.get_url(port)Public HTTPS URL of a port in the sandbox: url, url_with_token, token (see Public URLs).
sb.pause() / sb.resume()Freeze the sandbox with its memory; you don't pay while it is paused.
sb.kill()Destroy the sandbox now. Leaving a with block does this for you.

Background jobs

For long or many parallel tasks: one call, no open connection, results kept after the sandbox is gone.

from wbxsandbox import run_job, get_job, run_batch

job = run_job(command="python analyze.py", template="base", timeout_seconds=3600,
              save_artifacts=True, max_cost="0.10")
print(job.id)                                   # returns immediately

job = get_job(job.id).wait()                     # later, from anywhere
print(job.summary()["status"])
job.download("output/report.csv", "report.csv")  # checks the SHA-256

batch = run_batch([{"command": f"python worker.py {i}"} for i in range(100)],
                  defaults={"template": "base", "save_artifacts": True},
                  max_concurrency=10, max_total_cost="2.00")
print(batch.wait().summary()["status"])         # completed / partial / failed
CallWhat it does
run_job(command, template="base", timeout_seconds=3600, artifacts=None, save_artifacts=False, env=None, internet=True, allow=None, max_cost=None, max_attempts=1, idempotency_key=None)Submit a job and return a Job right away. save_artifacts=True keeps everything written to ./output/; max_cost (in your billing currency) refuses the job if the worst case could cost more.
get_job(job_id) / list_jobs(status=None)Pick up a job later / your recent jobs.
job.wait(timeout=None) / job.summary()Wait until it ends (server-side, reacts within a second) / short status (state, exit code, cost, file names, last log lines, test results).
job.digest(mode="summary", max_chars=6000) / job.test_results()A compact log for an LLM (test results, errors with context, end of log; mode="errors" or "tail") / recognised test results.
wait_jobs(job_ids, batch_ids=None, until="any", timeout_s=30)Block until any (or all) of several jobs or batches finished.
run_job(..., webhook_url="https://…") / webhook_secret(rotate=False) / verify_webhook(secret, body, header)Signed notification when the job (or batch, run_batch(..., webhook_url=…)) finishes / your signing secret / check a received webhook.
job.follow() / job.log_text()Yield new log text while it runs / the whole stored log.
job.receipt() / job.artifacts() / job.download(name, dest)Execution receipt / result files / download one file (SHA-256 checked).
job.cancel() / job.retry(env=None)Stop it / run it again with the same inputs (pass env again – values are not stored).
run_job(..., compute_class="performance", optimize_for="price", dependency_cache=False, inputs=[job_input(job_id, "output/x.csv")])Compute class (without it your account default), server choice inside the class, package cache, result files of earlier jobs as ./input/.
run_job(wasm_module=bytes, wasm_args=[...], wasm_stdin="", wasm_memory_mb=128)A WebAssembly job (no command or template, no network).
run_batch(jobs, defaults=None, max_concurrency=10, max_total_cost=None, idempotency_key=None)Submit many jobs at once; each item takes the run_job fields plus depends_on and inputs=[batch_input(index, name)]. Returns a Batch.
batch.wait() / batch.summary() / batch.report() / batch.report_csv() / batch.cancel() / get_batch(id)Poll until all jobs end / counts and cost / one row per job / cancel the rest / pick it up later.
get_budget() / set_budget(monthly, daily, alert_pct)Your limits, spend and reservations / set your own limits in your billing currency ("" removes one).
runtimes()Available runtimes with their isolation level and capabilities.
compute_preferences() / set_default_compute_class(name)Classes available to your account with their price, and your default class (applies to new sandboxes and jobs only).

Errors

API errors raise wbxsandbox.SandboxError with status (HTTP status) and message. A command that fails does not raise – check result.exit_code, or call result.check().

from wbxsandbox import Sandbox, SandboxError

try:
    sb = Sandbox.create(template="browser")
except SandboxError as e:
    if e.status == 429:
        print("limit reached:", e.message)   # too many sandboxes for your plan, or no capacity right now
    raise

MCP – connect ChatGPT, Claude and other AI assistants

MCP (Model Context Protocol) lets an AI assistant use external tools. Connect WBX Sandbox and the assistant gets tools to create sandboxes, run commands and code, and read and write files – everything runs on our servers, not on your computer.

The server address is always https://sandbox.wbx.cz/mcp. Assistants that can't send an API key simply ask you to sign in to WBX Sandbox and approve access; this creates an API key named “… (connected app)” in your team. To disconnect, delete that key in API keys. Only team owners and admins can approve.

ChatGPT

  1. In ChatGPT open Settings → Apps & Connectors → Advanced settings and turn on Developer mode (the exact names can differ by plan; workspaces may need an admin to allow it).
  2. Back in Apps & Connectors choose Create, name it “WBX Sandbox” and enter the URL https://sandbox.wbx.cz/mcp. Authentication: OAuth.
  3. ChatGPT opens our sign-in page – sign in and click Allow access.
  4. In a new chat enable the connector and ask, for example: “Create a sandbox and compute the 50th Fibonacci number in Python.”

claude.ai and Claude Desktop

Settings → Connectors → Add custom connector, URL https://sandbox.wbx.cz/mcp. Leave the advanced OAuth fields empty – Claude registers itself, opens our sign-in page and you click Allow access.

Claude Code

With an API key in a header (no browser needed – good for servers and CI):

claude mcp add --transport http wbxsandbox https://sandbox.wbx.cz/mcp --header "Authorization: Bearer YOUR_API_KEY"

Or leave out --header and run /mcp in Claude Code to sign in through the browser.

Other MCP clients (stdio)

Download the small helper program and add it to claude_desktop_config.json:

{
  "mcpServers": {
    "wbxsandbox": {
      "command": "/path/to/wbxsandbox-mcp",
      "args": ["-url", "https://sandbox.wbx.cz", "-key", "YOUR_API_KEY"]
    }
  }
}

Helper downloads: macOS (Apple Silicon) · macOS (Intel) · Windows · Linux

Tools the assistant gets

ToolWhat it does
create_sandboxStart a sandbox from a template
run_commandRun a shell command
run_codeRun Python, JavaScript or bash code
write_file / read_fileUpload and read files
list_filesList a directory
list_sandboxes / list_templatesSee running sandboxes and available templates
set_timeoutExtend a sandbox's lifetime
get_public_urlHTTPS address of a web app or API running in the sandbox
kill_sandboxDestroy a sandbox
run_job / get_job_summaryRun a command as a background job and check on it later (also get_job_result, list_job_artifacts, download_job_artifact, cancel_job, retry_job)
wait_jobsWait until a job or batch finishes – the assistant does not have to keep asking
get_job_logsA compact log digest by default: recognised test results, errors with context and the end of the log (mode=raw for raw bytes)
run_batch / get_batch_summary / cancel_batchRun many jobs at once with a concurrency and price limit, see the aggregated result
get_budgetYour spending limits, spend and reservations
get_compute_classesCompute classes available to you and your default class

If the assistant doesn't pass a sandbox id, the tools use the last sandbox it created in the conversation (and create one when there is none).

Other languages

Everything the SDK does is plain HTTPS with JSON – see the REST API reference. A minimal example in Node.js (18+, no packages needed):

const API = "https://sandbox.wbx.cz";
const headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" };

const sb = await (await fetch(`${API}/v1/sandboxes`, {
  method: "POST", headers, body: JSON.stringify({ template: "base", timeout_s: 300 }),
})).json();

const r = await (await fetch(`${API}/v1/sandboxes/${sb.id}/exec`, {
  method: "POST", headers, body: JSON.stringify({ cmd: "python3 -c 'print(6*7)'" }),
})).json();
console.log(r.exit_code, r.stdout);          // 0 "42\n"

await fetch(`${API}/v1/sandboxes/${sb.id}`, { method: "DELETE", headers });