Download MLX models on your Mac and serve them to the rest of your network — an OpenAI-compatible endpoint, in a menu-bar app.
Gropius turns one Apple Silicon Mac into a shared local-inference server. Browse and download MLX models from HuggingFace, and serve them over an OpenAI-compatible API to every other machine and user account on your network. Anything that talks to ChatGPT can talk to your Mac — just change the base URL.
It manages its own runtime: on first launch it installs a private Python and MLX
under ~/Library/Application Support/Gropius and never touches your system
Python. Uninstalling is one command, which takes the firewall entry with it and
leaves the models you downloaded.
Experimental. Runs and is tested end-to-end on macOS 26 / Apple Silicon. Cross-machine LAN use works; TLS and notarised distribution are not yet included.
- Model browser — search the
mlx-communityorg, download with live progress, resume interrupted transfers. - OpenAI-compatible server —
/v1/chat/completions,/v1/completions,/v1/models, streaming included. Drop-in for any OpenAI SDK. - Context window published — the models list gives each model's maximum context, so a client can size its prompts instead of discovering the limit by failure (reference).
- Model kind published — each model carries HuggingFace's own pipeline tag
and tags, recorded when it was downloaded, plus a
chatflag from a rule you set: a chat client can offer only the models that can hold a conversation while every model stays callable by name (how to, reference). - Residency published — with an API key set, the models list also says which models are loaded, how busy each one is and when it was last used, so a client picks the warm model instead of triggering a load (reference).
- Runs many models — one process per model, with an LRU memory budget so a request for a second model evicts an idle one instead of OOMing the machine. The budget is yours to set: give a dedicated Mac most of itself, or a shared laptop less (how to).
- Pinned models — the models you rely on stay in memory: never evicted to make room, never reaped by the idle timeout (how to).
- Eviction grace, opt-in — on a Mac two people share, a model that answered a moment ago is not torn out for the next request: that request waits a bounded time for something to fall idle, and is told that it waited (how to, why).
- Sampling defaults — one place to say what an omitted
temperaturemeans for the whole machine, with an optional override per model; a request that sets its own value still wins. - Request statistics, opt-in — off until you turn it on: a content-free record of each request's model, token counts and timings, shown per model in the control panel and kept on this Mac. The records are plain text, one line of JSON each, kept for as many months and as many megabytes as you say (how to switch it on). The Statistics tab also reads those records back as history: tokens per day by model with each model's share, how long requests took, the spread of those times, and when models were evicted and reloaded (what the views mean).
- Self-test, opt-in — off until you turn it on: while the Mac is idle, Gropius loads each of your models in turn, runs the same short set of tests against it — load time, prompt reading, generation, and generation under concurrent requests — writes one line of figures to a file in your data folder, and unloads what it loaded. A request from anyone ends the run at once (how to switch it on, and what is recorded).
- Context-window probe, opt-in — off until you turn it on: while the Mac is idle, Gropius measures the largest prompt each model will actually take, through its own endpoint, and records it beside the window the model declares and the one it serves. A figure one of Gropius's own limits stopped is published as a floor, and nothing changes until you adopt it (how to switch it on, and what it costs).
- A log that says why — Gropius keeps its own log in your account's data folder, so the reason behind a refusal a client saw is somewhere you can read it even when the app was launched from the Finder and has no terminal to print to. Sparse by default, one line per event that mattered; switch it to detailed while you are diagnosing something and it adds the figures behind each line. Neither level writes a prompt, an answer, a key or the address of the client that sent a request (reference).
- Network-shared — bind the LAN, discoverable over Bonjour (the chat client lists the servers it finds, so nobody has to guess an address), optional API key. A mesh VPN reaches it from further away, with the same steps and a different address (how to) — or bind that network alone, so the local one cannot reach the server at all. Every bind includes this Mac, so narrowing one never costs you the control panel (what each choice binds).
- One page that says what is on — the control panel's Posture tab states, as fact rather than warning, who can reach the server and by which addresses, what a request has to carry from the network and from this Mac, what is announced over Bonjour, what the request log writes down, and what is recorded and for how long — and says where Gropius's own view stops (what each line is read from).
- It installs, updates, repairs, removes and diagnoses itself —
gropius installdoes the provisioning in the foreground and, run again, repairs what is missing rather than reinstalling what is not.gropius updatefetches the current release, verifies it against the published checksums, swaps it in and reports two facts under two labels — the version installed and the version this Mac is serving — saying plainly when the second cannot be known rather than printing the first in its place.gropius uninstallremoves the app, the runtime, the settings and the firewall entry every hand-written instruction forgets, and leaves the models you downloaded with their size and the flag that removes them too.gropius doctorseparates what it verified from what it could only observe — the firewall entry is never a verdict — and says outright that Local Network Privacy cannot be determined from here, rather than guessing it.gropius config showprints every setting in force, with the API key and the HuggingFace token masked (how to, reference). - Multi-account — other user accounts on the same Mac share one copy of each model on disk and on the GPU. The models are shared; each account keeps its own settings and its own model list, so no key or token crosses accounts.
One line — installs Gropius.app (the menu-bar server) to /Applications, or to
your own ~/Applications when your account is not an administrator, allows it
through the firewall, and launches it:
curl -fsSL https://raw.githubusercontent.com/intentdriven/Gropius/main/install.sh | bashThat command is a bootstrap and only a bootstrap. It downloads the release,
verifies it against the checksums published beside it, clears the quarantine
attribute, and hands over to gropius install inside the bundle it has just
verified — so the script and the binary it calls are always the same build.
The binary does the rest in the foreground, where you can watch it: it places
the application, asks once for the firewall grant, installs the private Python
and MLX runtime with a proportion rather than a spinner, links a gropius
command into ~/.local/bin, and opens the app
(how to, reference).
The server needs administrator rights once, to allow itself through the macOS firewall so other machines can reach it. It asks through the standard macOS authentication panel: this account does not have to be an administrator — the panel takes an administrator's name and password, so someone else can enter theirs. Decline it and the install finishes without the grant, printing the two commands that make it by hand; the server then answers on this Mac and other machines see an empty response. The bundle is ad-hoc signed, so its identity changes with every build and the firewall grant has to be made again on each update. The chat client needs no administrator rights at all.
That command and a direct download of the current release are also on the project's page at https://intentdriven.sh/Gropius, which is where someone who is not building from source starts. The page names the release GitHub flags as latest, lists every file it carries with its size, and links the checksums to verify a download against; the release run renders it from the release itself, so no one edits the page to keep it current.
Requires macOS 26. Both apps declare that minimum and the installer checks
it, so an older Mac is turned away before anything is installed. The server
needs Apple Silicon (MLX runs on Metal). For the native chat client
(GropiusChat.app, universal — it runs on any Mac that runs macOS 26, Intel
included, and talks to a server over the network):
curl -fsSL https://raw.githubusercontent.com/intentdriven/Gropius/main/install.sh | bash -s -- clientThe installer verifies the download before installing it: it fetches the
checksums file published on the same GitHub release and refuses anything that
does not match. Every release asset also carries a GitHub build-provenance
attestation binding it to the release workflow run; check it yourself with
gh attestation verify:
gh attestation verify Gropius.app.zip --repo intentdriven/GropiusThere is no offline signing key; building from source is the escape hatch. The
binaries are ad-hoc signed, not notarised; because the installer has verified
the download, it clears the Gatekeeper quarantine so it launches without a
prompt. The MLX runtime installs while gropius install runs, in the terminal
and with a proportion (a few minutes); a launch from the Finder finishes what
that run could not and shows progress in the control panel. Then you can
download and serve models. New here? See
docs/getting-started.md.
make app # build Gropius.app (menu-bar app bundle)
make install # copy to /Applications and launch it
make run # or: run headless in the foreground, for developmentClick the menu-bar icon → Open Control Panel, or from any OpenAI client:
from openai import OpenAI
client = OpenAI(base_url="http://your-mac.local:11535/v1", api_key="not-needed")
print(client.chat.completions.create(
model="mlx-community/Qwen3-8B-4bit",
messages=[{"role": "user", "content": "Hello!"}],
).choices[0].message.content)By default the server is reachable by anyone on your network with no API key —
the control panel warns you while this is so. Set a key in Settings to require
Authorization: Bearer <key>. Same-machine clients (loopback, including other
user accounts) never need a key. The control panel and its /api/* endpoints are
bound to loopback only and are never reachable from the LAN.
The models list's residency fields, which say which models are loaded and how busy they are, are served to a client on this Mac — loopback, other user accounts included — whether or not a key is set, and to a client on the network when a key is set. With no key set, a client on the network still gets every downloaded model's name and nothing about what this Mac is doing with them — though it can still time a request to find out. Keeping activity private from the network means setting the key, not leaving the fields off.
The server's request log records the method, path, status and duration of a request, and never the client's network address.
Gropius collects no telemetry: nothing about usage, models, hardware or errors leaves your Mac, to the project or to anyone else. Request statistics is a local record, off unless you turn it on, holding token counts and timings — never a prompt, an answer, an API key or a client's address. The boundary is the Mac rather than your account: the control panel answers anyone who can reach it on this Mac, so on a Mac several people log into, any of them can turn the switch on and read what it records (what is recorded, and who can see it).
Gropius passes a request's prompt on to the model without reading it. The one exception is Merge system messages, a per-model setting that is off unless you switch it on: for a model you switch it on for, Gropius gathers that request's system messages into the first one, reads nothing else of the request, and keeps none of what it reads (how to switch it on; the rule it runs under is written down as an architecture decision).
cmd/gropius/— menu-bar app + singleton election.internal/— the engine:hub(HuggingFace client + downloader),runtime(Python/MLX provisioning + process pool),gateway(OpenAI + control API),registry,discovery,bindandnetshape(which addresses are bound, and which network each sits on),stats(request statistics),applog(the server's own log),config,capability,ui,app.docs/— getting-started guide, how-to pages and reference.
Design decisions and the empirical facts behind them: DECISIONS.md.
make test # go test -race ./...
make lint # fmt + vet + testMIT. See LICENSE.