See Node API reference for the entry,
context, Connector, and tool contracts, and Agent API reference for session methods.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
The manifest determines whether
hostConnector, agent, and tool are injected. Switching a permission off in the permission panel keeps the object but rejects the corresponding calls, so object presence alone does not establish authorization. capabilities.has(name) reports host implementation support, not a grant to this plugin. Use optional chaining for older hosts, such as context.capabilities?.has('tool').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.The current log bridge truncates messages and sends only the keys of fields, not their values. See the logger reference for methods and limits.