Skip to main content

Quickstart

npx vendo init turns your app into an MCP server. Outside agents connect through your own login; your own backend gets a token from one method. Four steps, each with a checkpoint. Do not move on until the checkpoint passes.
1

Run init

Already ran vendo init and answered From outside agents over MCP? You have this step — skip to Open the door.
Answer the first question — How will people use your agent? — with From outside agents over MCP — Claude, ChatGPT, Cursor, or any MCP agent (experimental). That option is labelled experimental; the door itself is what this page proves.Next it confirms the auth preset it detected — Should the agent act as your signed-in … user? — which is what wires actAs, and then asks where your model key comes from.Then Where does this app run in dev? — the origin your dev server actually prints, http://localhost:3000 or whatever port it names.
This answer decides which sign-in postures are open to you. Vendo Cloud registers this origin as your tenant’s forwarding address and refuses one that is not https, so a localhost answer and the Cloud broker cannot go together — init refuses the pair rather than writing a door that dies on its first request. On localhost, take Local at the posture question below. To run the Cloud broker in dev, give an https tunnel’s origin here instead.
After the posture question below, init asks a few more that are not this page’s business — the AI judgment pass, and any theme slots it could not read confidently. Their defaults are fine; press Enter through them.Init writes the wiring: mcp: true into the composition it creates, the origin-root discovery route beside it, VENDO_BASE_URL into .env.local, and a .vendo/ directory holding the tool catalog, judgments, policy, overrides and theme. It adds two package scripts — predev and prebuild, both running vendo sync so the catalog cannot drift from your code — and installs ai and @ai-sdk/anthropic. The closing line counts the files it wrote.
lib/vendo.ts
auth has to be a preset that carries the oauth half — clerk(), authJs(), supabase() or auth0(). Plain jwt() does not, and init refuses to write the door without one. Auth lists them.
Checkpoint. lib/vendo.ts says mcp: true, app/.well-known/[...vendo]/route.ts exists, and .env.local has a VENDO_BASE_URL line. If init refused instead, the message names the reason and the way out — a missing auth preset, or the http-origin-with-Cloud pair above.
2

Check the door answers

Start your app and ask the door who it is. It should refuse you, and say where to go and sign in.
response
That 401 is the door working. The www-authenticate header is how every MCP client discovers your sign-in without being told about it.
Checkpoint. A 401 with a www-authenticate header naming your own origin. A 404 means the app is not serving /api/vendo/mcp — check that init’s wire route landed. A 200 means something else is answering that path.If you are on the Cloud posture, two more answers are worth knowing. A 400 with "code":"validation" is Vendo Cloud refusing to provision your tenant, and it repeats the console’s own reason and docs link — read the message, it names the fix. A 503 with "code":"unavailable" is Cloud itself failing, not your wiring.
3

Connect an outside agent

Your users’ setup page ships with the door. Open it in a browser:
It carries the copy-paste config for Claude, ChatGPT and Cursor. Paste one in, sign in with your app’s own login when the client bounces you there, and ask it to do something.For Claude Code, the plugin is one command:
Checkpoint. The client lists your tools, and a call runs and comes back with your own data. Reads and writes both execute — nothing parks, and that is correct on a fresh install. The policy init writes to .vendo/policy.json asks on one thing only, risk: destructive, and a young app often has no tool graded that way. Approvals are a policy you opt into, not a default the door imposes: edit that file when you want a call to wait for a person.If a tool answers not-implemented, that means a missing actAs — the door has no auth material to call your API with. A Cloud problem never looks like this; it answers 400 or 503 with its own reason, per Step 2. How the door works explains actAs.
4

Mint a token for your own backend

A backend agent has no browser to bounce through, so it asks your composition for a token instead. One method, two ways to call it.It exchanges a service key to do that. On Vendo Cloud the key is provisioned with your tenant and there is nothing to do. On the zero-key posture you declare one — npx vendo init --service-key writes it, and Service keys is the whole story. Without one, tokenFor says so and names the variable to set.Inside a request, pass the request — tokenFor reads who is signed in from the session cookie, the same way your own pages do:
app/api/agent/route.ts
Headless — a cron job, a queue worker, anything with no request in hand — pass the user id your own product spells:
jobs/nightly.ts
The token lasts ten minutes, is bound to that one person, and has no refresh path. Every call made with it runs as that user, through the same guard, policy and audit your own UI answers to. Under the hood it is an RFC 8693 urn:ietf:params:oauth:grant-type:token-exchange, which you never have to spell yourself.A subject that is not a real id — blank, null, undefined, or not a string at all — is refused at mint rather than handing you a token that dies on the first tool call.
Checkpoint. tokenFor returns a token string. Its shape tells you which posture minted it: vmat_… from your own door on the local posture, a raw JWT (eyJhbG…) from the broker on Vendo Cloud. Both are correct — feed it to a stock MCP client — Your own agent is the whole loop — and the call lands in your product under the user you named.
One thing to pass on that first call. Your client abandons a tools/call after 60 seconds by default, and generating a screen with vendo_make routinely runs longer. The door beats notifications/progress every 15 seconds, but the SDK extends the deadline only when you asked it to — so a client that receives every frame still gives up at 60 seconds. Ask for both: onprogress is what puts the progress token on the request, and resetTimeoutOnProgress is what makes the frames count.

Who runs the sign-in

The door needs an OAuth authorization server in front of it. There are two answers, and the door’s URL is the same either way — it derives from VENDO_BASE_URL and never from a broker, so switching later invalidates nothing your users already configured. Local — your app serves its own OAuth (default, no key). Everything above already did this. Your app is the authorization server, your login is the sign-in, and nothing leaves your origin. Zero config, and it works on http, so this is the posture for local development. Vendo Cloud broker (one answer). With a VENDO_API_KEY in hand, init asks one more question — How should outside agents sign in? — and answering Vendo Cloud broker moves the OAuth surface off your domain to a stable yourcompany.mcp.vendo.run tenant that Vendo provisions on first use. There is nothing to copy: no console visit, no environment values, no keys. It needs an https VENDO_BASE_URL, per the warning in Step 1. To front the door with a broker you run yourself, set both values where you deploy. An explicit override always wins over the Cloud default.
.env
Upgrading a deployment that already has VENDO_API_KEY and mcp: true moves its door from local to Cloud-brokered — declare those two variables yourself, or pass mcp.serviceAuth, to keep it where it is.

When you deploy

Set VENDO_BASE_URL on your platform to the public origin your app answers on. Discovery, issuer and token audience all derive from it, and a door pointed at the wrong origin surfaces hours later as “Claude can’t find my server”.

Where to go next

How the door works

Who is calling, how a call reaches your host tools, and what comes back.mcp + oauth + actAs

Your own agent

The stock MCP client, one session per conversation, and the whole loop.tokenFor

Service keys & broker

Where the key lives, the audit’s svc: attribution, and rotation.svc:<hash8>