- The AI can only pick from the list your page registered. It never reads the page or fills in forms itself, unless you turn on page control for the site.
- Your functions never leave the page. Only the name, description, input schema and the extras below are sent to the desk.
- Each run is recorded in the conversation’s AI actions, so agents see what happened.
The standard: WebMCP
Page actions use WebMCP, the browser API for exposing a page’s tools to AI agents, rather than a Jun-only format. Tools you register for Chrome’s own agent work with Jun with no extra code, and actions you register with Jun are visible to other WebMCP agents too. Where the browser has no nativedocument.modelContext, the loader installs a small stand-in so the same code works everywhere. Where the browser has it, the loader uses the native one.
Put the snippet in
<head> with defer, before your own scripts. Code that checks for document.modelContext once on load must find it there.Register an action
There are three ways in. All three end up in the same list the AI picks from.- JunDesk.registerAction
- document.modelContext.registerTool
- Declarative forms
The WebMCP tool shape plus Jun’s extras (pages, instances, highlighting, context, availability, undo, risk, status line, result card). Returns a function that removes the action.
JunDesk.registerAction exists as soon as the loader has run. Registrations made before the actions code has loaded are queued and replayed.What execute gets and returns
execute(input, signal) runs in the visitor’s browser.
inputholds the values the AI filled and the visitor confirmed, already checked against your schema (numbers are numbers, enums match exactly).- The second argument is an
AbortSignalthat also has asignalproperty pointing at itself, so bothfetch(url, { signal: opts })andopts.signalwork. - It may be async. A thrown error or rejected promise shows as Didn’t work with the error’s message, and the AI hears about it.
Return JSON when the AI needs data for its next step (search results, a list of slots). Return a sentence when the customer should see what happened.
Fields
Standard WebMCP fields:string
required
The action’s name, like
add_to_cart. Up to 64 characters. The AI sees it as pa_<name>.string
required
What it does, in one line (up to 200 characters). The AI chooses by this, and the confirm card shows it.
object
A JSON Schema object. Each property can be
string, number, integer or boolean, with description, enum (up to 24 values), minimum, maximum and format (email, date and url get matching fields). Up to 12 properties. Properties listed in required that the AI couldn’t fill are asked for in the chat.function
required
Runs the action. See above.
object
WebMCP hints. Without
risk, consequentialHint: true means confirm and readOnlyHint: true means auto.JunDesk.registerAction or useJunAction):
string[]
Offer the action only on matching paths. An exact path (
"/pricing") or a prefix ending in * ("/plans/*" matches /plans/pro and /plans/). Left out, it’s offered everywhere it’s registered.string | number
One instance per item on screen, like one per product card.
name plus key must be unique; registering the same pair again replaces the older one. Keyed instances aren’t mirrored into document.modelContext (WebMCP names must be unique there).Element | RefObject
Highlighted with an outline and scrolled into view while the action runs. Also tells the loader whether the action is on screen, which helps it rank.
object | function
Small facts for the AI, like
{ price: 89, inStock: true }, or a function returning them. Up to 8 entries; strings up to 80 characters, numbers and booleans.() => boolean
Checked before the action is offered and again before it runs. When it returns
false (sold out, already on this plan), the action isn’t offered, and a run in flight shows No longer on this page.(result) => void | Promise
Gives the run an Undo button. Receives what
execute returned."auto" | "confirm" | "human"
default:"confirm"
How it runs. See Risk levels. Overrides
annotations.string
The line the chat shows while it runs, like
"Checking fares". Up to 60 characters. Without it, one is made from the name: set_pickup becomes “Setting pickup”, export_csv “Exporting CSV”.string
A card made on the desk, by its widget name (like
"cart_summary"). When execute returns an object, the chat shows it as that card (up to 32 KB of JSON). See Widgets.Risk levels
When required inputs are missing, the chat asks for them first (“A couple of details first”): a short list of choices (up to 6) becomes buttons, a longer one a select, a yes/no two buttons, a
date a date picker, an email an email field. Then the visitor presses Continue.
Override risk on the desk
Your site sets a default, but the desk decides. Addwebsite-actions.yaml to your agent config: a map of action name to auto, confirm or human, or to run: and status:.
website-actions.yaml
jun eval.
After it runs
Once a run starts, it shows as a step under the AI’s answer: a state icon, a title made from the name (“Add to cart”), and a short chip with the result (or the readable inputs). Opening a step shows the description, inputs, and result or error. Several steps fold under one “N steps” header. Undo sits on the step when you registeredundo.
Multi-step requests
Some requests take several actions: “find a flight to Lisbon on Friday and add it to my cart” might be a search, then a selection, then an add. After each result, the AI gets one more turn, and in that turn it must decide: call the next action, or finish. So requests chain one action per turn:- The visitor asks.
- The AI calls
search_flights(auto). It runs and returns JSON. - The AI reads the result and calls
add_to_cart(confirm). The visitor confirms. It runs. - The AI writes a short reply, or ends the chain.
- At most 12 AI messages in a row after the visitor’s message.
- Calling the same action with the same inputs again ends the chain instead of repeating it.
- Each
confirmstep still waits for the visitor.
Many actions on one page
The loader sends up to 60 actions with each message: actions on screen first, then the rest in registration order, then other WebMCP tools. The desk picks up to 30 for each reply: words from the visitor’s question that match an action’s name, description or context count most, being on screen counts too, and registration order breaks ties. Actions defined on the desk (actions/*.yaml) can be limited to pages the same way, with pages: ["/orders/*"].
With page control on, its five actions (read_page, click and the rest) are always offered as well, after your own.
React
useJunAction from packages/widget-react (MIT, React 18+ as a peer, no other dependencies) registers an action while the component is mounted and removes it on unmount. If the loader hasn’t loaded yet, it waits for the jundesk:ready event.
useEffect’s dependency list: the action is registered again when it changes.
The package lives in the repo at
packages/widget-react and isn’t published to npm. Copy src/index.ts into your app, or install it from the repo. Its types don’t list status yet; the loader accepts it anyway.Card buttons that run page actions
A widget card’s button can run one of your page actions: give it the action typepage:<name>, for example {"type": "page:remove_from_cart", "payload": {"id": "mug"}}. The press counts as the visitor’s yes, so the action runs at once with the payload and the card’s fields, unless a required input is still missing. A human action still goes to your team. There’s no AI turn for the press.
Test page actions
Eval cases intests/*.yaml take an actions: list and a page: path, and can expect an action:
tests/plans.yaml
upgrade_plan (confirm, with undo), export_csv (auto) and a plain WebMCP show_usage.
Actions
Server-side actions that call your API.
JavaScript API
registerAction and everything else on JunDesk.