---
title: "MCP server"
description: "Give an AI agent Glac's products as tools, Cubes to deploy and sandboxes to work in, in Claude Code, Claude Desktop, Cursor or any Model Context Protocol client."
---

> Documentation Index
> Fetch the complete documentation index at: https://glac.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP server

The MCP server lets a model deploy an image as a Cube, or several Cubes together, read their logs and go back to an earlier version; make sandboxes, run commands in them, read and write files, open ports, take snapshots and roll back; make Drives, disks that outlive sandboxes, mount them, keep snapshots of them and back them up; point domains at apps and sandboxes; and look at the project and the account. It keeps no state of its own: everything goes through the [TypeScript SDK](/docs/sdks/typescript/) to the API.

It needs Node 23.6 or later, and runs from npm as `glac-mcp`, which brings the official MCP TypeScript SDK it is built on:

```bash
GLAC_API_URL=https://api.glac.ai GLAC_API_KEY=ak_... npx glac-mcp
```

## Connect a client

Claude Code:

```bash
claude mcp add glac -e GLAC_API_URL=https://api.glac.ai -e GLAC_API_KEY=ak_... -- npx glac-mcp
```

Claude Desktop (`claude_desktop_config.json`; on macOS in `~/Library/Application Support/Claude/`), and Cursor (`~/.cursor/mcp.json` or `.cursor/mcp.json` in a project) take the same entry:

```json
{
  "mcpServers": {
    "glac": {
      "command": "npx",
      "args": ["glac-mcp"],
      "env": { "GLAC_API_URL": "https://api.glac.ai", "GLAC_API_KEY": "ak_..." }
    }
  }
}
```

### Over HTTP

`glac-mcp serve --http` serves the same tools over MCP's Streamable HTTP transport at `/mcp`, for a client that connects to an address instead of starting a process:

```bash
npx glac-mcp serve --http 127.0.0.1:7071
claude mcp add --transport http glac http://127.0.0.1:7071/mcp --header "Authorization: Bearer ak_..."
```

The server holds no key of its own: each request brings an API key as a Bearer token, the server checks it with the API (and takes it as good for 30 seconds), and the tools use that key and no other. A request without a key, or with one the API does not know, is answered `401` with a `WWW-Authenticate: Bearer` challenge that points at the endpoint's protected resource metadata (RFC 9728, at `/.well-known/oauth-protected-resource/mcp`); signing in with OAuth is not offered yet. Every request is served on its own, with no session, so any number of these servers can stand behind one address. It listens on this machine only unless told otherwise; on any other address put TLS in front of it, and set `GLAC_MCP_PUBLIC_URL` to the address clients use.

## Tools

The 73 tools follow what a model needs to do, not the REST calls one by one. They are grouped, and named, by product: Cube (`cube_*`; the same tools answer to `worker_*` too, which are not listed), Sandbox (`sandbox_*`), Drive (`drive_*`), domains (`domain_*`), Browser (`browser_*`), Database (`database_*`), Managed Applications (`catalogue_*`, `installation_*`), then the project (`project_*`) and the account (`account_*`), and listed in that order.

### Cube

Cube runs an image as a service at an address of its own, a Cube, which the API calls an app, and so do the tools' arguments (`app`). These seven need the platform's control plane.

| Tool | What it does |
|---|---|
| `cube_deploy` | Deploys an image to the app of a name, which is made the first time; what holds at once (`public`, `regions`, the scale, `checkpoint_seconds`: [checkpoints](/docs/cube/keyed-instances/#checkpoints) of its keyed instances, 0 for none) is changed first. What holds at once also takes `concurrency`, `depends_on`, `network` (aliases, how it is reached) and `metadata`. A version also takes volumes (under the server's prefix), `kind`, `user`, `working_dir`, `stop_signal`, `stop_grace_ms`, `hostname` and `runtime` (limits, sysctls, tmpfs, its resolver, the size of `/dev/shm`); never a file, which is how an app would be given a secret. Waits for the version for a while, and says whether it is ready, failed (in the image's own words) or still building. |
| `cube_list` | The apps, or one in detail: its versions, where its traffic is, its keyed instances, the secrets its keys are signed with (by their first characters, never a value) and what its log counted. A database's app says which database (`managed_by`), which deploys and deletes it: a deploy or a rollback of it here is refused. |
| `cube_rollback` | Sends an app's traffic back to an earlier version. |
| `cube_metrics` | What the machines that serve an app counted of it, minute by minute or hour by hour: requests, those refused and by the class of their status, p50 and p99, instances awake and stopped, wakes, instances made, CPU and memory; the steps with something in them, and what they add up to. |
| `cube_logs` | Reads an app's log: what its instances printed, its requests, what the platform did; by instance, version, kind, status or text, and on from a cursor. |
| `cube_instance` | Makes or deletes the instance of a key. Moving one to another version in place replaces its machine and ends its connections, and so does its revert: that is a person's to start, cancel and follow (`glac cube upgrade`, `glac cube upgrades revert|cancel`, the SDKs' `upgrade` and `revert`), a batch of them over an app's keys the more so (`glac cube upgrades batch`), and no tool does it. |
| `cube_instance_ports` | The own public IPv6 address of a key's instance and the ports open at it, wherever the instance is: `open` opens a port (TCP, or UDP) to the sources `from` names (IPv6 prefixes or addresses, `::/0` for everybody, which has to be said), `close` closes one. What is open stays open while the instance sleeps (a packet wakes it) and when it moves; a new instance of the key has none of it. The program has to listen on `::`. |

### Sandbox

| Tool | What it does |
|---|---|
| `sandbox_run` | Runs a command and waits. With a `sandbox` name the sandbox is made the first time and kept; without, a one-off sandbox is used and deleted. A sandbox it makes (as `sandbox_exec` makes one) takes a service of its own that starts when it boots (`start`, `start_command`, `ready`), `project_network`, `hostname` and `stop`. A sandbox it makes with `init: "systemd"` is a whole system: the image's own systemd is PID 1 (without `image`, the platform's Ubuntu with systemd and OpenSSH), so packages, services started with `systemctl`, cron and the journal work as on a machine; `sandbox_exec` takes it too, and `sandbox_list` shows each sandbox's `init`. |
| `sandbox_exec`, `sandbox_output`, `sandbox_stdin`, `sandbox_signal` | Starts a long command; reads its output from a position; writes to its input; sends it a signal. |
| `sandbox_read_file`, `sandbox_write_file`, `sandbox_list_files` | Files. Images come back as images; other binary files are only described. |
| `sandbox_url` | A link to a port. |
| `sandbox_ports` | The sandbox's own public IPv6 address, where it has one, and the ports open at it: `open` opens a port (TCP, or UDP) to the sources `from` names (IPv6 prefixes or addresses, `::/0` for everybody, which has to be said), `close` closes one. Inbound is closed by default; what is open stays open while it sleeps (a packet wakes it) and when it moves. The program has to listen on `::`. |
| `sandbox_revoke_tokens` | Revokes every token handed out for a sandbox so far, those in preview links and cookies and those SSH sessions hold too (401 `invalid_token` from then on); tokens made after it work. With `token` (the token, or its id), that token alone. |
| `sandbox_egress` | What a sandbox may reach on the internet: `default`, `allow` and `deny` (default deny with an allow list keeps code you do not trust in), or `clear`. A sandbox named for the first time is made with the rules. The rules that add a secret to requests or name the requests a host may be sent are the project's, and are left alone. |
| `sandbox_checkpoint` | A sandbox's checkpoints while it runs, shown, and set with `every_seconds` (1 to 600; 0 turns them off): when its machine is lost, it goes on elsewhere from its last one with its processes. Off by default; a sandbox named for the first time is made with them. Where no machine takes checkpoints it is refused (`checkpoints_unavailable`). |
| `sandbox_list`, `sandbox_sleep`, `sandbox_delete` | The sandboxes and snapshots, or one in detail (with how its checkpoints go, where it has them); sleep now; delete. |
| `sandbox_snapshot`, `sandbox_fork`, `sandbox_restore` | Snapshot, fork, and back to a snapshot in place. |
| `sandbox_add_ssh_key`, `sandbox_list_ssh_keys`, `sandbox_delete_ssh_key` | An SSH key for one sandbox, for a day unless you say (30 days at most). |

### Drive

A Drive is a disk of the project's that outlives the sandboxes that use it: mounted read-write by one sandbox at a time, and kept, with what it holds, for the next sandbox: of its region, or of another whose machines read its home's object store. A snapshot keeps a Drive as it is at an instant, a version of it in the object store: nothing is copied. A backup is a copy of it at an instant, in the object store, until it expires. These sixteen need the platform's control plane. Behind a prefix, the server's Drives carry it, and it mounts them in its own sandboxes only, so a Drive is made with a name.

| Tool | What it does |
|---|---|
| `drive_create` | Makes a Drive of a name, a size in GiB (64 unless you say, or 5 on Free) and a region, `standard` or `strict` (an fsync in the sandbox that has it returns once the object store holds every write before it; kept for good). Only what is written is billed. |
| `drive_list`, `drive_get` | The Drives, or one in detail: size, what is written, region, the sandbox that has it and where, the node that writes it, its home (the region whose object store holds its copy) and how far a move of it is (`home_move`: a Drive's home follows it to another region an hour after it got there; no tool moves it sooner, `glac drives home` does), the Drive a fork was made of, the database a database's Drive is under (`managed_by`: its instance alone mounts it read-write, and a rename, a restore or a delete of it is refused). |
| `drive_attach` | Mounts a Drive in a sandbox at a path, read-write, at once; one another sandbox has is refused, and the result says which has it. With `snapshot`, one of its snapshots instead, read-only, which any number of sandboxes mount at once. |
| `drive_detach` | Takes it away again (`force` while a process still uses it); it keeps what it holds. With `snapshot`, that snapshot's read-only mount. |
| `drive_delete` | Deletes a Drive and what is on it; one that is mounted is not deleted. Its snapshots stay, and the result names them. |
| `drive_snapshot`, `drive_snapshot_list`, `drive_snapshot_delete` | Takes a snapshot of a Drive (`fs_consistent` freezes the file system of the sandbox that writes it; otherwise its machine is paused for a moment, as at a power cut), lists a Drive's snapshots (without a Drive, those of every Drive, a deleted one's among them), deletes one. |
| `drive_fork` | A new Drive of a name, made of a snapshot or of the Drive as it is now: nothing is copied, and it can be mounted at once. |
| `drive_restore` | Brings a Drive back to one of its snapshots; one a sandbox has is refused: `drive_detach` it first. |
| `drive_versions` | The versions a Drive keeps of itself where its window is set (its `history_seconds`: one every 30 seconds in which it was written), the newest first; with `at`, the one a restore or a fork to that time takes. |
| `drive_backup`, `drive_backup_list`, `drive_backup_get`, `drive_backup_delete` | Takes a backup of a Drive (a sandbox that writes it has its file system frozen for a few milliseconds, so that the copy is at rest), kept until it expires (`expires_in_days`, or the Drive's rule: 30 days unless set); lists the backups of the server's Drives, or of one, newest first, with why each was taken (`manual`; `daily`, by the Drive's rule; `before_version`, before a service's new version); shows one; deletes one. |
| `drive_backup_restore` | A new Drive of a name, made of a ready backup: the size of the Drive it was taken of, in its region, `restoring` until all of it is back from the object store, then `ready` (`drive_get` says which), and mounted by nothing before. Where the machine it is made on cannot bring it back, the platform tries once more on another. |

The model calls a snapshot by its name or its id (`dsnap-…`), always with its Drive: `drive_snapshot` gives no name twice among a Drive's snapshots, and behind a prefix it names each one under the prefix, making a name up (the Drive's and the time) when none is given. A snapshot outlives its Drive: the result of `drive_delete` names those that remain, and they are reached by the Drive's id. A snapshot is billed for what it keeps alone, at the Drives' data price ($0.02 a GB-month), until it is deleted; a fork is billed by all it holds, like any Drive (a fork of a 10 GB snapshot is billed as a 10 GB Drive); what a read-only mount reads from the object store costs nothing. A restore keeps the versions in between where a snapshot or a fork keeps them, so a Drive restored to an older snapshot can be restored to a newer one after; what it held that no snapshot kept is gone.

A backup is not a snapshot: a snapshot is a version of its Drive, while a backup is a copy of its own, called by its id (`bak-…`), with no name, from which a Drive is made alone. A Drive's backups keep only what changed since the one before, and a block several of them need is billed once, on the earliest: what each is billed for (`stored_bytes`), what deleting it would save (`own_bytes`) and what it shares (`shared_bytes`) are in its row. It is `making` until the object store has all of it, then `ready`, and billed from then at the price of what sleeping sandboxes keep ($0.02 a GB-month), until it expires or is deleted. It outlives its Drive: a new Drive is made of it whatever became of the one it was taken of. Behind a prefix, the server sees the backups of its own Drives alone, and names a Drive made of one under the prefix. A Drive whose machine copies nothing to an object store has none: `drive_backup` says so.

### Domains

| Tool | What it does |
|---|---|
| `domain_create` | Registers a name of the user's own for an app of the server's, or for a port of one of its sandboxes; it is `pending_verification` until DNS shows the record the result names (a CNAME, which also leads the name here, or a TXT record), and then its certificate is issued. The user adds the record at their DNS provider. |
| `domain_list`, `domain_update`, `domain_delete` | The domains that lead to the server's apps and sandboxes, with the records that prove each while it is not proven; one pointed at another app, sandbox or port; one deleted. |

### Browser

**Not open yet.** Browser is not open to accounts yet: what follows is the server as it stands, ahead of its opening.

A session is a hosted Chromium, awake while a client of the Chrome DevTools Protocol (CDP) is connected to it and asleep, its pages kept, a few seconds after the last one leaves. The server opens and closes sessions; what is done in the browser is done by Playwright's MCP server, connected at the session's address (`npx @playwright/mcp --cdp-endpoint <cdp_url>`). These three need the platform's control plane. Behind a prefix, the server's sessions carry it in their names: it sees and closes its own alone.

| Tool | What it does |
|---|---|
| `browser_open` | Opens a session (headless unless `headful`), or the one of the name given, woken, with its pages, and gives its CDP address (`cdp_url`) and where a person watches its page live (`live_url`). The address is the credential: whoever has it drives the browser. |
| `browser_list` | The server's sessions: name, kind, whether each is awake (or `failed`, with why: its browser stopped and did not come back) and how many clients are connected. No address. |
| `browser_close` | Ends a session for good: its browser, its pages and its connections. |

A session is billed by the Cube resources it uses, as any instance is: its CPU while it computes, its memory while it is awake and its traffic out ([pricing and plans](/docs/platform/pricing/)). `account_usage` gives a session's use beside its sandbox.

### Databases

A database is a managed Postgres, asleep when nothing asks anything of it and woken by a connection, its sessions kept. The server makes them and branches of them, and looks at them; deleting one or a branch, putting it to sleep, waking it and a new password are a person's (`glac db rm`, `glac db branches rm`, `glac db sleep`, `glac db wake`, `glac db reset-password`). These four need the platform's control plane. Behind a prefix, the server's databases carry it in their names: it sees its own alone.

| Tool | What it does |
|---|---|
| `database_create` | Makes a database (PostgreSQL 18 unless `version`), ready in a few seconds, and gives its connection string with the owner's password: the one time it is given, so the model keeps it where the program that connects needs it (an environment variable such as `DATABASE_URL`). The name of one there is gives that one, without its password. On a Free plan `sleep_after_seconds` is 1, fixed. |
| `database_list` | The server's databases: name, version, status, whether each is awake, paused or asleep, and its connection string without the password. |
| `database_get` | One database and its branches (`main`, then those made of it), each with its endpoint, its state and its connection string without the password; with `branch`, that branch alone. It says the database's rest (how long it is held paused before it sleeps, its daily wake), from when its root branch can be restored, and the gaps of its WAL archive (where the object store refused its WAL so long that it was given up; a time in one is restored only to a whole minute, from its Drive's window): a restore is a person's (`glac db restore`). |
| `database_branch_create` | A branch of a database: a copy of `main` (or of `parent`) as it is now, made at once whatever its size, with a PostgreSQL of its own, to try a migration or a change on the real data without touching it. It gives the branch's connection string without the password: its roles and passwords are its parent's, so the owner logs in with the password `database_create` gave. With `expires_in_hours` it is deleted by itself then; otherwise a person deletes it (`glac db branches rm`). |

A database's history (how far back it can be restored, `glac db update --history`) and a restore of it in place (`glac db restore`: its root branch back to a time, its connections dropped, its state before kept as a branch) are a person's: no tool does either. A Drive's window of versions is in the SDKs and `glac drives versions|history`, not yet among these tools.

A database is reached on the project's network, from a sandbox made with `project_network: true` (and from the project's apps), at its name.

### Managed Applications

An application of the catalogue ([Rivet](/docs/managed-applications/rivet/), say) is installed in the project as its own Cube resources, labelled with the installation and changed through it alone ([Managed Applications](/docs/managed-applications/)). The server looks at the catalogue, installs, and looks at the installations; changing one and deleting one, which takes its Volumes and buckets with what they hold, are a person's (`glac installations update`, `glac installations rm`). These five need the platform's control plane. Behind a prefix, the server's installations carry it in their names: it sees its own alone.

| Tool | What it does |
|---|---|
| `catalogue_list` | The catalogue's applications, each with what it is, its licence, whether the platform packaged it, and what a month of it comes to in Cube's meters. |
| `catalogue_get` | One application (`app`, `version`): the parameters an installation of it takes (a JSON Schema), its outputs, and its month in Cube's meters. |
| `installation_create` | Installs it (`app`, `name`, `version`, `region`, `parameters`, `requires`), waits for it up to the server's wait, and gives its outputs, the secret ones too, which the model keeps where the program that uses it needs them. |
| `installation_list` | The server's installations: application, version, status, region, what each is made of and its outputs. |
| `installation_get` | One installation: its status (and why, where it failed), its apps, Volumes and buckets, and its outputs (a secret one hidden where the server's key may not manage the project). |

### The project and the account

| Tool | What it does |
|---|---|
| `project_get`, `project_set_variable` | The project and its events, each in a few words (among them a Browsers session that failed after it had run; a database made, deleted, branched or restored, its WAL archive failing and archiving again, and a gap in it, opened and closed; and of the others, what each is of), and its shared variables, which an app's env refers to: a control plane's. `project_get` also shows the project's spend limit and what it spent this month, its webhooks, its API keys and where the logs of its apps are exported (never a secret, a key or a header's value; webhooks and keys with a key that has the `admin` scope), which a person sets up: no tool makes or changes them. Asking for one of the account's limits to be raised is a person's too (`glac limits request`). |
| `project_secrets` | The project's secrets: names and hosts only, never values. |
| `project_deploy` | Deploys several of the server's apps together, as one deploy of the project (`/v1/deploys`): each is given what `cube_deploy` takes of a version (`{}`: as it is), and they are taken in the order of what each waits for and switched together. Waits for the deploy a while, and says each app's version and how it stands, or which app it failed at and why. A control plane's. |
| `project_deploys` | The newest deploys of the server's apps; or one, with each app's version, how long each step took and why it failed if it did (`wait`: once it is over). A control plane's. |
| `project_audit` | A page of the project's audit log, the newest first: every call that was no reading, a refused one too, with when, who (a key by its name, a sandbox's token, a user of the console, the platform, an operator), the method, the path, the status and where it came from; and every reading of an app's log by an operator. Takes a key with the admin scope. A control plane's. |
| `account_billing`, `account_usage` | The plan, the bill and the usage: read only. The bill also has what the key's project keeps of volumes and Drives by the numbers a create is held to (`keeps`; a person asks for more), the account's billing address and tax id, and says whether a purchase of the account needs the buyer's express request to have it at once (a consumer's in the EEA or the UK, who may withdraw within 14 days), in the request's words. |
| `account_statement` | A month's statement (`period`, YYYY-MM; this month's unless given): each meter's line, what it comes to at the list prices, what the credit of the plan's periods paid, what is left to pay, the parts of the month, the fees, the Credits debited and written off, what the account held at the month's end, what each project used, and each database's own line (what it used comes to at the list prices, its history and its tiered data apart). Read only. |
| `account_documents` | The invoices at Stripe (with the PDF's link, and of a charge given back, its credit note), the mail the account was sent, and what it may still withdraw from as a consumer in the EEA or the UK, and the withdrawals it made. Read only: a withdrawal is the buyer's own statement, never an agent's (`glac billing withdraw`). |
| `account_settings` | Where the account's notices go, the language they are written in, and its own cap on what it sends to the internet in a month: shown, and changed when the person you work for asks. It says when the address is not verified yet (`email_verified`: no notice goes to it until the link mailed to it is opened) and when mail to it bounced (`email_undeliverable`: none goes to it until the address is set again). |

A few rules the server keeps:

- **State follows the name.** A sandbox the model names stays the model's, with its files, packages and processes, across conversations.
- **A failed command is a result**, not an error: a non-zero exit code, a time-out or a signal comes back as a normal result with the exit code on its first line. A missing sandbox, an API that cannot be reached or wrong arguments are errors, with what to do.
- **Long output is cut in the middle**: at most 16,384 bytes a call, the start and the end, with the call that reads the rest.
- **A call waits at most 45 seconds**; a command still running comes back as `running` with its id, and `sandbox_output` waits on.
- **Paying is a person's job.** There are no tools to top up, change the plan, turn on auto reload, withdraw from a purchase or store a secret; the results say which `glac` command does it. Where the account buys as a consumer in the EEA or the UK, a purchase needs the buyer's own express request to have it at once: that request is theirs to give (`--start-now` on the `glac billing` commands, or the box on the console's billing page), never an agent's, and the commands the results name carry `--start-now` there.
- **Secrets stay out of reach of the model**: an app deployed here (`cube_deploy`, `project_deploy`) gets no secrets or files from this server, its `env` refers to shared variables alone, and an image pull secret goes only to the registry configured for it (`GLAC_MCP_IMAGE_PULL_SECRETS=GHCR=ghcr.io,HUB=docker.io`).

## Settings

| Variable | What it sets | Unless you say |
|---|---|---|
| `GLAC_API_URL`, `GLAC_API_KEY` | The API's address and key | `https://api.glac.ai`; no key |
| `GLAC_MCP_IMAGE` | The image of the sandboxes it makes | The platform's default image |
| `GLAC_MCP_IMAGE_PULL_SECRETS` | Pull secrets the model may name, each with its one registry | None: public images only |
| `GLAC_MCP_PREFIX` | A prefix for the names of everything it makes (sandboxes, snapshots, Drives, apps); it sees only those | None |
| `GLAC_MCP_WAIT_SECONDS` | How long a call waits for a command | 45 |
| `GLAC_MCP_OUTPUT_BYTES` | How much output or file a call returns | 16384 |
| `GLAC_SSH_GATEWAY` | The SSH gateway, for the `ssh` command lines it writes | None |
| `GLAC_MCP_LOG` | `1` logs every tool call on stderr (over HTTP, every request too) | Off |
| `GLAC_MCP_HTTP_ORIGINS` | Over HTTP: the hosts of web pages a browser may call it from, besides this machine | None |
| `GLAC_MCP_PUBLIC_URL` | Over HTTP: its address as clients reach it, named in its protected resource metadata | `http://<listen address>/mcp` |

It is built on the official MCP TypeScript SDK, and speaks the protocol of 2026-07-28 and, for older clients, the `initialize` handshake of 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05: one revision a connection, the one its client opens it with. Not there yet: tools for volumes (the disks underneath Drives), a terminal (pty), a sandbox's own environment, making a sandbox from a snapshot, and signing in with OAuth over HTTP.

Source: https://glac.ai/docs/mcp/index.mdx
