Skip to main content

Entry contract

  • The entry is an ES module that exports start(context). Return from start only 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 }. dispose closes 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.ts file 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 on publish or restart. 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: stop only 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.
The host manages only the entry Node process. Close any child processes the plugin starts in dispose; the host doesn’t promise to find and end them.

Port authentication

Before loading the entry, the host installs an authentication guard on the node: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, either createServer or a framework built on it. If https, http2, a raw net server, or a child process binds the port directly, the guard can’t protect it, the service never becomes ready, and startup fails with READINESS_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:http server 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 401 until the user refreshes or reopens it; this is expected.

Logging

Use context.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.