Skip to main content
Are you a large language model? This page is available as raw markdown at /mcp.md. The full docset is at /llms-full.md and the index is at /llms.md.

MCP server

C3 hosts a Model Context Protocol (MCP) server so coding agents can use C3 directly. An agent connected to it can list hardware and prices, deploy a job to a chosen GPU class or provider, wait for it, read its logs, and collect its results. Nothing needs to be installed on the machine running the agent.

The MCP server is a third thin client next to the CLI and the web dashboard. Every tool mirrors one c3 command, calls the same API the CLI calls with the same credential, and returns cli_equivalent, the exact c3 invocation that reproduces what it did. If you can do it with c3, an agent can do it through MCP; if c3 refuses, so does the tool, with the same error.

Endpoint​

EnvironmentURL
Productionhttps://api.cthree.cloud/mcp
Staging previewhttps://test.api.cthree.cloud/mcp

The setup examples below use the production URL; substitute the staging URL to try a change before it reaches production. The transport is Streamable HTTP. Every tool call is a self-contained request; there are no sessions to keep alive.

Two ways to authenticate​

URL only (OAuth). Give your host just the URL. On first use it receives a login challenge, discovers C3's Auth0 tenant from the endpoint's metadata, registers itself, and opens a browser login. Tokens refresh automatically. Use this on your own machine.

API key (header). Create a key with c3 apikey create <name> or on the dashboard settings page and send it as Authorization: Bearer c3_key_.... No browser is involved. Use this for headless agents, CI, or hosts that cannot run a browser. A configured key never triggers a login; a revoked key returns a 403 so you see the problem instead of a login prompt.

Both paths give the agent exactly the access the same credential has in the CLI, including the verified-email requirement and any account restriction.

Host setup​

HostURL-only (OAuth)API-key header
Claude Codeyesyes
claude.ai and Claude Desktopyesno
Cursoryesyes
ChatGPTyesno
Codexyesyes
VS Codeyesyes

These are host authentication capabilities, not a verified C3 support matrix. Only Claude Code with an API-key header has been exercised against staging (2 September 2026); OAuth and the other hosts remain unverified.

Claude Code​

URL only:

claude mcp add --transport http c3 https://api.cthree.cloud/mcp
# then, inside a session, run /mcp and choose "Authenticate" for c3

API key:

claude mcp add --transport http c3 https://api.cthree.cloud/mcp \
--header "Authorization: Bearer c3_key_..."

Headless use works with the API-key form, for example claude -p "List the hardware C3 offers and deploy run.sh on an l40".

claude.ai and Claude Desktop​

Open Settings → Connectors → Add custom connector, enter https://api.cthree.cloud/mcp, and complete the login when prompted. Connectors use OAuth only.

Cursor​

Add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global). Omit headers to use OAuth instead of a key.

{
"mcpServers": {
"c3": {
"url": "https://api.cthree.cloud/mcp",
"headers": { "Authorization": "Bearer c3_key_..." }
}
}
}

ChatGPT​

Enable developer mode under Settings → Connectors, choose Create, and enter https://api.cthree.cloud/mcp. ChatGPT connectors use OAuth only.

Codex​

Add to ~/.codex/config.toml. For OAuth omit bearer_token_env_var and run codex mcp login c3.

[mcp_servers.c3]
url = "https://api.cthree.cloud/mcp"
bearer_token_env_var = "C3_API_KEY"

Set C3_API_KEY in the environment Codex runs in.

VS Code​

Add to .vscode/mcp.json. Delete the headers block to use OAuth.

{
"inputs": [
{ "id": "c3-key", "type": "promptString", "description": "C3 API key", "password": true }
],
"servers": {
"c3": {
"type": "http",
"url": "https://api.cthree.cloud/mcp",
"headers": { "Authorization": "Bearer ${input:c3-key}" }
}
}
}

Tools​

Each tool description begins with the c3 command it mirrors. Results always include cli_equivalent. Money-moving commands (c3 topup, c3 upgrade), API-key management, and operator commands are deliberately not exposed; when a job needs credits the error names c3 topup for the user to run.

ToolMirrorsWhat it doesInputs (? = optional)
whoamic3 whoami --jsonShow the identity behind the configured credential: user id, email, organisation, permissions, admin flag and C3 access status.none
list_hardwarec3 list --json [--class <class>] [--provider <id>] [--all]List the hardware classes C3 can run jobs on with C3-billed prices and a coarse live availability signal.class?, provider?, all?
preview_routec3 deploy --dry-run --json [-p <provider>]Show how a deploy with these selectors would route (provider, region, hardware profile, hourly rate, availability) without creating a job or reserving credit.hardware?, gpu?, provider?, regions?, walltime_seconds?, capacity?, project?
balancec3 balanceShow the credit balance, subscription plan and storage usage for the account.none
list_jobsc3 squeue --json [-n <history>]List jobs the way c3 squeue does: every active job (PENDING, SCHEDULING, RUNNING) followed by the most recent terminal jobs (history of them, default 10).status?, history?, limit?, offset?, hardware_profile?
get_jobc3 squeue <job-id> --jsonShow one job in full: status, selected route (provider, region, hardware profile, rate), capacity policy, failure diagnosis and the latest attempt.job_id
job_logsc3 logs <job-id> [-v]Return one page of log lines for the job's latest attempt with a cursor for the next page.job_id, cursor?, limit?, all_streams?
wait_for_jobc3 logs <job-id> -fPoll a job every 2 seconds until it reaches a terminal state (SUCCEEDED, FAILED, CANCELED, TIMED_OUT) or the timeout elapses (default 30s, maximum 50s).job_id, timeout_seconds?
cancel_jobc3 cancel <job-id>Request cancellation of a job.job_id
deployc3 deploy [script] --json [-p <provider>] [--storage <mode>]Submit a job.storage?, hardware?, gpu?, provider?, regions?, walltime_seconds?, capacity?, project, script?, files?, workspace?, workspace_publication_id?, job_name?, datasets?, output?, python_project_dir?, docker_image?, docker_requires_accelerator?
list_artifactsc3 pull <job-id> --jsonList the files a job produced with sizes and short-lived download URLs.job_id
read_artifactc3 pull <job-id>Return one artifact's content inline (text, or base64 for binary) when it is at most 1 MiB; larger files return the size and a download URL only.job_id, path
data_lsc3 data ls <path>List datasets, a dataset's versions, the files in a version, uploaded workspace versions, or a job's artifacts.path
data_duc3 data du <path>Report storage used by datasets: total bytes across versions, unique bytes after content-addressed dedup, file and version counts.path?
data_rmc3 data rm [-r] [-n] -f <path>Remove a dataset version or a whole dataset with recursive, repointing latest to a surviving version when needed.path, recursive?, dry_run?
data_uploadc3 data cp <local-dir> /datasets/<name>/Create a new version of a dataset from inline files (at most 20 MiB decoded and 500 files; for anything larger, hosts with a shell use prepare_upload and hosts with the CLI use c3 data cp).dataset, files
prepare_uploadc3 data cp <local-dir> <dest> (upload phase; c3 deploy for a workspace)For shell-capable hosts without the CLI.storage?, target, transfer?, files?, script?, python_project_dir?, docker_image?, docker_requires_accelerator?, output?
complete_uploadc3 data cp <local-dir> <dest> (completion phase; c3 deploy for a workspace)Finish or check a retained owned upload prepared by prepare_upload.target, transfer, parts?
ar_deployc3 ar deploy <script> --json [-p <provider>]Start an autoresearch run from an experiment folder: script (run.py, which calls autoresearch.run(...) on the C3 research controller) plus the program being improved and its evaluator.script, workspace?, files?, project?, name?, hardware_profile?, provider?, limits?, datasets?, python_project_dir?, docker_image?, docker_requires_accelerator?
ar_list_runsc3 ar squeue --json [-n N]List the authenticated organisation's research runs, newest first, with cursor pagination.limit?, cursor?
ar_get_runc3 ar squeue <run-id> --jsonShow one autoresearch run in full (status, engine, script, project, limits, usage, result, error) together with its progress steps and a count of steps by status.run_id
ar_cancel_runc3 ar cancel <run-id>Cancel an autoresearch run: queued evaluations are dropped and running evaluation jobs are stopped.run_id

Two read-only resources are also served in-band: c3://docs/mcp (this page) and c3://docs/cli-reference (the reference printed by c3 docs).

Workspaces are data​

File selection happens on the host. The server cannot read your .c3ignore or .gitignore, so apply them before sending contents or digests. With the CLI installed, c3 deploy --list-files --json returns the selected paths locally. The built-in exclusions still apply. See What gets uploaded.

The owned shell helper prepares private .c3 copies using the CLI’s typed configuration rules, removes api_key, and hashes/uploads only the prepared bytes. Your originals remain unchanged. This step requires local PyYAML 6.0.3; install it explicitly in your Python environment before preparation. The helper never installs dependencies or sends configuration values through MCP. Missing YAML support or ambiguous configuration stops before uploading. Inline uploads reject .c3 files containing api_key; legacy digest uploads still require you to prepare credential-free copies or omit them.

A workspace is the code and configuration a job needs: the submission script, a .c3, a pyproject.toml, your source files. C3 stores it the way it stores a dataset, as content-addressed blobs plus a manifest, and c3 deploy is that upload followed by a job that points at the manifest hash. Data belongs in datasets (mounted with datasets:), environments are rebuilt from pyproject.toml or a Docker image, and caches are excluded. Uploaded workspace versions are visible with data_ls /projects/<project>/workspace/.

There are two ways to get a workspace to C3 from an agent, chosen by what the host can do:

Hosts with a shell (Claude Code, Cursor, Codex, VS Code)​

Never paste file contents into a tool call. Hash the files, send the digests, run the returned script, deploy the returned version:

# 1. digests only (paths, sha256, size); the model never sees the bytes
find . -type f -not -path './.git/*' -exec sh -c 'printf "%s %s %s\n" "$(sha256sum "$1" | cut -d" " -f1)" "$(stat -c %s "$1")" "${1#./}"' _ {} \;

Call prepare_upload with target: /projects/<project>/workspace/, script: run.sh and file digests. Datasets use target: /datasets/<name>/. The server selects the configured storage protocol.

When owned storage is enabled, the result contains helper_script, a Python 3 helper, and a preparation object. Workspace .c3 preparation requires PyYAML 6.0.3 in that Python environment. Save the result privately as preparation.json, save the helper as c3-owned-upload.py, then run:

python3 c3-owned-upload.py build --plan preparation.json --state .c3-owned-upload.json
# For a large manifest, keep every {path, sha256, size_bytes, mode?} locally,
# one JSON object per line, and add: --digests all-digests.jsonl

The helper prints the next MCP call. Execute it and save each returned part window as window.json, then run python3 c3-owned-upload.py upload --state .c3-owned-upload.json --plan window.json. Repeat the emitted calls. The helper streams file ranges to presigned URLs, records each acknowledgement privately, and submits the original manifest after its files. API authentication stays in MCP; the helper takes no API, publisher or machine credential.

complete_upload submits the exact acknowledgements and checks platform verification. Keep the same transfer identity while the state is publication_pending or manifest_pending; neither state means the workspace is ready. Save a published file result or committed manifest result as result.json, then run python3 c3-owned-upload.py record --state .c3-owned-upload.json --plan result.json. Deploy only the final returned workspace together with workspace_publication_id. Dataset mounts can retain the exact publication_id alongside their reference.

After an interrupted PUT, repeat prepare_upload with the retained transfer to reconcile native part acknowledgements. The helper refuses blind replay of an uncertain part. For an expired or explicitly retried window, python3 c3-owned-upload.py renew --state .c3-owned-upload.json emits a new bounded window request for the same upload. Preserve the journal and original file bytes until publication completes. Configured server object, manifest and lifetime policies still apply; multipart transport does not raise release limits.

If the initial server capability selects legacy storage, the result instead contains the existing shell curl script. Run it before deploying the returned workspace reference. Legacy preparation supports at most 2000 files and 5 GiB per file. An owned transfer never falls back to this flow.

Hosts without a shell (claude.ai, ChatGPT)​

These hosts only have the small scripts the agent wrote, so deploy and data_upload accept the files inline: a list of { path, content } entries, encoding: "base64" for binary files, executable: true for scripts. The standard exclusions apply. The limit is what a Worker request can hold:

LimitValue
Decoded inline payload20 MiB
Inline files500
Owned prepare_upload digest transport page2000 entries / 1 MiB; larger manifests use local JSONL, with total policy enforced by the server
Owned part windowAt most 25 signed parts per call
Legacy prepare_upload2000 files, up to 5 GiB each
Inline artifact read1 MiB (larger files return a download URL)

Whichever path is used, the workspace manifest hash is identical to what c3 deploy produces for the same prepared file bytes and metadata, including credential-free .c3 copies.

Worked examples​

Run a script on an H100 on a named provider​

Ask the agent to preview first, then deploy:

Preview an h100 route on nebius, then deploy train.py from this directory with run.sh as the script, project matmul, and wait for it.

The agent calls preview_route with hardware: "h100", provider: "nebius", then deploy with the files, script: "run.sh", project: "matmul", the same selectors, and finally wait_for_job. The deploy result carries the CLI form, for example:

cli_equivalent: c3 deploy run.sh --json -p nebius
c3_config:
project: matmul
script: run.sh
hardware: h100
provider: nebius

Deploy a real repository from Claude Code without the CLI​

Hash this directory, prepare the upload for project matmul, run the upload script, then deploy the returned workspace on an h100 and wait for it.

The agent hashes the selected files, calls prepare_upload, and follows the returned protocol through publication. For owned storage, it retains the helper journal and uses complete_upload, then passes both workspace and workspace_publication_id to deploy. The lock file travels as bytes over HTTPS, never through the model; an existing hash alone never proves ownership.

Follow a job and read its results​

wait_for_job returns after at most 50 seconds (under the tool timeout hosts enforce) with terminal: false if the job is still running; the agent calls it again. job_logs returns a page of lines and a cursor; passing the cursor back returns only new lines. When the job succeeds, list_artifacts lists the output files with download URLs and read_artifact returns a small result file (a metrics JSON, say) inline.

Upload a dataset and mount it​

data_upload with dataset: "my-data" and a few files creates a new version. A later deploy mounts it with datasets: [{ ref: "/datasets/my-data", mount: "/data" }], exactly as datasets: in .c3 would.

Troubleshooting​

SymptomMeaningFix
Host opens a browser login you did not expectNo credential was sent. This is the OAuth path working as designed.Complete the login, or configure an API key header if you want no browser.
403 Invalid or revoked API keyThe c3_key_ header is wrong or the key was revoked.Create a new key with c3 apikey create <name> and update the host.
EMAIL_VERIFICATION_REQUIREDThe account's email is not verified.Run c3 verify-email, click the link, retry.
INSUFFICIENT_CREDITSThe balance cannot cover the job's reservation.Run c3 topup <amount> or top up on the dashboard. The agent cannot do this for you.
GPU_OUT_OF_STOCK or PROVIDER_UNAVAILABLEThe requested class or pinned provider has no capacity.Call list_hardware, choose another class or provider, or set capacity.on_unavailable: "wait".
CONCURRENCY_LIMITYour plan's concurrent job limit is reached.Wait, cancel_job, or c3 upgrade.
429 RATE_LIMITEDMore than 120 tool calls in a minute from one identity.Slow the loop; retry after the Retry-After interval.
WORKSPACE_TOO_LARGEInline payload over 20 MiB or 500 files.With a shell, use prepare_upload; with the CLI, run c3 deploy from the project directory.

Tokens issued by the OAuth login expire; hosts holding a refresh token renew silently. If a host shows 401 invalid_token and does not recover, remove and re-add the server to trigger a fresh login.