/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
| Environment | URL |
|---|---|
| Production | https://api.cthree.cloud/mcp |
| Staging preview | https://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
Verification status is recorded after each host is exercised against staging.
| Host | URL-only (OAuth) | API-key header | Verified |
|---|---|---|---|
| Claude Code | yes | yes | API-key header, 2 Sep 2026 (OAuth not yet) |
| claude.ai and Claude Desktop | yes | no (connector settings have no header field) | not yet |
| Cursor | yes | yes | not yet |
| ChatGPT | yes | no | not yet |
| Codex | yes | yes | not yet |
| VS Code | yes | yes | not yet |
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.
| Tool | Mirrors | What it does | Inputs (? = optional) |
|---|---|---|---|
whoami | c3 whoami --json | Show the identity behind the configured credential: user id, email, organisation, permissions, admin flag and C3 access status. | none |
list_hardware | c3 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_route | c3 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? |
balance | c3 balance | Show the credit balance, subscription plan and storage usage for the account. | none |
list_jobs | c3 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_job | c3 squeue <job-id> --json | Show one job in full: status, selected route (provider, region, hardware profile, rate), capacity policy, failure diagnosis and the latest attempt. | job_id |
job_logs | c3 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_job | c3 logs <job-id> -f | Poll 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_job | c3 cancel <job-id> | Request cancellation of a job. | job_id |
deploy | c3 deploy [script] --json [-p <provider>] | Submit a job. | hardware?, gpu?, provider?, regions?, walltime_seconds?, capacity?, project, script?, files?, workspace?, job_name?, datasets?, output?, python_project_dir?, docker_image?, docker_requires_accelerator? |
list_artifacts | c3 pull <job-id> --json | List the files a job produced with sizes and short-lived download URLs (about one hour). | job_id |
read_artifact | c3 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_ls | c3 data ls <path> | List datasets, a dataset's versions, the files in a version, uploaded workspace versions, or a job's artifacts. | path |
data_du | c3 data du <path> | Report storage used by datasets: total bytes across versions, unique bytes after content-addressed dedup, file and version counts. | path? |
data_rm | c3 data rm [-r] [-n] -f <path> | Remove a dataset version or a whole dataset with recursive, cleaning up empty datasets and repointing latest to the newest survivor. | path, recursive?, dry_run? |
data_upload | c3 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_upload | c3 data cp <local-dir> <dest> (upload phase; c3 deploy for a workspace) | For hosts that can run a shell but not the CLI. | target, files, script?, python_project_dir?, docker_image?, docker_requires_accelerator?, output? |
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
Workspace file selection happens on the client host. The hosted MCP server
cannot read your local .c3ignore, .gitignore, or .c3 ignore_file setting.
Apply those rules before sending file contents or digests to a tool. If the
host has the CLI, c3 deploy --list-files --json returns the selected paths
locally without uploading or authenticating. Otherwise, explicitly select the
files needed by the job. The built-in workspace exclusions still apply to
submitted lists. See Workspace upload exclusions
for comment syntax, imports, and .env examples.
The CLI automatically removes api_key from uploaded .c3 copies. Hosted MCP
uploads do not re-encode config files. Inline workspace .c3 files must be
valid YAML without a top-level api_key (including keys supplied by YAML
merges); otherwise the tool rejects the request before storage calls.
Digest-only prepare_upload cannot inspect or redact file contents. Omit .c3 files from MCP workspaces when they are unnecessary,
or prepare credential-free copies before hashing or sending them. Hash and
upload the same prepared bytes; --list-files only lists paths and does not
produce redacted copies.
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#./}"' _ {} \;
The agent calls prepare_upload with target: /projects/<project>/workspace/, the digests and script: run.sh. The result has presigned PUT URLs for only the files storage does not already hold, the manifest, and a shell field that is a complete bash script of curl PUTs (no credential in it). The agent runs that script, then calls deploy with workspace: /projects/<project>/workspace/@<hash>. Lock files, large sources and anything up to 5 GiB per file go this way. Datasets use the same flow with target: /datasets/<name>/.
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:
| Limit | Value |
|---|---|
| Decoded inline payload | 20 MiB |
| Inline files | 500 |
Files per prepare_upload call | 2000, up to 5 GiB each |
| Inline artifact read | 1 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.pyfrom this directory withrun.shas the script, projectmatmul, 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 runs the find/sha256sum one-liner above, calls prepare_upload, runs the returned script with bash, and calls deploy with workspace: /projects/matmul/workspace/@<hash>. Only files not already in storage are uploaded, and the lock file travels as bytes over HTTPS, never through the model.
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
| Symptom | Meaning | Fix |
|---|---|---|
| Host opens a browser login you did not expect | No 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 key | The 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_REQUIRED | The account's email is not verified. | Run c3 verify-email, click the link, retry. |
INSUFFICIENT_CREDITS | The 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_UNAVAILABLE | The requested class or pinned provider has no capacity. | Call list_hardware, choose another class or provider, or set capacity.on_unavailable: "wait". |
CONCURRENCY_LIMIT | Your plan's concurrent job limit is reached. | Wait, cancel_job, or c3 upgrade. |
429 RATE_LIMITED | More than 120 tool calls in a minute from one identity. | Slow the loop; retry after the Retry-After interval. |
WORKSPACE_TOO_LARGE | Inline 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.