mola.

mola

Instant computers powered by Omarchy that your AI agents can see, control, and operate.

A full Linux desktop, driven entirely through an API. Each machine runs Arch Linux and Hyprland in its own virtual machine, on hardware you already own. Creating one takes about a second. It accepts commands about seven seconds later.

~
$ npx mola-core

  fetching guest image omarchy-4.0.1-aarch64
  guest image ready

  API    http://127.0.0.1:4141/v1
  Token  7_NmOSph4PWtcZP5QpVW4rJJ-9VIlIx3

$ 
1sto create a machine
7suntil it answers
9things an agent can do
0accounts to sign up for

What you get

Each machine boots Arch Linux with Hyprland under your platform's hypervisor. You get root, systemd, pacman, a compositor, and a browser. Install what you need. If you break the machine, delete it and create another.

The engine clones every machine from one base image using copy-on-write. A new machine therefore costs about a second and a few megabytes rather than a 16 GB copy. Machines share nothing after creation.

The engine listens on loopback only. You do not create an account, and no hosted service sits in the middle. Your data stays on your computer unless you send it elsewhere.

Start

Two commands on macOS. One on Linux, if you already installed QEMU.

Install QEMU

brew install qemu        # macOS, Apple Silicon
sudo pacman -S qemu-full # Linux, and check /dev/kvm exists

Any QEMU build works. QEMU applies the macOS hypervisor entitlement to itself when it builds, so you need no Developer ID and no separate runtime.

Start the engine

npx mola-core

The first run downloads a 1.1 GB guest image and verifies its checksums as the bytes arrive. Expect a few minutes. The engine then prints an address and a token, and stays in the foreground until you stop it.

To check your machine first, run npx mola-core doctor. It tests each requirement and names whatever is missing.

Create a machine

export API=http://127.0.0.1:4141/v1
export TOKEN=$(cat ~/.mola/token)

curl -s -X POST $API/machines \
  -H "Authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name": "scratch"}'

A machine gets 4 vCPU, 4 GB of memory, and a 40 GB disk by default. Set vcpus, memory_mb, or disk_gb to change that. The response returns booting. The machine reaches ready about seven seconds later.

Where the engine stores things

The engine writes its token, keys, guest image, and machine disks to ~/.mola. Set MOLA_HOME to move them. That directory sits outside the installed package on purpose: npx clears its cache without warning, and a machine's disk must outlive the tool that created it.

Control a machine

Send one action per request to POST /v1/machines/{id}/actions. The engine accepts nine.

VerbBodyWhat you get
exec{"command": "ls ~"}exit code, stdout, stderr
read_file{"path": "~/notes.md"}base64 bytes and size
write_file{"path": "~/a", "content": "hi\n"}bytes written
screenshot{}a PNG, base64
click{"x": 640, "y": 400}ok
move{"x": 640, "y": 400}ok
scroll{"direction": "down"}ok
type{"text": "hello"}ok
key{"key": "super-return"}ok

Open a terminal and run a command

curl -X POST $API/machines/$ID/actions -H "Authorization: Bearer $TOKEN" \
  -d '{"action": "key", "key": "super-return"}'

curl -X POST $API/machines/$ID/actions -H "Authorization: Bearer $TOKEN" \
  -d '{"action": "type", "text": "fastfetch"}'

curl -X POST $API/machines/$ID/actions -H "Authorization: Bearer $TOKEN" \
  -d '{"action": "key", "key": "enter"}'

Those keys are Omarchy's own bindings. super-return opens a terminal, super-space opens the launcher, and super-k lists every binding.

Watch the screen

curl -X POST $API/machines/$ID/desktop -H "Authorization: Bearer $TOKEN"

{"data": {"desktop_url": "http://127.0.0.1:4141/desktop#t=...", "expires_in": 60}}

Open that URL in a browser to see the live desktop. The mouse and keyboard work. The ticket travels in the URL fragment, which browsers never send to a server, so it stays out of access logs and referrer headers. Each ticket works once and expires after 60 seconds.

Use it from an agent you already have

The engine ships an MCP server, so Claude Code, Claude Desktop, Cursor and anything else that speaks MCP can drive a machine. You write config, not code.

claude mcp add mola -- npx -y mola-core mcp

Or, for a client that wants JSON:

{
  "mcpServers": {
    "mola": {
      "command": "npx",
      "args": ["-y", "mola-core", "mcp"]
    }
  }
}

Then ask for something that needs a computer.

claude
> make me a linux machine, install neovim, and show me the desktop

  create_machine   ready in 7s
  start_task       pacman -S neovim
  check_task       downloading... installed
  open_desktop     http://127.0.0.1:4141/desktop#t=...

  Done. neovim 0.11.4 is installed. The link opens the live
  desktop, and it expires in a minute.

Seventeen tools: machines, the shell, files and the screen. screenshot hands the agent a picture of the desktop, which is what lets a model that has never heard of Mola look at a window and click the right thing.

Two things the tools get right for you

run_command stops at 120 seconds, so installs and builds go through start_task, which detaches the process and returns a handle to poll. That is also how the agent reports progress instead of going quiet for four minutes.

The engine has to be running. If it is not, the tools say so and name the command, rather than failing in a way that looks like a broken server.

Full MCP setup, including Claude Desktop, Cursor, and what to do when something goes wrong.

API

Send Authorization: Bearer <token> with every request under /v1. GET /v1 returns the endpoint list, so an agent can discover the API itself.

EndpointDoes
POST /v1/machinesmake one and start it
GET /v1/machineslist them
GET /v1/machines/{id}describe one
POST /v1/machines/{id}/startstart a stopped one
POST /v1/machines/{id}/stopshut down, {"force": true} cuts power
POST /v1/machines/{id}/actionsdo something inside it
POST /v1/machines/{id}/desktopa browser URL for the screen
DELETE /v1/machines/{id}destroy it and its disk

Environment

VariableDefaultDoes
MOLA_HOME~/.molatoken, keys, images, machine disks
MOLA_PORT4141listen port
MOLA_QEMUfound on PATHuse a particular QEMU
MOLA_IMAGE_URLGitHub releasewhere the guest image comes from
MOLA_MAX_RUNNING2how many may run at once
MOLA_MAX_MEMORY_MB8192memory they may reserve between them

Machine states

A machine reports one of four states. Two of them need explanation.

StatusMeans
bootingQEMU is running, the guest has not reported in for this boot
readythe guest reported in and its shell works
stoppedoff, disk intact
unknownthe runtime cannot see it

ready does not mean "the process started". QEMU can run while the guest still starts its network or its compositor. The engine waits for a heartbeat from the current boot before it reports ready. Your first command therefore reaches a machine that can answer it.

unknown reports that the runtime cannot see the machine. The engine returns it instead of guessing. A runtime that cannot see a machine has not reported that the machine stopped, and a platform that treats those two answers as the same loses track of machines it still runs.

The engine does not trust the guest

Whoever uses a machine has root inside it. The engine therefore treats everything a guest reports as a claim about itself rather than a fact about the platform. It records the capabilities a guest reports, then decides readiness on its own.

A failed delete keeps the record

A delete that cannot tear a machine down returns 502 and keeps the machine registered, with the reason attached, so you can retry. Dropping the record instead would leave a virtual machine consuming memory that you could no longer name or stop.

Troubleshooting

doctor reports that the host is not ready

Read the line it names. On macOS, run brew install qemu. On Linux, check that /dev/kvm exists and that your user can open it. Inside a cloud VM, your provider must expose nested virtualisation, and many do not.

A machine never reaches ready

tail -40 ~/.mola/runtime/machines/<id>/console.log

HVF does not support GICv2 emulation means your QEMU needs a different interrupt controller. Set MOLA_GIC=3 and create the machine again. QEMU writes its own errors to runtime.log, beside the console log.

Screenshots return scrambled frames

The capture reached the compositor mid-draw. Send an input event first, such as {"action": "key", "key": "super"}, then capture again.

The desktop URL stays on "connecting"

Request a new ticket. Each one works once and lasts 60 seconds. Note that changing only the #fragment of a URL does not reload the page, so pasting a fresh ticket over a spent one in the address bar does nothing. Open the URL in a new tab.

Creating or starting a machine hits a limit

The engine allows 2 running machines and 8192 MB between them. Raise either limit with MOLA_MAX_RUNNING or MOLA_MAX_MEMORY_MB, or stop a machine you are not using. Stopped machines consume disk only and count against neither limit.

A machine appears that you did not create

At startup the engine adopts any machine the runtime holds but the registry does not list, and names it adopted-<id>. A previous engine died before it wrote the record. The machine runs normally, so delete it the usual way.

Build your own guest image

Most people use the downloaded image. Build your own to change the package set, or to avoid the download.

git clone https://github.com/obaid/mola-core
cd mola-core && npm install
python3 bin/native-prepare --output ~/.mola

The script writes ~/.mola/image, where the engine looks for it. If you pass a different --output, set MOLA_HOME to match. The script never overwrites an existing image, so move the old directory aside to rebuild.