Using Symbioza through MCP

Run GPU training jobs from Claude Code, or any MCP client.

From SSH key to finished artifacts in two steps: connect, then submit and collect. Estimates and status checks are free; money moves only at submitJob.

Before you connect

Step zero, do it now: send the output of cat ~/.ssh/id_ed25519.pub to access@symbioza.dev. Access is by invitation, one customer at a time — nothing below works until that key is enrolled. Then two things happen on the operator’s side:

your SSH public key is enrolled on the control plane as a forced-command entry — it can run the MCP server and nothing else;
you get back two lines to paste into the block below: HostName and User. Nothing else in it changes. That key is your credential — over ssh there is no second secret to paste.
No key yet? ssh-keygen -t ed25519 makes one; send the .pub half only.
Step 1

Connect

Your MCP client launches ssh as the server transport and speaks JSON-RPC over the pipe. Two config blocks, then one command to prove it: ssh symbioza should connect and sit there waiting for JSON-RPC. Ctrl-C out — that silence is success.

Your agent connects over ssh to symbioza-mcp, which enqueues the job and returns an id; the always-on daemon rents, drives and reaps the GPU box. your agentsymbioza-mcpdaemonrented GPU SSH · JSON-RPCENQUEUE · IDRENT · DRIVE · REAP

Your session only enqueues and returns an id — the always-on daemon owns the run. Dotted = per-session; solid = always-on.

~/.ssh/config
Host symbioza
    HostName <SYMBIOZA_HOST>
    User symbioza
    IdentityFile ~/.ssh/<YOUR_KEY>
    IdentitiesOnly yes
    StrictHostKeyChecking accept-new
    BatchMode yes
.mcp.json — in your project root · restart Claude Code, then /mcp should list symbioza with five tools
{
  "mcpServers": {
    "symbioza": {
      "command": "ssh",
      "args": ["-o", "BatchMode=yes",
               "symbioza",
               "/srv/symbioza/bin/symbioza-mcp"]
    }
  }
}
First connect fails with “Too many authentication failures”? That is a full ssh-agent, not a bad key — IdentitiesOnly yes in the block above fixes it.
First connection learns the host key — accept-new trusts it on first use; verify out-of-band with ssh-keyscan if you’d rather.
You do not keep the connection open while the job runs — the session only enqueues; an always-on daemon owns the run.
Step 2

Submit, disconnect, collect

You submit one JSON object, a TrainingJobSpec. The contract is strict: unknown fields are rejected, so a typo can never silently fall through to a default. Put your own success check inside the command — your exit code is the verdict.

Declare your data under datasets. Two mechanisms exist: "transferMode": "https", where the rented box fetches the file itself from a URL you can sign — use it for large or public data — and "push", where the control plane streams a file you staged with us over ssh, resumable. Either way the sha256 you declare is verified before your command starts, and the file lands at destination inside the container. Nothing on your laptop is ever on the data path.

spec — the required fields, plus datasets
{
  "image": "ghcr.io/you/train:latest",
  "command": ["python", "train.py",
              "--epochs", "40"],
  "gpu": { "minVramGb": 24, "cuda": true },
  "budgetUsd": 12,
  "maxRuntimeSeconds": 43200,
  "checkpoint": { "enabled": true },
  "datasets": [{
    "source": "https://<signed-url>/train.tar",
    "sha256": "<sha256>", "sizeBytes": 41000000000,
    "destination": "/data/train.tar",
    "transferMode": "https"
  }]
}
1
estimateExecution(spec)sanity-check the cost — free
2
submitJob(spec)→ { executionId, status: "queued" } in seconds
3
save the executionId · disconnectsymbioza owns the run now
4
getStatus(executionId)progress · spend · incidents — any session
5
getArtifact(executionId)exitCode · finalCostUsd · manifest · a link per file
6
download each file · verify its sha256links are valid 24 h; ask again for a fresh set

Status is one of queued · running · completed · failed · cancelled · lost. The id is durable — reconnect tomorrow in a fresh session and ask again. If the box dies, the run restarts on another machine under the same ceiling; the checkpoints already streamed off it are kept. Automatic resume from those checkpoints is next. Artifacts tier to offsite storage the moment the record is written; getArtifact returns a presigned download link per file, each already re-hashed against its recorded sha256.

Never send

A GPU-provider API key · the Symbioza control-plane key · any SSH private key. Those stay on the control plane. If an onboarding step asks you for one, stop and report it.

When something goes wrong
rejected: <reason>Budget too low, spec invalid, or no suitable hardware. Read reason; fix the spec or raise the budget.
unauthorizedOnly on a non-ssh transport: the authKey is wrong or absent — the same message for every tool, by design.
validation error: <field>A field is missing, misspelled or mistyped — or you sent an unknown key. Match the contract exactly.
status: failedgetStatus().incidents[] and the exit code say why. Infra failures were already retried or moved to another box; a workload failure is your code.
status: lostA control-plane restart interrupted the run; the ledger shows what was charged up to that point and nothing after. Resubmit; tell the operator if it recurs.

Errors are plain messages with a next step, not stack traces.

Ready when you are.

Send your SSH public key to access@symbioza.dev — it gets enrolled, your host entry arrives out-of-band, and the first estimate is one call away.

Back to the overview