# The MCP server

> The same operations div drives, as tools an MCP-capable agent calls directly — runs, evaluations, the registry, storage, secrets, arXiv, the catalog, billing.

`@divergent/mcp` is a second surface for what [`div`](https://docs.divergentlabs.xyz/compute.md) does. It is not a wrapper around the binary — it talks to the control plane directly, the same way `div` does, so an agent with an MCP client skips the subprocess and the exit-code parsing entirely.

```bash
DIVERGENT_API_KEY=dv_live_... node apps/mcp/dist/index.js
```

Point an MCP client at that command. In Claude Code's `.mcp.json`:

```json
{
  "mcpServers": {
    "divergent": {
      "command": "node",
      "args": ["/path/to/divergent-studio/apps/mcp/dist/index.js"],
      "env": { "DIVERGENT_API_KEY": "dv_live_..." }
    }
  }
}
```

## The key

`DIVERGENT_API_KEY` is required. Mint one from the dashboard under Settings, or with [`div login`](https://docs.divergentlabs.xyz/compute/cli/login.md) — both work the same way here, since the server treats it as an ordinary bearer credential. `DIVERGENT_API_URL` defaults to `https://api.divergentlabs.xyz`; set it to talk to a local control plane instead.

There is no login flow in the server itself. `div login` writes a key to `~/.config/divergent/config.json` so a person never has to paste one twice; this server never writes anything to disk, so the process that spawns it is the one holding the secret.

## The tools

| Tool | What it does |
| --- | --- |
| `whoami` | which org this key belongs to |
| `catalog` | what `gpu` takes, the container each comes with, and what an hour of each costs |
| `create_run` | ships a directory to a volume and places a run, with any `checks` its result must pass, and `cpu` and `memory_gib` for a bigger [container](https://docs.divergentlabs.xyz/compute/cli/run.md#the-container). A `telemetry.toml` in the directory lays out its [telemetry](https://docs.divergentlabs.xyz/compute/cli/telemetry.md#laying-it-out) |
| `list_runs` | the org's runs, optionally narrowed to a project or a status |
| `get_run` | one run, by its id or its name, with its checks and their verdicts |
| `get_run_result` | what a finished run wrote to `DIVERGENT_RESULT_PATH`, or the value at one path in it |
| `get_run_telemetry` | what a run has written to `DIVERGENT_TELEMETRY_PATH`: every key with its last, lowest and highest value, or the curve of the keys asked for. See [div telemetry](https://docs.divergentlabs.xyz/compute/cli/telemetry.md) |
| `cancel_run` | stops a run — a no-op, not an error, on one already finished |
| `get_run_logs` | a run's log from `after_seq` on |
| `run_evaluation` | measures a model version for an evaluation — or answers with the run that already did. `suite_paths` says which files are the eval's code, like `--suite-path`; `setup` is what one model's runtime needs, never part of the suite, like `--setup` |
| `list_evaluations` | the org's evaluations, each with the version that leads it |
| `get_evaluation_report` | an evaluation's versions, best first, with earlier suites apart |
| `list_models` | the registry: every model's versions, with where the weights are and what they weigh, like `div model ls` |
| `publish_model_version` | names weights on a volume as `model:tag`, sized on the volume, like `div model publish` |
| `list_volumes` | every volume the org holds |
| `list_volume_files` | what sits directly under a prefix on a volume |
| `storage_put` | uploads a local file or directory to a volume; a file that fails is listed under `failed` and the rest still go |
| `storage_get` | reads one file back — refused over 8MB |
| `list_secrets` | names and timestamps, never a value |
| `set_secret` | creates a secret or replaces its value |
| `delete_secret` | removes a secret |
| `billing_summary` | the org's plan, credit, and spend |
| `search_papers` | searches arXiv, like [`div papers search`](https://docs.divergentlabs.xyz/compute/cli/papers.md): `total` matched, and each paper with its abstract |
| `get_papers` | reads papers by id, like `div papers get`, with the ids arXiv does not have under `missing` |

## `create_run` does not wait

[`div run`](https://docs.divergentlabs.xyz/compute/cli/run.md) ships the directory, places the run, and stays attached until it stops — that is the right shape for a terminal, where someone is watching. `create_run` returns as soon as the run is placed: the id, its starting status, and what was uploaded. A run can run for hours, and a tool call that blocked for all of it would outlast any reasonable timeout.

Poll `get_run` for its status, or `get_run_logs` for its output — pass the highest `seq` you have seen back as `after_seq` to read only what is new, the same reconnect logic [`div logs`](https://docs.divergentlabs.xyz/compute/cli/logs.md) uses:

```
create_run  → {"run": {"id": "run-k4t9", "status": "queued", ...}, "uploaded": {...}}
get_run     → {"status": "provisioning", ...}
get_run_logs(after_seq: 0)  → [{"seq": 1, "message": "queued on H100", ...}, ...]
get_run_logs(after_seq: 1)  → [{"seq": 2, ...}, ...]
get_run     → {"status": "succeeded", "terminal": true, ...}
```

## What a file comes back as

`storage_get` decides the same way the dashboard's file explorer does: it tries to decode the bytes as UTF-8, and returns `{"encoding": "utf8", "text": "..."}` when that works or `{"encoding": "base64", "base64": "..."}` when it does not — never by looking at the extension. Anything over 8MB is refused rather than returned, with a pointer to [`div storage get`](https://docs.divergentlabs.xyz/compute/cli/storage.md) instead.

## Secrets are still write-only

`list_secrets` prints names and timestamps and nothing else — there is no tool, and no route on the API, that returns a value. `set_secret` takes the value as a plain argument, unlike [`div secrets set`](https://docs.divergentlabs.xyz/compute/cli/secrets.md), which reads it from stdin so it never sits in shell history. An MCP tool call has no shell history to sit in, but it does travel through the calling client's own conversation, so treat it with the same care you would a credential typed into a chat.
