12.6. Serving
A compiled program is a command-line tool, but the nexus can also keep it
running as a server, answering many requests without restarting its pools.
This section specifies the server modes, how one command of a manifest
becomes an HTTP endpoint and an MCP tool, how a server recovers from a
crashed pool, how callers may evaluate new expressions, and the security
model that decides who may call what. How mim chooses what to serve is
specified last.
12.6.1. Server modes
| Mode | Serves |
|---|---|
Daemon |
One program, to local clients over a Unix socket or loopback TCP (length-prefixed JSON), and optionally over HTTP. Starts every pool at startup. |
MCP |
One program to one agent, over standard input and output, or over HTTP. |
Front end |
Several installed programs over one HTTP port, as MCP tools and a JSON API, with optional eval. Each program runs in its own daemon, which the front end starts on first use. |
mim always serves through the front end. It names the programs to serve
explicitly; nothing is served because it happens to be installed. The front
end finds each program’s manifest in the installed-programs directory of
the state (Overview), checks every one at startup, and starts each
program’s daemon on its first request. A daemon that has exited is started
again on the next request for it.
12.6.2. Endpoints
The front end answers:
| Request | Meaning |
|---|---|
|
Liveness. Open to every caller, so a monitor needs no secret. |
|
The MCP endpoint. |
|
Call one command. The body is the argument list as a JSON array, or an object with an |
|
The served modules, then one module’s commands with their arguments, types and documentation. |
|
Evaluate an expression, when eval is enabled (below). |
A successful call returns {"status":"ok","result":…}. A command whose
result carries a media type returns the raw bytes with that type instead.
A failure returns {"status":"error","error":"…"}.
12.6.3. Commands as MCP tools
Each exported command of each program on the MCP view becomes one tool,
named <module>__<command>. A name that collides is refused at startup.
The tool’s description is the command’s documentation, and its input schema
is an object with one property per argument, keyed by the argument’s
published name (its declared long name, or _1, _2, … for positional
arguments). Morloc types map to JSON Schema:
| Morloc type | JSON Schema |
|---|---|
|
|
integer types |
|
real types |
|
|
|
enumerations |
|
sum types |
|
lists, tables |
|
tuples |
|
records |
|
optional |
|
A record argument may instead be spread into one property per field. A
command that renders its result several ways gets an extra _render
property naming the rendering. When a command returns a record, the tool
also declares an output schema and returns structured content; otherwise
the result is one text block of JSON.
A command is not offered as a tool if an argument or its result is a file handle or a stream, or if it reads standard input. These mean nothing to a remote caller, and a stream would write to the standard output that the MCP transport owns.
Protocol. The server speaks MCP protocol version 2025-06-18. A client
sends initialize, then the notifications/initialized notification;
tools/list and tools/call before that are rejected. Over HTTP,
initialize returns an Mcp-Session-Id header that every later request
must carry; an unknown session is told to initialize again, and an idle
session expires. A call that fails while running returns a normal result
marked isError, with the message as text. A malformed request, an unknown
tool or a bad argument is a JSON-RPC error.
12.6.4. Pool crash recovery
A daemon outlives any one call, so it must survive a pool that crashes. When it finds a pool dead, it closes admission (new requests are answered as unavailable, HTTP 503), stops every pool, and waits for the requests already running to finish. Only then does it discard the program’s shared memory, create it afresh, restart and ping every pool, and reopen admission. No request spans a recovery, so no shared-memory reference from before the crash is ever read after it. If running requests do not finish within a minute, or recovery keeps failing, the daemon exits instead of serving from an inconsistent state.
12.6.5. Eval
When enabled, eval lets a caller send a whole Morloc expression that
composes installed functions. It is offered as POST /eval with
{"expr": "…"} and as an MCP tool named eval whose description lists
the importable modules.
Each request is typechecked, compiled and run in a fresh process group, under these restrictions:
-
Every
importmust name a module on the allow-list, checked against the module’s real name, so renaming on import does not help. -
Only installed modules resolve; local imports are refused.
-
The expression may not source foreign code or declare types, typeclasses or instances.
-
Input and output intrinsics may not appear directly; a function from an allowed module may still perform them, so the reachable I/O is exactly what those modules export.
-
The compiler’s memory is capped at 2 GB; CPU time at a configurable limit (30 seconds by default) and wall time at four times that. A request over its limit has its whole process group stopped.
Nothing about one request is kept for the next.
12.6.6. Security model
A server binds the loopback address unless told otherwise, so only the
local machine can reach it. A server bound to any other address must have a
token, a shared secret every request presents as
Authorization: Bearer <token>; without it a request is refused with 401.
The token is compared in constant time, and only /health is exempt.
Starting an exposed server without a token requires an explicit waiver,
meant for a gateway that already checks callers.
Eval is code execution, so it is held to a stricter rule: with no token it
is locked — not listed, reported as locked by /discover, and refused — unless waived separately from the server-wide waiver. mim grants that
waiver to a loopback server, where every caller is already local.
Remote clients are untrusted in other ways too: a request must arrive within a deadline, its headers and body are bounded, and the number of simultaneous connections is capped, so one client cannot exhaust the server.
The plain-HTTP server does not encrypt. A token stops strangers who find
the port, not anyone who can watch the network; mim therefore requires
the operator to acknowledge plaintext before exposing a server, and an
untrusted network needs a tunnel or a TLS proxy in front.
12.6.7. Serving an environment
mim keeps, per environment, a view for each adapter — the modules
answering over MCP and over the JSON API — and an optional eval
allow-list. Installing a module never serves it. mim start reads the
views, starts one front end for the environment, and records its process
or container. A second start is refused while the recorded server is
alive (judged by the recorded process being the one mim started, not
merely a live process id), unless told to replace it. stop ends the
server and removes the record.
The token is chosen by the operator and passed to the server in its
environment. mim never generates one, writes one to disk, or puts one on
a command line. A container server runs with a read-only root file system,
and the environment is marked immutable while it serves (Environment solving),
so a caller cannot change the installed software.