Skip to main content
Your user asks your agent for something. A real, live view appears in your product, on your brand, reading your own data. Your agent keeps its loop, its model, its tools, its MCP connectors, and its chat UI.
A product dashboard slot showing a generated spending breakdown with a donut chart and a category table of live amounts

A generated spending breakdown filling a host's own page slot. The figures come from the host's endpoint at render time — nothing in the request was a number the agent computed.

1. Install

Init detects your framework and auth from package.json and writes: Init never edits a file you authored, so one paste is yours:
The generated wrapper also mounts <VendoOverlay />, Vendo’s own launcher pill. Delete that line if your agent is the only conversation in your product — but keep the provider, or the slots and embeds below have no wire to read. Full inventory: vendo init. Caller identity comes from your auth, through principal or an auth preset.

2. Pick how your agent reaches Vendo

One question: can your agent import the package? Node or TypeScript in the same app as createVendo → Path A. Separate service, another language, or you already standardize on MCP connectors → Path B.

Path A — tools in your agent

Built per request: tool execution needs a principal-scoped context, so pass the caller your auth already resolved. On Mastra, vendoMastraTools(vendo) from @vendoai/vendo/mastra is the same pack in createTool shape, with the principal on Mastra’s RequestContext. Detail: AI SDK · Mastra.

Path B — Vendo as an MCP connector

Three things, none of them scaffolded by init:
Your existing catch-all already serves the transport. Your agent then adds one URL: https://your-app.example.com/api/vendo/mcp. The OAuth is nobody’s code. Adapter contract, consent page, and tool-menu curation: Bring your own agent over MCP · door internals.

The fork, side by side

Identical either way: the same tools, the same guard-bound registry (policy, approvals, audit), the same receipt of words rather than pixels, the same slots, the same .vendo/theme.json.
Read the Extra tools row twice. The in-process pack does not carry any vendo_apps_* tool — in-app interaction rides the wire, not your loop. Moving a view your agent already made is still fully available on Path A; you do it from your own code with the app id, in step 5.

3. Teach your agent when to use it

A tool your agent never reaches for is not a feature. Paste this into your system prompt, beside whatever you already say about your other tools.
Add this second block too. WHERE IT LANDS is both paths; add MOVING ONE THEY ALREADY HAVE only on Path B, because teaching an agent about a tool it does not have is how you get an invented tool call:
Source, shipped to Claude Code as a skill: examples/claude-code-plugin/skills/make-a-screen.

4. Your first screen

The view builds on your live data, lands in the person’s own list of views, and your agent gets exactly this:
Four fields of words, never pixels — the whole contract, not a summary of a richer one. The earlier version handed the agent the whole document, and a model handed a tree eventually talks about the tree: narrating a screen it has not seen, to someone looking at a different one. So the receipt is deliberately unusable for narration. status is "ready", "building", or "failed", and "building" is honest — an escalated build outlives the call and there is nothing to poll. "failed" means the checks floor rejected a view whose bindings claim data your host does not return, or whose props do not type-check; nothing is painted. In process the call returns fast: the first streamed view part carries the app’s permanent id, so your loop gets a vendo/app-ref@1 (envelope contract) while the build streams over the wire.

5. Put it where you want it

A slot is your markup:
Add @vendoai/ui as a direct dependency. Empty, the slot renders your children untouched — no wrapper, so inline one anywhere. Filled: build skeleton, then the live view, then a failure with a retry. Never a blank hole. One view per slot per person; a second one evicts the first. Either path — your agent aims at a slot by id:
Path A, without slot — you place it, using the appId from the vendo/app-ref@1 envelope your loop already received:
Building a document or block editor? That is this shape. One <VendoSlot> per block, with the block’s own id as the slot id. A generated view then lives in a block like any other: it moves when the block moves, it is scoped to the person with that document open, and your editor keeps owning layout.
Slot ids are yours, and nothing enumerates them for an agent on either path — tell the agent the id, or let the person say it. That is why step 3 forbids inventing one.

6. Move a screen after the fact

“Put that on my dashboard” about a view they already have is vendo_apps_pin, which answers with what it displaced so your agent can say what moved:
vendo_apps_unpin clears the slot; the view itself is untouched. Both are writes — under a cautious policy the first one parks in your approvals queue, where the person sees the tool, the view, the slot, and the exact arguments before anything moves. Routes: HTTP routes.
Both pin tools reach an agent over the MCP door only. Do not put vendo_apps_pin in a Path A system prompt — move a view there by writing the app id into your own record and re-rendering the slot.
Inside your own chat, your agent’s Vendo tool outputs render inline:
<VendoToolResult> dispatches any vendo_* output — a live view for an app ref, an approve/deny card for an approval ref, nothing for plain data. You never branch on envelope type (Embeds and envelopes).

7. The perimeter

  • Every call runs as the signed-in person, through the same guard-bound registry Vendo’s own surfaces use — policy, approvals, audit. No second authority path.
  • No duplicate of your API credentials. Host actions reach your own routes through the actAs seam, never by forwarding an agent’s bearer token; without a grant the call fails closed.
  • Writes can require the person. .vendo/policy.json decides where that line sits; a parked call waits in your queue while the agent moves on.
  • Generated views render jailed — in an iframe with connect-src 'none' by default, so a tree-only view reaches no host tools and performs no egress. Interactions ride the wire back through the guard, never your loop.
Tools and safety · How generated UI works.

Next

The MCP door

Path B end to end: the OAuth dance, the consent page, the connect page for your users, and a screenshot walkthrough.

Door internals

The HostOAuthAdapter contract, surfaces.mcp curation, federation, revocation.

Quickstart: AI SDK / Mastra

Path A wired end to end with the real diff — and the same in Mastra.

Embeds and envelopes

The envelope contract and the three components that render it.

Register your components

So generated views render your branded UI, not generic primitives.