divergent/docs

@divergent/mcp is a second surface for what div 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.

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

Point an MCP client at that command. In Claude Code’s .mcp.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 — 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. A telemetry.toml in the directory lays out its telemetry
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
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: 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 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 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 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, 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.

esc