Skip to main content

Connectors SDK

gaia.connectors is GAIA’s external-integration layer. It manages two kinds of connectors:
  • OAuth (type oauth_pkce) — user-authorized flows (Google, etc.). Stores refresh tokens in the OS keyring.
  • MCP server (type mcp_server) — API-key-based connections to third-party MCP servers (GitHub, Brave Search, etc.). Stores keys in the OS keyring as $keyring references.
Three caller surfaces share the same keyring and grants file: SDK (direct Python import), CLI (gaia connectors …), and Agent UI (Settings → Connectors page).

Catalog

The catalog is populated at import time by gaia.connectors.catalog. Every connector is a ConnectorSpec:
Catalog entries cover 23 connectors across core, productivity, dev, and search tiers.

SDK use — OAuth

get_access_token raises AuthRequiredError on four failure modes:

Forwarded connections (no re-auth)

When a host app has already authenticated the user (its own OAuth flow), it can FORWARD that connection to GAIA instead of triggering a second consent screen. GAIA persists the forwarded grant and refreshes as the host app’s client — credential forwarding, not a GAIA-run OAuth flow. The host app forwards three things: the OAuth client it was issued under (client_id + client_secret) and the user’s long-lived refresh_token.

REST: POST /v1/connections/{provider}

The same ingestion is exposed over REST by the Agent UI server (which binds localhost by default and gates remote/tunnel access behind a token). Mutating routes require the X-Gaia-UI: 1 header.
After a successful POST the agent resolves the connection ambiently — the triage request carries no credentials; get_access_token reads the persisted grant and refreshes transparently.
The host app’s initial consent must request the UNION of every scope both it and GAIA need. GAIA refreshes with the forwarded refresh_token and cannot widen scope at refresh time — a refresh only ever returns the scopes the original consent granted. If the forwarded scopes don’t cover what the target agent needs (for the Email agent: gmail.modify, gmail.send, and a calendar scope), the import fails loudly with ScopeMismatchError (HTTP 403 + missing_scopes) rather than silently under-provisioning. Re-consent by the host app — with the wider scope set — is the only fix.
GAIA refuses to persist tokens to an insecure keyring backend (plaintext / weak file-backed stores). On such a backend the import fails loudly with an actionable error pointing at the system credential-store setup. Forwarded secrets are never written to ~/.gaia/ and never echoed in any response.

SDK use — MCP server

MCP server connectors are configured once and then provide their API keys via the get_credential dispatcher:
The MCP bridge injects these credentials as environment variables when it launches the MCP server process.

CLI use

Agent-author guide

Declare the connectors your agent needs as REQUIRED_CONNECTORS:
The Agent UI consent dialog renders reason in plain language. After the user grants the scopes, subsequent get_access_token_sync calls return fresh tokens transparently. If instead your agent loads MCP servers dynamically at runtime (from ~/.gaia/mcp_servers.json) rather than declaring specific connectors, set CONSUMES_MCP_SERVERS = True so the Settings → Connectors “Active for” panel lists it for every mcp_server connector:

Per-agent activation (MCP servers only)

Activations are the second axis of the per-agent authorization model (issue #1005) and apply to mcp_server connectors only:
  • Grant (grants.json) — agent X is allowed to use connector Y’s credentials with scopes S. Gates credential access (get_access_token). Applies to every connector type.
  • Activation (activations.json) — agent X currently has MCP connector Y’s tools surfaced in its toolset. Gates MCP tool visibility (MCPClientManager.tools_for_agent).
A tool from an MCP connector is visible to an agent if and only if:
Activations default to inactive when absent — least-privilege opt-in. The ledger stores explicit true/false entries; an unknown pair returns is_agent_active == False.
OAuth connectors do not accept activations. Agents reach OAuth providers (Google, etc.) through native Python @tool functions that call get_credential_sync directly — there is no MCP tool surface to gate. Per-agent access for OAuth connectors is controlled entirely by the per-scope grant toggles above.Calling activate() / deactivate() for an OAuth connector raises ConfigurationError; the HTTP PUT / DELETE returns 400 Bad Request; the CLI exits with code 3. The Agent UI hides the “Active for” section for OAuth tiles for the same reason.
One-click activation: activating an agent with no prior grant auto-creates a grant using the agent’s declared REQUIRED_CONNECTORS scopes. This is the path Settings → Connectors → “Active for” takes when the user ticks an agent that has never been granted access:

Which agents appear in the “Active for” panel

For an mcp_server connector the panel lists agents from two sources:
  1. Agents that declare the connector in REQUIRED_CONNECTORS (static).
  2. Agents that set CONSUMES_MCP_SERVERS = True — they load MCP servers dynamically at runtime (from ~/.gaia/mcp_servers.json) and gate the tools through this same activation ledger, so they can use any MCP connector once activated. builtin:chat is the canonical example.
Category (2) agents declare no scopes of their own, so one-click activation auto-grants the canonical use scope. The registry surfaces the flag on each agent as consumes_mcp_servers. OAuth connectors have no activatable agents — the panel is hidden for them entirely. The HTTP router exposes:
Both mutating routes reject non-mcp_server connectors with 400 Bad Request (after the standard X-Gaia-UI CSRF check). Every activation change emits the SSE event connector.activation.changed ({connector_id, agent_id, active}) so an open Agent UI Settings tab refreshes the “Active for” state without a manual reload. The emit is centralized in api.activate / api.deactivate, so HTTP, SDK, and CLI callers all notify through one path. Because the CLI runs in a separate process from the UI server, the server also runs a background watcher on activations.json that emits the same event (within ~1 s) when an out-of-process writer changes the ledger. Disconnecting a connector wipes both grants and activations for that connector — re-adding the same connector_id must not silently inherit the previous user’s tool-visibility decisions.

Where things live

Adding a new OAuth provider

  1. Create src/gaia/connectors/providers/<name>.py satisfying the OAuthProvider protocol (auth/token URLs, client env vars, etc.).
  2. Register in src/gaia/connectors/providers/__init__.py:get.
  3. Add a ConnectorSpec entry in src/gaia/connectors/catalog.py.
  4. Add unit tests under tests/unit/connectors/test_providers.py.

Adding a new MCP server connector

Add one entry to the _MCP_CATALOG list in src/gaia/connectors/catalog.py:
The MCP bridge will inject MY_SERVICE_API_KEY from the keyring as an environment variable when launching the server process.