Robutler

Local dev server

Experimental. The local dev server is a hand-run script (scripts/widget-dev-server.ts), not a polished tool. It binds to localhost only and is great for fast iteration, but treat it as a preview. See What's ready and Contributing. The CLI around it is experimental too.

The dev server serves your app bundle on localhost and reverse-proxies /api/* to the cloud portal, attaching a short-lived widget-dev Bearer. Because everything is same-origin under the dev server, the unmodified App SDK works and host.* calls (such as host.fn and host.discover) reach real portal data. Your coding agent can then render the page, screenshot it, read the console, and click around.

1. Mint a dev token

Mint a short-lived, least-privilege Bearer with the widget_dev_token MCP tool (or POST /api/local-dev/mint-token):

ArgumentTypeNotes
agentIdstringOptionally bind the token to one agent you own.
ttlMinutesnumber5..60, default 30.

It returns { token, portalUrl, agentId, expiresAt, scope }. The token carries the narrow widget-dev scope, not read / write / *, and it acts as you against the CORS-open portal_token endpoints. Keep it on the dev machine, never commit or log it, and prefer the shortest TTL that fits the session. It is deliberately rejected at /mcp (it is not the control surface). See the security model.

2. Run the server

ROBUTLER_DEV_TOKEN=<bearer> ROBUTLER_PORTAL=https://robutler.ai \
  pnpm tsx scripts/widget-dev-server.ts --dir ./my-app --port 4610

Flags and environment:

Flag / envDefaultNotes
--dir <folder>public/widgets/deckYour app bundle folder. Absolute or relative to the working directory.
--port <n>4610Local port.
ROBUTLER_DEV_TOKEN(unset)The dev Bearer. If unset, proxy calls go out anonymous and the server warns.
ROBUTLER_PORTALhttps://robutler.aiThe portal to proxy to.

On start it logs the folder it is serving and the proxy target, then listens on 127.0.0.1:<port>.

What it serves

  • http://localhost:<port>/ renders the dev host, which loads your entry HTML inside a standalone bridge.
  • Files under your --dir are served directly (/index.html, your CSS, /images/*, and so on).
  • The portal-absolute SDK path /widgets/sdk.v2.js (and other shared /widgets/* assets) is served from the repo's public/widgets/, so a bundle that uses the absolute SDK tag renders here exactly as on the portal.
  • /api/* is reverse-proxied to the portal with the Bearer attached. Only a small allowlist of prefixes is proxied (/api/widgets/, /api/discovery, /api/agents/); it is never an open proxy.

What works and what does not in dev

  • host.fn and host.discover reach real portal data through the proxy.
  • host.kv falls back to localStorage in dev.
  • public custom functions (anonymous endpoints) work cross-origin. session / visitor_session function endpoints are not reachable cross-origin in dev, so design those to degrade gracefully when previewing.

Preview and debug with your coding agent

With the page on localhost, your coding agent can drive it: render it, take screenshots, read the browser console, and interact with it. Iterate by landing edits with widget_put_files and reloading.

For server-side issues (a published custom function throwing), use widget_fn_logs to read recent invocations with status, error code, and duration. For a deployed instance on a canvas, workspace_widgets_logs returns the captured console and load state. Both are in MCP build tools.

Next

On this page