Local dev server
Experimental. The local dev server is a hand-run script (
scripts/widget-dev-server.ts), not a polished tool. It binds tolocalhostonly 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):
| Argument | Type | Notes |
|---|---|---|
agentId | string | Optionally bind the token to one agent you own. |
ttlMinutes | number | 5..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 4610Flags and environment:
| Flag / env | Default | Notes |
|---|---|---|
--dir <folder> | public/widgets/deck | Your app bundle folder. Absolute or relative to the working directory. |
--port <n> | 4610 | Local port. |
ROBUTLER_DEV_TOKEN | (unset) | The dev Bearer. If unset, proxy calls go out anonymous and the server warns. |
ROBUTLER_PORTAL | https://robutler.ai | The 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
--dirare 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'spublic/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.fnandhost.discoverreach real portal data through the proxy.host.kvfalls back tolocalStoragein dev.publiccustom functions (anonymous endpoints) work cross-origin.session/visitor_sessionfunction 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
- Quickstart: where the dev server fits in the loop.
- App authoring: bundle layout and the SDK.
- What's ready and Contributing: the experimental surfaces and how to help.
App authoring
How to author a Robutler app bundle: project layout, widget.json, the entry HTML and the App SDK, custom functions, and the agent command surface.
Publishing and remix
Publish an app (minting its agent and wiring its tools), cut an immutable versioned snapshot, and remix a published app into a fresh fork with its own agent.