Who is calling
The bearer on the request is a JWT your broker signed, and the door verifies it against the broker’s published keys. Thesub claim is one of your own user
ids, in your own spelling.
The door then asks your auth preset to resolve that subject to a live
principal, on every request. Return null there and the token is dead on the
next call, sessions included.
An anonymous or ephemeral principal is never served a session. The door
persists grants and audit rows, so it needs a durable subject.
What the agent sees
With no menu authored,tools/list answers with your merged, enabled tools
whose audience is end-user or unset. Operator and internal tools stay off
it. Author a surfaces.mcp menu and that list is the menu instead — see
Curate the menu below.
Names, descriptions, and input schemas are served verbatim from your registry.
A tool’s title is what a client shows a person instead of the wire name.
Vendo’s own vendo_* tools ride along: vendo_make for a generated screen; the
saved-apps viewer, which is vendo_apps_list, vendo_apps_open, and
vendo_apps_call; and the caller’s own files, which is
vendo_user_files_list, vendo_user_files_read, and vendo_user_files_put.
They are runtime plumbing and are never curated away — a surfaces.mcp menu
filters your tools, and the vendo_* prefix bypasses it.
The file tools are scoped to the bearer’s subject and take no subject argument,
so a token for user_1904 reaches that person’s files and no one else’s. See
User data for what saves, what reads back, and the cap.
Each listing carries MCP annotations derived from the tool’s Vendo risk
label, so a client can warn before a write without re-reading prose.
An
ungraded tool asserts neither hint. MCP’s own default for
destructiveHint is true, so leaving both out keeps the client on the spec’s
conservative reading.
Curate the menu
A shorter, deliberate menu is named in.vendo/overrides.json under
surfaces.mcp.
.vendo/overrides.json
add() registers is never stranded. The file
itself resolves once at boot: edit it and restart.
A menu is curation, not a permission boundary. Your policy, approvals, audit,
disabled, and audience exclusions decide what may run, and none of them read
this block.
How a call reaches your host tools
The agent has no session in your product, so the inbound bearer is never forwarded to your host routes. Door tool calls reach your API through theactAs seam.
The door records the caller’s consent for that client id and projects it onto
every context it mints. When a call resolves to a host route, Vendo hands
actAs(principal, grant) that projection and uses the auth material it returns.
Without actAs configured the tool returns a not-implemented error. A grant
whose subject is not the current principal fails closed with
act-as-subject-mismatch.
The authenticated user is the authority. Scopes on the token buy no exemption,
and the door adds none.
What comes back
Five outcomes, all of them in-band so a retry never re-executes a committed write.vendo_make answers with an id, a title, a status, and one line to say out
loud. The screen itself lands in your product, never in the agent’s transcript.
That line is say, and it is written for the person rather than for the model.
An agent relays it close to verbatim and reports nothing else.
Rendering the app in your own chat
React only — there is no web component and no MCP Apps UI resource. If the chat callingvendo_make is your own frontend, <VendoAppEmbed> is the documented
way to put the app inline in it, off the receipt’s own id.
status on the ref is always the literal "building", whatever the receipt
actually said — the embed polls your own wire for the real state, so a receipt
that already came back "ready" just resolves on its first poll. Skip the
mount on "failed": nothing painted, and say already says why.
While the build streams, the embed paints the app assembling step by step,
then swaps to the live app in place.
<VendoProvider> rides your product’s own session, not the MCP token — the
same wrapper Embeds in your chat uses, because this
polling never goes through the door.
Where a screen lands
A new screen goes to the person’s own list of views. Passslot on
vendo_make and it takes a named place on your page instead.
tools/call arguments
slot is for something new. Moving an app the person already has is a
different pair of tools.
A slot holds one app, so pinning replaces. Both are writes, and on the
cautious preset they park like any other write.
vendo_make refuses app and slot together, by name. An edit that also
moved the app would evict whatever held that slot off the back of a change
nobody aimed there.

A pin, start to finish. The slot is the caller's own, so the second person's home is untouched.


Approvals wait for the person
On thecautious preset, reads answer straight away and writes park. The agent
gets an error naming the approval and where it is waiting.
Approving does not resume the call. The agent has to call again, and it has to
be on the same MCP session.
The discovery your broker owns
The first unauthenticated request answers401 with a WWW-Authenticate
challenge naming the door’s protected-resource metadata. That document names
your broker as the authorization server.
A broker-fronted door serves no /authorize, /token, or /register of its
own. Those live at the broker, which is why the exchange in
Service keys & broker posts there.
Broker mode is declared, never discovered. Nothing is registered anywhere and
nothing is fetched at boot, so no deploy can repoint a live door behind its
back.