The picture
Acme pastes an MCP URL and a token into your admin screen. From the next request, Acme’s users have Acme’s tools. Globex’s users never see them — not because a filter hid them, but because Globex is served a different tool registry. There is no Vendo-hosted UI here. The API is server-side, and the screen around it is yours.Registering
register is save-and-test in one call: it validates by actually connecting, and answers with the tools the server really advertised.
url then names the API’s base URL.
How you wire it
The screen is yours and so is the route behind it. Vendo supplies the registration API and nothing else — who may administer an org is a question your own auth answers. Start with a form that has noorg field, because the org is not the browser’s to name:
app/settings/connectors/page.tsx
app/api/tenant-connectors/route.ts
memberships seam names the org that owns it.
app/api/vendo/[...vendo]/route.ts
Who sees what
Visibility follows the orgs your host asserts for the request — the samememberships seam the rest of Vendo reads. A run that asserts acme is served a registry carrying your tools plus Acme’s; a run that asserts nothing is served the shared registry, unchanged.
The isolation is structural. Another tenant’s connector is not withheld from that registry, it was never in it — so there is no filter that can be got wrong, and no listing that can leak a name.
Without a memberships seam no request can name an org, so nothing here can ever
bind. The boot block says so: the
tenants row appears only once the seam is wired.The token
The pasted token is vaulted in your store’s encrypted secrets under a tenant-scoped name, and it is never readable back on any surface.list answers metadata only.
VENDO_STORE_ENCRYPTION_KEY (base64, 32 bytes) is what turns it on. If you pass your own store, you configure it there yourself — createStore({ url, encryption: { key: ... } }) — because an explicitly passed store owns its own secrets config, and the environment variable never reaches it.
With no key at all, what happens next depends on which store composed, and the two answers differ.
A store Vendo composes gets the development allowance automatically. A store you pass does not: it owns its own secrets config, so it refuses until you give it a key — or opt in explicitly with
createStore({ url, allowUnencryptedSecrets: true }), which is a development convenience and nothing more.
A tokenless registration is unaffected either way: it stores no secret, so it needs no vault.
Checking and removing
test re-runs the same live handshake register did, which is what an admin screen’s status dot should call.
remove drops the registration and its vaulted token, and the tools are gone from the next request onward.
What this is not
Not per-user credentials
One token per tenant, shared by that tenant’s users. For a credential per
person, use connected accounts.
Not an OAuth flow
A pasted token, not a consent screen against the tenant’s server.