GenerateSaaS

AI integrations

The shared integration catalog the web AI can act on, its entry anatomy, and how a connector attaches.

Integrations are the third-party services the AI can act on for a signed-in user - an HTTP API, an OAuth provider, or an MCP server. The catalog is one module, packages/config/src/ai/integrations.ts (@repo/config/ai/integrations), and it ships empty: add an entry to offer a connector. The module's JSDoc carries a copyable shape for each kind.

An entry

Each entry is an AiIntegrationSpec - pure, non-secret metadata (no credentials, no tokens). It drives the connect form and decides whether the model may call the connector.

FieldPurpose
idStable catalog id that stored connections reference. Letters, digits, _ or - only - it can become an AI tool name (see below).
nameDisplay name in the picker.
kindapi | oauth | mcp | cli - see below.
descriptionOne-line picker copy (optional).
credentialsThe connect-form schema: { key, label, type: "text" | "password" | "url", required } per field.
oauthThe OAuth descriptor - required for oauth, absent otherwise.
callableWhether the model may call it as a tool. A non-callable entry is still connectable, just never advertised as a tool.
mutatingMarks a write-capable tool (optional; omitted means read-only).
maxInstancesCap on connected instances (optional; 1 forces a single connection).
surfaces("web" | "desktop")[] - which surfaces offer it (optional; omitted means both).

An id can become a tool name, so name every one like an identifier. A single-connection oauth connector's tool is literally <id>_request. (An api connector's is api_request, and once a user connects several instances of one integration, both kinds are named from that user's label instead - store_main__api_request.) Wherever the id does surface, it leaves your server: when a user runs your app's AI through their own coding CLI, tool names are joined into one comma-separated allowlist the CLI reads as a list of permission rules. An id like acme,Bash would, absent the daemon's own guard, pre-approve unprompted shell execution on that user's machine; the daemon refuses such a session, so what it really does - like a merely exotic id (acme.crm, Google Search Console) - is silently break every terminal session. The catalog is charset-checked where it is read, and every declared id is checked, so a bad one fails the app at boot - loudly, in front of you, rather than quietly on a user's machine.

The four kinds

kindWhat it connectsOn the web
apiAn HTTP API reached by a base URL and an API key (sent as a Bearer header).Callable.
oauthA sign-in provider authorized by an OAuth-2 authorization-code + PKCE flow.Callable once the user signs in - the server mints and auto-refreshes the token.
mcpA Model Context Protocol server reached by command or URL.Connectable, never callable - MCP execution is desktop-only.
cliA local command run as a tool.Hidden - the web has no machine to run a command on.

OAuth entries

An oauth entry carries an AiIntegrationOAuthDescriptor. It holds only static, non-secret values; the buyer's client credentials are referenced by env var name, never stored here.

FieldValue
authUrl / tokenUrlThe provider's authorization and token endpoints.
scopesThe scopes the sign-in requests.
clientIdEnv / clientSecretEnvThe env var names holding the buyer's OAuth client id and secret.
apiBaseUrlThe fixed provider origin the bearer token is host-locked to - the callable tool refuses any request path that resolves off this origin.

How the web attaches a connector

Metadata alone renders the connect form; the executable half lives in the API:

  • Tool builders (packages/api/src/ai/integration-tools.ts) turn the two server-executable kinds (api, oauth) into AI-SDK tools per connection; mcp and cli are skipped. A tool sends any HTTP method (GET by default) with an optional JSON body, so the model can use the whole API - reports that POST, records that PUT - not just reads.
  • The OAuth flow runs through the integrations router (packages/api/src/routes/internal/ai/integrations-router.ts) and the flow helpers (packages/api/src/ai/oauth-flow.ts), which mint and auto-refresh the token per user.
  • Shared security helpers (@repo/ai/integrations) enforce the two invariants every tool upholds: the model supplies a request path (plus method and body), never a full URL - the path is host-locked to the connection's fixed origin so a prompt-injected model can never retarget the request; and credentials are resolved fresh per call, fail-closed - a connection that no longer decrypts yields an error string, never a throw and never a secret.

Gating

Web integrations ride config.ai.enabled - the integrations routes sit behind the AI router's guard, so they are inert when AI is off.

How users connect one

End-users connect an integration on the Integrations tab in Settings (/settings/integrations): they pick a catalog entry and fill its credential fields (or sign in, for oauth), and the connection is stored server-side, never returned to the client. Once connected, its tools are advertised to chat and scheduled runs.

On this page