All pages
On this page

preview

With preview an agent puts something with a web UI on a public link, so you can open it from your phone or send it to someone: a dev server that listens on a port of the relay's machine, or a folder of plain files. It can also list and close the previews of its own conversation.

Ask in plain words: "start the dev server and give me a link", "show me the built site". The agent is told to use the tool whenever you want to see, try or share a web UI, and never to send you to localhost.

A preview link is public

Anyone who has the link can open the app without signing in. The link's token is the only protection. The agent is told to say so when it gives you a link. Close a preview when you are done with it.

What you see #

  • A card for the preview in the conversation's background-task panel, with Open, Copy link and Close.
  • A link card in the transcript, on the reply in which the agent published it. It shows a closed badge once the preview is gone. A shared view of the conversation shows the card's label only, never the link.
  • Every preview of the relay, across all conversations, under Settings → Previews, each with a button to close it.
  • An offline badge on the card when the dev server behind a port preview stops answering. The link stays registered: start the server again on the same port and the same link works again.

You can do the same without an agent by typing /preview in the composer:

/preview 5173 web app        publish a port
/preview ./dist built site   publish a folder
/preview list                list the previews
/preview close               close one

Setting it up #

Previews are off until the relay's operator sets them up. They need a hostname of their own, separate from the relay's, so that a previewed app can never reach the relay itself. Three things:

  1. A hostname you control that differs from the relay's by name, not only by port.
  2. A front proxy (Caddy, a Cloudflare Tunnel, …) that points this hostname at the preview port.
  3. A previews block in the relay's config.json:
{
  "publicHostnames": ["relay.example.com"],
  "previews": {
    "enabled": true,
    "port": 3334,
    "bindHost": "127.0.0.1",
    "publicBaseUrl": "https://preview.example.com",
    "allowedTargetHosts": [],
    "maxLive": 8
  }
}

Agents have the tool whether or not this is done. Without it, a create call comes back with the reason, and the agent is told to pass that on to you: it is the operator's fix.

By provider #

Provider
Claude, Claude API Added in 0.9.18, DeepSeek Added in 0.9.17, Cursor has the preview tool
Grok; GitHub Copilot and OpenAI on the extension engine has the preview tool, through OAR's MCP server Added in 0.9.4
GitHub Copilot and OpenAI on the SDK engine no tool. While previews are set up, the agent is told how to call the relay's preview API, with the same rules and its conversation already written into the calls. What you see is the same.
Claude Cloud none: a cloud session runs in a sandbox at Anthropic, and there is no port or folder on your machine to publish

Actions #

Action What it does Parameters
create publish a port or a folder port or dir, optionally label
list list this conversation's previews
close close one of this conversation's previews token, optional when the conversation has exactly one

A result is JSON with "ok": true, or "ok": false and an error text. The tool never fails in another way; a wrong parameter comes back as an error the agent can read.

create #

Publishes either a port or a folder, never both in one call. Added in 0.9.11 Publishing the same port or folder again in the same conversation returns the preview that is already live, with the same link and the label unchanged; the result then says "existing": true.

Parameter Type
port 1024–65535 the local port of a dev server
dir path a folder to serve as plain files: absolute, or relative to the conversation's working folder
label text, up to 120 characters the name on the preview card

A dev server (port) #

The relay passes requests through to a server that listens on that port. It does not start, watch or restart that server; that stays an ordinary background task of the agent, with its own stop button.

Every preview lives under its own path on the preview hostname. The browser asks for /test_<token>/…; the relay removes that prefix before it passes the request on, and tells the app the prefix in X-Forwarded-Prefix. It does not rewrite pages. So an app works when the links and asset addresses it sends out are relative or carry the prefix, while it answers requests without the prefix. An app that asks for /assets/app.js from the root of the host will not find it.

That is why the agent is told to call create first and read the basePath from the result before it starts the dev server.

What agents are told does not fit every dev server

The tool's own description suggests starting the server with the base path set (Vite --base, Next.js basePath, an Express mount prefix). A server that then expects the prefix in the requests it receives does not match what the relay passes on: Vite's dev server and an Express router mounted at the prefix do not work that way. Previews explains what does.

{ "action": "create", "port": 5173, "label": "web app" }
{
  "ok": true,
  "existing": false,
  "url": "https://preview.example.com/test_0123456789abcdef0123456789abcdef/",
  "basePath": "/test_0123456789abcdef0123456789abcdef/",
  "token": "0123456789abcdef0123456789abcdef",
  "mode": "port",
  "hint": "Serve the app under /test_0123456789abcdef0123456789abcdef/ (the proxy strips the prefix and sends X-Forwarded-Prefix). Bind the dev server to 127.0.0.1, not 0.0.0.0: the relay's proxy reaches it there and nothing else should. Tell the user the URL and that the link is public."
}

Added in 0.9.17 The hint also tells the agent to bind the dev server to 127.0.0.1, not 0.0.0.0: the relay's proxy reaches it there, and nothing else should. Only sessions with the preview tool get this line (see By provider). Agents that are taught the preview API instead of the tool do not: GitHub Copilot and OpenAI on the SDK engine, and Grok and Copilot's extension engine when the relay runs with OAR_MCP_SERVER=0.

A folder (dir) #

The relay serves the folder's files itself. Nothing has to run: good for a build output, a mockup, a single HTML file or a coverage report.

  • The folder must be inside the conversation's working folder. This is checked again on every request, so a link cannot be made to lead outside later.
  • Files and folders whose names start with a dot (.git, .env) are never served, and neither are key files (*.pem, *.key, id_rsa…).
  • index.html is served for a folder path.
  • Relative asset paths work as they are. Paths from the root of the host (/assets/app.js) do not.
  • No WebSocket connections.
{ "action": "create", "dir": "./dist", "label": "built site" }

The result has the same fields, with "mode": "static".

list #

Lists the previews of the agent's own conversation, with token, label, url, target (the folder, or localhost:<port>), online and the conversation. Added in 0.9.14 Before 0.9.14 the list covered every preview of the relay; a preview's token is its only protection, so an agent now sees only its own. The relay-wide list is yours, in Settings → Previews and with /preview list. A preview the relay could not check after a restart carries "unverified": true and is not served until the check answers (see After a relay restart).

{ "action": "list" }
{
  "ok": true,
  "enabled": true,
  "previews": [
    { "token": "0123456789abcdef0123456789abcdef", "label": "web app", "url": "https://preview.example.com/test_0123456789abcdef0123456789abcdef/", "target": "localhost:5173", "online": true, "conversationId": "3f9c…" }
  ]
}

close #

Closes the link. A dev server behind it keeps running.

Parameter Type
token 32 hex characters the token from create or list. May be left out when the conversation has exactly one preview.

With no token and several previews in the conversation, the call comes back with an error and the list to choose from.

Added in 0.9.14 An agent closes only its own conversation's previews. The token of another conversation's preview is refused:

{ "ok": false, "error": "This preview belongs to another conversation; a conversation can close only its own previews. The owner can close it in Settings → Previews." }
{ "action": "close", "token": "0123456789abcdef0123456789abcdef" }
{ "ok": true, "closed": { "token": "0123456789abcdef0123456789abcdef", "label": "web app" } }

Limits #

Limit Value
Previews at a time, whole relay 8 (previews.maxLive)
Which previews an agent can list and close its own conversation's Added in 0.9.14
Port 1024–65535, and never one of the relay's own ports
Where a port preview may point the relay's own machine (loopback), plus hosts the operator lists in previews.allowedTargetHosts
Where a folder preview may point inside the conversation's working folder
Lifetime until it is closed or its conversation is deleted. Added in 0.9.11 It survives a relay restart; a port preview ends when the machine reboots, a folder preview when its folder leaves the working folder. A preview does not expire by itself.
Label 120 characters

Previews are kept apart from the relay: they are served on a separate port under a separate hostname, where none of the relay's own pages or API exist. Previews share that hostname with each other, each under its own path.

The conversation scope is not a wall

The relay scopes a preview request to the conversation it names, in the conversationId query or the conversation header every session worker and OAR's MCP server send. Agents use the same relay token as you do, so the scope is declared by the request, not proven: a request that names no conversation still gets every preview. The scope keeps the tool and the calls agents are taught to their own conversation; it does not stop an agent that reads the relay token and calls the API by hand. See What OAR exposes.