# EMT Compute Python SDK, CLI and MCP

Python 3.9+, no third-party dependencies. Production: https://emtcompute.com.
Download [the SDK](https://emtcompute.com/sdk/emt_compute.py) and
[the MCP server](https://emtcompute.com/sdk/emt_mcp.py) into the same directory.
The [OpenAPI contract](https://emtcompute.com/openapi.json) describes customer REST operations.

## Connect

For the hosted **Streamable HTTP MCP server** use `https://emtcompute.com/mcp`
and an `Authorization: Bearer <EMT API key>` header in a compatible client.
It exposes 17 tools for GPU/cluster catalogs, quotes, rentals, scientific presets,
experiment jobs, cancellation and private report links. Cookie authentication and
OAuth are not supported by this endpoint. Paid tools require explicit
`confirm_paid_rental: true` and a persisted `idempotency_key`.

The [n8n integration](https://emtcompute.com/integrations) includes a downloadable
[standard-node workflow](https://emtcompute.com/integrations/emt-compute-n8n.json),
tested in n8n 2.46.5. It defaults to quote-only mode and checks expiry and the rental
ceiling (`hourly × hours`, in micro-USD). Select an HTTP Header Auth credential on
every HTTP node. Never embed a real key in exported JSON.
Retries must preserve both the exact launch payload and key; a new workflow run
creates fresh quotes, so resume a known job with `existingJob` or retry the failed
launch node using its saved execution data. Retained storage is billed separately.
The local stdio server below remains available with its 18 infrastructure tools.

Set `EMT_BASE_URL=https://emtcompute.com` and supply `EMT_API_KEY` through your
client's secret manager. Never put keys in source code, notebooks or public workflows.
Create a workspace key in the EMT console, with only the needed scopes and a budget/expiry.
Public catalog, environments and model metadata do not need a key. Private reads need
`read`; GPU quotes and rentals need `rent`; lifecycle changes need `manage`;
billing operations can require `billing`. Training preparation uses `manage`.
Accept current terms in the console before paid use; API keys cannot accept them for you.
Read `/api/v1/health` and authenticated `/api/v1/me` for current readiness and account requirements.
Feature availability is enforced by the server and can differ between accounts.

```python
from emt_compute import EMTCompute
client = EMTCompute()  # reads the environment; never hard-code a key
catalog = client.catalog()
print(len(catalog.get("nodes", [])))
```

```sh
python3 emt_compute.py ls --cuda 13 --nvlink --tier secure
python3 emt_compute.py clusters
python3 emt_compute.py quote NODE_UUID --gpus 8 --hours 24
python3 emt_compute.py monitor ORDER_UUID --member POD_UUID
python3 emt_compute.py backups ORDER_UUID
python3 emt_compute.py upload ORDER_UUID ./model.safetensors --remote /mnt/models/model.safetensors
python3 emt_compute.py download ORDER_UUID /mnt/checkpoints --local ./checkpoint-copy
```

SCP transfers use the local `scp` executable. The SDK accepts only the SSH host/port,
rejects executable SSH options, and preserves host-key checking. A download destination
must be new; an upload can overwrite the chosen remote path.

`rent` supports `--volume`, `--backups` and `--ports`, and needs a fresh quote, template
and SSH **public** key. Persist `--idempotency-key` for rentals, volumes and manual backups.
After a timeout, inspect the operation and reuse that key rather than creating a duplicate.
A quote does not start a rental. A successful stop request does not by itself prove that
release and final settlement have completed: inspect the order. Persistent storage can
continue billing after a GPU stops. Prices are micro-USD unless explicitly stated otherwise.

## MCP stdio

Start with `python3 /ABSOLUTE/PATH/emt_mcp.py`. Configure that command and your environment
variables in an MCP-compatible client. The server offers 18 tools for catalog, quotes,
rentals, clusters, monitoring, logs, volumes, costs, backups and termination.
It uses JSON-RPC over stdin/stdout; **it is not a hosted HTTP MCP endpoint**.
Experiment and guided-training REST routes are not currently exposed as MCP tools.

The server negotiates initialization before tool calls. Destructive tools require explicit
acknowledgements. Paid operations require the client's spending approval; tool annotations
are hints, not authorization. Use a read-only key for discovery-only agents.
The protocol has automated tests; compatibility with every MCP client is not implied.

## Guided language-model training

CLI commands: `training-model MODEL`, `training-upload DATA.jsonl`,
`training-prepare CONFIG.json --dataset ID --name NAME --idempotency-key KEY`,
`training-jobs`, `training-job ID`, `training-events ID`, `training-recipe ID`,
`training-stop ID`, `training-resume ID`, `training-start ID --request REQUEST.json`.

Methods: `lora`, `full`, `scratch`. Scratch initializes random weights from the pinned
architecture and tokenizer. Upload and preparation do not rent hardware. Managed start
requires service availability, a fresh GPU quote, an owned persistent volume, an SSH public
key and storage-charge acknowledgement. The immutable recipe and cumulative GPU budget
are preserved across attempts; resume needs confirmed closure of the previous rental and
the same volume. Storage is separate. A manually started `runner.py` does not terminate
its GPU rental. Checkpoints are under `/mnt/emt-training/JOB/checkpoint-STEP/model`.

## Scientific experiment REST workflow

Use `client.request()` with the OpenAPI schemas for preset simulations, customer scripts,
parameter sweeps and checkpoint continuation. This workflow can also be built with an HTTP
client or an automation platform; do not put your EMT key in a public browser application.

1. POST `experiment-quotes` with GPU count, node count and maximum minutes. This only quotes.
2. Review `hourly`, `reserve`, `expires` and `ids`. Select the preset/configuration and prepare inputs.
3. With spending approval, POST `experiment-jobs` using those `ids`, a name, experiment and runs,
   plus a persistent `Idempotency-Key`. This starts paid GPU resources.
4. GET `experiment-jobs/{id}` for progress, result indices and GPU release state.
5. Download compressed JSON from `experiment-jobs/{id}/results/{index}` and ZIP files from
   `experiment-jobs/{id}/outputs/{node}`. Use a binary-capable HTTP client: `request()` decodes JSON.
6. POST `experiment-jobs/{id}/cancel` to stop early, then verify release. Optional `auto` continuation
   can rent subsequent segments within the configured budget and maximum segment count.

Multiple nodes distribute independent sweep runs; they do not make every solver distributed.
Uploads are limited to 50 MiB/file and output archives to 95 MiB/node. Simulation results and
numerical consistency checks are not physical validation or certification of a real system.

## Maintainer checks

```sh
python3 -m unittest discover -s tests -p '*_test.py'
node --import tsx scripts/sync-openapi.ts
node scripts/prepare-sdk.mjs
```

Protocol references: [lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)
and [tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).

## Computation reports and customer explanations

New GPU experiment runs produce a separate report ZIP at
`GET experiment-jobs/{job}/reports/{index}`. The job's `reports` array lists available indices.
It contains printable offline HTML (browser Print → Save PDF), Markdown, a JSON computation
record and CSV for every recorded numerical series. The record includes parameters,
configuration, seed/application status, runtime build, package versions, node allocation,
input/output SHA-256 manifests, results and numerical checks. Original uploaded inputs are
available through `GET experiment-jobs/{job}/inputs/{index}`. The full numerical result stays
at the existing `results/{index}` endpoint; saved output archives include the complete node log.

A report records execution and declared checks. It does not infer a mathematical proof from
arbitrary code or prove that every operation ran on a GPU. Intermediate tensors that were
never saved cannot be reconstructed. Old jobs do not acquire invented retrospective records.

Python SDK experiments can document actual calculated steps as they run:

```python
energy = 0.5 * mass * velocity ** 2
ctx.explain("Kinetic energy", "E = m * v^2 / 2",
            inputs={"mass": mass, "velocity": velocity},
            result=float(energy), unit="J",
            explanation="Substitute the measured mass and velocity in the kinetic-energy formula.")
```

Any other program can write equivalent JSON to `$EMT_CALCULATION_JSON`, for example
`{"steps":[{"title":"Kinetic energy","equation":"E=m*v^2/2","inputs":{"m":2,"v":3},"result":9,"unit":"J"}]}`.
Keep this declaration below 512 KiB; write large derivations/tables in `$EMT_OUTPUTS`.
Missing explanations are labelled as not supplied; they never become fabricated derivations.
Report archives are limited to 8 MiB per run and output archives to 95 MiB per node.

## Experiments on a dedicated rented cluster

All preset, Python-SDK and command experiments can distribute independent parameter runs
across an owned running cluster of 2–4 nodes. Prepare at least one parameter set per node.
This mode does not automatically turn a serial solver into an MPI/NCCL distributed solver.
The normal GPU/node rental mode remains available for single runs and checkpoint continuation.

1. Rent a cluster from the EMT catalog with your SSH public key; wait until every member is running.
2. In the experiment configurator choose **Use an already rented cluster**, select it and acknowledge
   whole-cluster shutdown. The cluster must be dedicated to this experiment: its other processes will
   stop when the experiment ends or is cancelled. Rental charges continue during preparation.
3. Prepare the experiment. The API equivalent is POST `experiment-jobs/cluster` with the ordinary
   experiment fields, `clusterOrder`, `maxMinutes`, `releaseCluster: true` and a persistent idempotency key.
4. Save `emt_cluster.py` and `emt_compute.py` in the same folder. Set `EMT_API_KEY` locally to a workspace
   key with read/rent/manage scopes; do not put the key in the command or a published workflow.
5. Run `python3 emt_cluster.py --job JOB_UUID --identity /path/to/private-key`. The connector uses OpenSSH
   and Docker inside the cluster nodes, with host networking, an isolated workspace and the selected
   scientific image. Verify the SSH host fingerprints through your trusted connection information.
   Your SSH private key stays local; nodes receive only the experiment-scoped token.
6. Monitor the experiment and download each run's report and each node's output ZIP. All nodes are
   released together after the work finishes. If a node cannot start, cancel the experiment and check
   `releasing` and the original order; partial termination is never treated as a stopped cluster.

The connector's startup is repeatable while its containers are running; a finished container is
not silently restarted. Keep the original job ID after a lost response. Automatic checkpoint-chain
continuation is not offered for attached clusters; only the first node's checkpoint is retained.

Provider networking reference: [Lium multi-node clusters](https://docs.lium.io/pod-users/clusters).
