Entry contract
- The entry is an ES module that exports
start(context). Return fromstartonly after the HTTP listener accepts connections and every declared route is installed. That is the readiness signal; you don’t write a health-check route. - In
start, only register routes and start listening. Don’t fetch business data synchronously; load it on demand in request handlers. - Return
{ dispose }.disposecloses the listener, SSE / WebSocket connections, child processes, timers, subscriptions, and persistence handles. It is not a per-request completion callback. - The entry process is the one the host manages directly. It must stay alive, holding its assigned port, for as long as the Mini App is available.
- No Mini App SDK is installed at runtime. The
miniapp-api.tsfile from the Skill is for type checking only.
context fields
Minimal entry
miniapp/node/server.mjs
When it runs
- On demand: it starts when a Mini App is selected with
@, its page is opened, or onpublishorrestart. Enabling the plugin doesn’t start it. - Shared service: each Mini App has one Node process shared by all conversations, and each conversation gets its own page. See Mini Apps and sessions.
- Capacity: at most 3 different Mini App services run at once. A fourth returns
BUSY; updating a running Mini App doesn’t take an extra slot. - Stop:
stoponly stops the service and keeps the plugin and its other capabilities. Closing a running Mini App’s tab and confirming stops its shared service and closes that plugin’s pages in every conversation. - Update and restart: the old process stops before the new one starts. A Mini App never has more than one process, so don’t start a second entry yourself.
Other languages and child processes
Anything can sit behind the entry. For a Python, Go, or other backend, have the Node entry launch it on its own internal port and reverse-proxy to it from the assigned port.Port authentication
Before loading the entry, the host installs an authentication guard on thenode:http servers in the process. Only the Mini App page and the managed MCP client carry the current run’s credential; every other request gets 401 before it reaches plugin code. Plugins need no auth logic and never see the credential. See Ports and credentials for how it works.- Serve the assigned port with
node:http, eithercreateServeror a framework built on it. Ifhttps,http2, a rawnetserver, or a child process binds the port directly, the guard can’t protect it, the service never becomes ready, and startup fails withREADINESS_TIMEOUT. - The
/__mavis/path prefix belongs to the host. Don’t declare or handle routes under it. - Build page requests from relative or same-origin URLs. Service Workers, pop-ups, external browsers, and ordinary browser tabs don’t carry the credential and get
401. - The guard covers every
node:httpserver in the process. To receive third-party callbacks such as OAuth redirects, route them through the assigned port instead of opening another HTTP server in the process. - A restart issues a new credential. An already open tab gets
401until the user refreshes or reopens it; this is expected.
Logging
Usecontext.logger for diagnostics. The host collects only bounded stdout / stderr samples, so keep credentials and business content out of logs and don’t rely on them being retained in full.