Connect other services
Give an agent a SaaS service's tools by naming it — per tenant, OAuth once, secrets the model never sees.
A connection is a named external service whose tools an agent may use. Declare connections: ["linear"] on an agent and every turn gets that service's tools, namespaced, for the tenant the turn runs as — plus a gated connect_linear step for a user who has not authorised it yet. The point is what you don't write: no per-tool wiring, no token handling in agent code, no per-tenant plumbing.
What it makes possible. An agent with Linear, Gmail and Airtable tools by listing three names; user-completed OAuth once, then tools that just work; mutating third-party tools that arrive approval-gated from their own annotations; a per-tenant SSRF allowlist; one org's grant that can never serve another's tool.
Connections — MCP and OpenAPI tools
A connection registry turns an agent's connections: ["linear", "airtable"] into a per-turn tool source: each authorized connection's tools namespaced <name>_<tool>, plus a gated connect_<name> tool for any that still need OAuth.
createRuntime({
// …
connections: {
async open(principal, names) {
/* → OpenToolSet */
},
},
});@intentface/latch-mcp implements this over MCP servers and adds two guards core doesn't have: a per-tenant SSRF allowlist (an unlisted host throws before a socket opens) and vault-brokered auth (bearer tokens resolved per tenant, never in agent config). @intentface/latch-openapi does the same for OpenAPI specs.
Helpers for composing sources by hand:
import { mergeToolSources, mergeConnections } from "@intentface/latch-core";Vault: secrets the model never sees
Connections keep each user's tokens in the vault, and your own bearer connections read keys from it:
import { createVault } from "@intentface/latch-core";
const vault = createVault<Principal>({ store, masterKey });
createMcpConnections({ vault, secrets: vault.resolver /* … */ });Setting it up, whose secret is whose, putting keys in from your settings page, and using them in your own tools: Secrets (the vault).
OAuth, end to end
- A connection the user hasn't authorised exposes none of its tools yet — only a gated
connect_<name>tool. When the agent calls it, the turn parks like an approval (Approvals). - The client sends the user to
GET /connections/:name/authorize. The principal is pinned into the flow state here — never re-resolved from the callback request, so the grant cannot land on whoever completes the redirect. - The provider redirects to the callback; it finishes the exchange, stores the token in the vault under that principal, and redirects to
returnTo. - The client approves the parked
connect_<name>and the turn continues with the tools available. The chat UI has the exact steps.
Next
- Guide: gate a tool behind approval — the same pause the connect step uses
- Reference: packages — latch-mcp, latch-openapi
- Who owns a conversation — how a principal reaches a tool and its credential