GenerateSaaS

AI integrations

The backend-owned catalog of third-party services a signed-in user connects once, so chat, automated runs and desktop agents can all act on them as tools.

Integrations are the third-party services the AI acts on for a signed-in user: an HTTP API the backend calls, or a hosted MCP server it dials. The backend owns every part - it validates credentials, stores them encrypted, probes health, and turns each connection into agent tools - so the web app and the desktop app are two screens onto one system.

Every generated app offers two built-in connectors out of the box - API for any HTTP API, MCP Server for any streamable-HTTP server - so a user can connect something on day one. Name your own services by adding catalog entries in packages/config/src/ai/integrations.ts: those lead the picker, one sharing a built-in's id replaces it, and config.ai.integrations.genericConnectors: false offers the curated entries alone.

The two kinds

kindWhat it connectsHow it becomes tools
apiAn HTTP API the backend calls.One request tool per connection: any HTTP method (GET by default) with an optional JSON body.
mcpA streamable-HTTP MCP server the backend dials.One tool per server tool, advertised in the mcp_ namespace (the model calls mcp_search, not search), from a per-connection cache re-synced every 15 minutes.

A stdio MCP server is not an integration - it runs on the user's machine, which no backend can dial. Users add those to their own coding CLI, and your backend never sees them.

How a user connects

Integrations ride config.ai.enabled: their routes sit behind the AI router's guard, so they are inert when AI is off.

The user opens /settings/integrations on the web, or the Integrations tab in the desktop app, and picks a catalog entry.
They fill its credential fields. A sign-in entry has none to fill: it connects only through its own OAuth flow, which on desktop opens in the system browser.
They choose the scope, sign-in entries included. The picker lists only the scopes legal for that entry here, narrowest first.
The credentials are validated before anything is saved, so the form confirms the account it reached instead of storing a dead key.
The connection is stored encrypted server-side and never returned to any client. Its card carries the account identity, a health dot, its scope, who connected it, and - for mcp - the tool count.

Afterwards a connection can be renamed any time, and its credentials replaced as a deliberate opt-in through the same validation gate a first connect passes. A sign-in connection is rename-only - its credentials are a minted token - and scope never changes. The edit controls appear only for someone who may manage that row.

Every connection is testable. An api entry's declared health block verifies the credentials and may name the account - identityPath is a dot-path into the JSON response (user.email, or siteEntry.0.siteUrl to index an array) - and an entry without one still gets a reachability check. Either way the verdict is three-way: reachable, key refused (401/403), or unreachable, so a wrong key never reads as an outage.

An mcp probe is a successful tools/list, and the server's advertised name becomes its identity. Its verdict is two-way by design: the MCP client reports one opaque failure for a dead host and a refused header alike, and the dialer will not echo upstream error text that can carry the credential. Both re-check on demand from the card.

A failed MCP sync keeps the previously cached tools and records only the health failure, and a live server that answers with an empty list keeps them too - saying so on the card ("reachable but listed no tools, previous tools kept") rather than silently emptying a working toolset. After a failure the automatic re-dial waits 5 minutes; the manual re-check on the card ignores that cool-down.

Where a connection lives

Every connection is placed at one scope - a project, an organization, or the connector alone - which decides who can use it and therefore whose agents get it as a tool. Which scopes a deployment offers follows from its projects and tenancy flags, and an entry can narrow that further.

What the agents get

Every callable connection becomes tools in the one shared toolset assembly, so chat, automated runs and the desktop app's agents see the same set with no per-surface wiring.

Three invariants hold on every call, enforced in @repo/ai/integrations: the model supplies a request path, never a URL, host-locked to the connection's fixed origin, so a prompt-injected model cannot retarget the request or the credential riding on it; credentials resolve fresh per call and fail closed - a connection that no longer decrypts yields an error string, never a secret; and every outbound request passes the SSRF guard with redirects refused.

On this page