9.3. Serving
An installed program is a command line tool inside its environment. Serving
makes its functions callable from outside: by an AI assistant over the Model
Context Protocol (MCP), and by any HTTP client over a JSON API. Both come from
the same exported functions, types, and docstrings that produce the command
line interface, so there is nothing more to write. Serving takes two commands:
mim view declares what to serve, and mim start serves it.
9.3.1. Choosing what to serve
A view is the set of installed modules that answer on an adapter. MCP and the JSON API each have their own view. Installing a program never makes it reachable on its own; you add it to a view:
$ mim view add smiles --as mcp,api # serve smiles on both adapters
$ mim view # show the environment's views
$ mim view rm smiles # take smiles out of every view
--as takes mcp, api, or both, comma-separated. A module must be installed
before it can be added. Views are stored with the environment’s configuration
and take effect the next time the environment is served.
9.3.2. Starting a server
$ mim start -p 8005:8005
mim start launches one server for the environment and returns. For a
container environment the server runs in a container named
morloc-serve-<user>-<env>; on the native backend it is a background process.
It answers on one port:
| Path | Purpose |
|---|---|
|
The MCP endpoint, for AI assistants |
|
Call one function with JSON arguments |
|
List the modules on the API and their commands |
|
Describe one module’s commands and arguments |
|
Liveness check |
-p maps a host port to the server’s port and is written HOST:CONTAINER,
8005:8005 here. Without it, mim serves on 9000 when MCP is in the view
(8080 otherwise) and moves to the next free port if that one is taken; it
prints the port it chose.
By default the server listens on the host’s loopback address, 127.0.0.1. Only
programs on the same machine can reach it, so it needs no password. Once a
server is running, edits to the views apply only after you replace it with
--force:
$ mim start -p 8005:8005 --force
9.3.3. Serving to other machines
To reach the server from elsewhere, --expose binds it on every interface
(0.0.0.0). An exposed server must be protected by a token, a shared secret
that every client sends with each request:
$ export MORLOC_MCP_TOKEN=$(openssl rand -hex 16)
$ mim start -p 8005:8005 --expose --allow-plaintext
mim never generates a token and never writes one to disk. You choose it, and
pass it either as --auth-token <token> or, to keep it out of your shell
history and process list, in MORLOC_MCP_TOKEN. Every request to /mcp,
/call, and /discover must then carry the header
Authorization: Bearer <token>; a request without it gets 401 and
{"error":"unauthorized"}. /health stays open so monitoring works without
the secret.
--allow-plaintext is required with --expose because the server speaks plain
HTTP: the token and the data travel unencrypted. A token stops strangers who
find the port; it does not stop anyone who can watch the network. For use over
an untrusted network, keep the default loopback bind and reach the server
through an SSH tunnel, or put a TLS proxy in front of it.
--allow-no-auth serves an exposed server with no token at all. It exists for
a trusted private network or a gateway that already checks callers, and is a
bad idea anywhere else.
9.3.4. Calling the JSON API
/call/<module>/<command> takes the function’s arguments as a JSON array, in
order, and returns the result in a JSON envelope:
$ curl -s http://localhost:8005/call/smiles/mw \
-H "Authorization: Bearer $MORLOC_MCP_TOKEN" \
-d '["NC1=NC=NC2=C1N=CN2"]'
{"status":"ok","result":135.13}
A failed call returns {"status":"error","error":"…"} with HTTP status 500.
/discover lists every module in the API view with its commands, and
/discover/smiles gives each command’s arguments, types, and docstrings, so a
client can find out what to call without reading the source.
9.3.5. Connecting an AI assistant
When the MCP view is not empty, mim start prints a client configuration
entry on standard output, and its status messages on standard error. The
entry has this shape:
{"mcpServers":{"smiles":{"url":"http://<host>:8005/mcp","headers":{"Authorization":"Bearer <token>"}}}}
Redirect it to a file to hand it to an MCP client that reads this format.
Treat the file as a secret, since it contains the token, and keep it out of
version control. For an exposed server, <host> is the machine’s hostname;
replace it with an address your clients can resolve.
To add the server to Claude Code:
$ claude mcp add --transport http smiles http://<host>:8005/mcp \
--header "Authorization: Bearer $MORLOC_MCP_TOKEN"
Each exported function becomes one MCP tool, named <module><command>
(smilesmw), with the docstrings as its description and the argument types
as its input schema. The protocol details — the handshake, how arguments map
to tool properties, and what is not exposed — are in Model Context Protocol (MCP).
9.3.6. Watching and stopping servers
$ mim status # every running server: env, mode, modules, URL
$ mim logs # the default environment's server log
$ mim logs -f # follow it
$ mim stop # stop the default environment's server
mim status lists the servers of all your environments, with what each serves
(mcp+api here) and its URL, marked (token) when one is required. logs
and stop act on the default environment, or the one named with --env.