Skip to main content
External connector tools (Gmail, Slack, GitHub, and anything else exposed through Composio) execute as the signed-in user, not as one shared bot account. Each user connects their own account once, and every later tool call rides that connection. Composio is the sole broker. Vendo builds no OAuth flows of its own. A connection is a Composio connected account whose entityId is the Vendo principal subject, so isolation is structural: every list, initiate, and disconnect operation is scoped to exactly one subject. No token ever enters Vendo — the broker holds the credentials, and initiate hands back only the hosted redirect URL.

Setup

You have two options: bring your own Composio key, or ride the Vendo Cloud broker. Bring your own key. Pass composioConnector to createVendo:
Vendo Cloud broker. Set VENDO_API_KEY and ship no Composio key of your own. Connection endpoints then ride the Vendo Cloud broker using Vendo’s Composio credentials, so cloud users bring zero keys. A BYO composioConnector always wins when both are configured, because connections must live where the connector’s tools execute. GET /api/vendo/status reports the active posture under blocks.connections: "byo", "cloud", or false when neither path is wired.

The in-flow connect card

When a tool call needs a connection the user does not have yet, the connector returns a typed connect-required outcome instead of an opaque error. The shipped VendoThread renders an inline connect card next to the tool call, following the same pattern as approvals:
  1. The user selects Connect. The card opens the broker’s hosted OAuth page in a popup.
  2. The card polls the connection until it becomes active.
  3. The thread retries the original tool call through the fresh connection — no re-prompting required.
Automations and MCP door calls do not have a browser surface, so they surface connect-required as an actionable in-band error that names the connector and toolkit the user needs to connect.

The settings panel

ConnectedAccountsPanel (exported from @vendoai/ui/chrome) lists a user’s connected accounts and disconnects them. Disconnect severs the account on the broker. VendoPage chrome mounts the panel automatically under a new Accounts tab. If you render your own chrome, drop the panel in wherever you already handle user settings, or read the same state through the useConnections hook from @vendoai/ui.

Connection endpoints

Every connection route is per-principal. The wire uses exactly the resolved principal’s subject — there is no caller-supplied subject and no cross-user read. Anonymous (ephemeral) visitors cannot initiate a connection — an external account would outlive the session it was minted for. Synthetic subjects (webhook principals, reserved webhook: and vendo: prefixes) are refused for the same reason.

Risk labels for Composio tools

Composio tools now carry a curated risk instead of the previous blanket write default. The connector resolves risk in three steps:
  1. Metadata hints. Composio destructiveHint and readOnlyHint tags take precedence when present.
  2. Slug patterns. Destructive verbs anywhere in the slug (DELETE, REMOVE, DESTROY, and similar) mark the tool destructive. Leading read verbs (GET, LIST, FETCH, and similar) mark it read.
  3. Conservative default. Anything else falls back to write.
.vendo/overrides.json still wins over the curated map, so you can pin risk per tool.

MCP connector credentials

mcpConnector({ headers }) accepts either shared static headers (the simple default) or an async per-principal resolver. Use the resolver when the MCP server binds authentication to the session and you need each user to get their own MCP session:
With a resolver, each subject gets its own cached MCP session (bounded by an LRU of 500 subjects). Descriptor listing still resolves without a principal, so tool discovery keeps working. Every connector execution is audited with its account identity — connector, toolkit, entityId, and whether the credential was shared or per-principal — in the tool-call event’s detail. The identity is stripped from the outcome the model and UI see, so no broker identifier leaks into a message.