Appearance
AI Connection Tools (Per Instance)
Connection tools let an AI agent call actions on specific connection instances — for example, send an email from a specific Gmail account, list events from a specific Google Calendar, or send a Telegram message via a specific bot token.
Unlike internal tools, which use a static catalog of Operations CRUD actions, connection tools are bound per connection instance. Each connection the operator grants to an agent gets its own set of namespaced tools.
What and Why
An AI agent needs access to external integrations (email, calendar, spreadsheets, messaging) to take meaningful action on behalf of users. Connection tools bridge that gap:
- An operator connects their external accounts via the Connections module (Gmail, Google Workspace, Telegram, SMTP).
- The operator opens the agent's Herramientas → Conexiones tab and selects which tools to enable for each connection instance.
- At chat time,
ConnectionsToolProvider.build()reads the grants fromai.agent_connection_toolsand injects the enabled tools into the Vercel AI SDKToolSet, namespaced by connection slug.
Each granted connection gets an independent, namespaced tool entry in the LLM's tool set — so an agent with two Gmail accounts gets work-gmail__send_gmail_message and personal-gmail__send_gmail_message as separate tools.
Architecture
graph TD
subgraph HTTP Layer
CTL["AgentConnectionToolsController\nGET catalog / PUT upsert"]
end
subgraph Application Layer
QH["GetAgentConnectionToolCatalogHandler"]
CH["UpsertAgentConnectionToolsHandler"]
CTP["ConnectionsToolProvider\n(IToolProvider)"]
end
subgraph Infrastructure Layer
REPO["AgentConnectionToolRepositoryImpl\n(ai.agent_connection_tools)"]
CONN_REPO["IConnectionRepository"]
end
subgraph Tool Map
TPM["tool-provider-map.ts\nTOOL_TO_PROVIDERS + slugify + computeSlugs"]
end
CTL --> QH
CTL --> CH
CH --> REPO
QH --> CONN_REPO
QH --> REPO
QH --> TPM
CTP --> REPO
CTP --> CONN_REPO
CTP --> TPM
Key design decisions:
- The
connectionIdis captured in a closure at build time — it never appears in the LLM-facing Zod input schema. - Authorization context (
orgId,userId) also comes from the verified JWT, never from LLM input. ConnectionsToolProvideris a top-levelIToolProvider(not an internal-tools provider). It runs independently of theInternalToolProviderpipeline.
Tool Naming Convention
Every tool exposed to the LLM is named as:
<slug>__<toolName>Where <slug> is derived from the connection's name field:
- Lowercased, non-alphanumeric characters replaced with
-, leading/trailing dashes stripped. - If two connections produce the same base slug, subsequent ones get a numeric suffix:
-2,-3, etc. (ordered bycreated_at ASC, id ASC). - The first occurrence always keeps the bare slug.
Examples:
| Connection name | Slug |
|---|---|
Work Gmail | work-gmail |
Work Gmail (second connection with same name) | work-gmail-2 |
My Bot Token | my-bot-token |
The full namespaced tool seen by the LLM:
work-gmail__send_gmail_message
work-gmail__list_calendar_events
my-bot-token__send_telegram_messageThe slug the LLM sees in the tool set is the same slug returned by the GET catalog endpoint. The panel UI also shows the slug pill so operators know exactly how the tool will be named.
Tool Catalog
The 30 available tools are organized by provider type:
Telegram
| Tool Name | Description |
|---|---|
send_telegram_message | Send a message to a Telegram chat |
SMTP
| Tool Name | Description |
|---|---|
send_smtp_email | Send an email via SMTP |
Google Gmail (google_gmail)
| Tool Name | Description |
|---|---|
send_gmail_message | Send a Gmail message |
list_gmail_messages | List Gmail messages |
get_gmail_message | Get the body of a Gmail message |
reply_gmail_message | Reply to a Gmail thread |
create_gmail_draft | Create a Gmail draft |
get_thread | Retrieve a full Gmail conversation thread by threadId |
search_threads | Search Gmail threads using Gmail search syntax |
list_drafts | List Gmail drafts for the authenticated account |
list_labels | List all Gmail labels (system + user-created) |
label_message | Add labels to a Gmail message |
unlabel_message | Remove labels from a Gmail message |
label_thread | Add labels to an entire Gmail thread |
unlabel_thread | Remove labels from an entire Gmail thread |
Google Workspace (google_workspace) — Gmail capability
| Tool Name | Description |
|---|---|
send_gmail_message | Send a Gmail message |
list_gmail_messages | List Gmail messages |
get_gmail_message | Get the body of a Gmail message |
reply_gmail_message | Reply to a Gmail thread |
create_gmail_draft | Create a Gmail draft |
get_thread | Retrieve a full Gmail conversation thread by threadId |
search_threads | Search Gmail threads using Gmail search syntax |
list_drafts | List Gmail drafts for the authenticated account |
list_labels | List all Gmail labels (system + user-created) |
label_message | Add labels to a Gmail message |
unlabel_message | Remove labels from a Gmail message |
label_thread | Add labels to an entire Gmail thread |
unlabel_thread | Remove labels from an entire Gmail thread |
Google Workspace — Sheets capability
| Tool Name | Description |
|---|---|
list_spreadsheets | List Google Sheets |
get_spreadsheet | Get spreadsheet metadata |
read_spreadsheet_values | Read values from a range |
write_spreadsheet_values | Write values to a range |
append_spreadsheet_values | Append rows to a spreadsheet |
Google Workspace — Calendar capability
| Tool Name | Description |
|---|---|
list_calendar_events | List calendar events |
create_calendar_event | Create a calendar event |
update_calendar_event | Update a calendar event |
delete_calendar_event | Delete a calendar event |
get_calendar_event | Get a specific calendar event |
list_calendars | List calendars accessible to the authenticated account |
find_free_time | Find free/busy time windows across one or more calendars |
respond_to_event | Respond (accept / tentative / decline) to a calendar event invitation |
Acting identity for
respond_to_event: The attendee whose response is updated is matched against the OAuth account's email (the email of the user who authorized the Google connection). The LLM cannot respond on behalf of other attendees — if the OAuth account is not in the event's attendee list, the call fails.
A single google_workspace connection can have tools from all three capability groups enabled simultaneously. The panel groups them visually by capability (Gmail / Sheets / Calendar), but they are stored in a single enabledTools array on one grant row.
Configuring Connection Tools (Panel)
- Open the AI agent edit dialog and navigate to step 7 Herramientas.
- Click the Conexiones tab.
- The catalog lists all connections accessible to the authenticated user in the org. Each card shows:
- Connection name and provider badge.
- Slug pill (this is the prefix that will appear in the LLM tool set).
- Tool checkboxes (flat list for Telegram/SMTP/Gmail; grouped by capability for Google Workspace).
- Toggle the desired tools, then click Guardar to persist the full grant set.
Clicking Guardar performs a full-diff replace — it sends the complete current selection for every connection. There is no partial-save API.
Runtime Auth Semantics
At chat time, ConnectionsToolProvider.build() is called once per request. For each granted connection:
- The connection is loaded from the repository using the
connectionIdcaptured at build time. - Accessibility is checked: the connection must still exist and be visible to the executing user (
userIdfrom JWT). - If the connection is no longer accessible (deleted, revoked, or visibility lost), the tool is not injected into the tool set for that request — the LLM will not see it.
- If the connection passes the pre-flight check, each enabled tool is injected with its
executeclosure performing a runtime visibility re-check before dispatching. If the check fails at execution time, the tool returns{ success: false, error: "Connection not accessible" }to the LLM — no HTTP 500, no silent success.
This means connection grants are evaluated at call time, not at agent-save time.
Data Model
Connection tool grants are stored in ai.agent_connection_tools:
| Column | Type | Notes |
|---|---|---|
id | UUID v7 | Primary key |
agent_id | UUID | FK → ai.agents(id) ON DELETE CASCADE |
connection_id | UUID | FK → connections.connections(id) ON DELETE CASCADE |
enabled_tools | JSONB | Non-empty array of tool names |
created_at / updated_at | timestamptz | Managed by TypeORM |
Unique constraint on (agent_id, connection_id) — one grant row per connection per agent.
Migration Note
The migration for agent_connection_tools also removes stale data from the previous approach:
sql
DELETE FROM "ai"."agent_internal_tools" WHERE "provider_key" = 'connections';Any agent_internal_tools rows with provider_key = 'connections' that existed before this feature are no longer valid — connections are no longer an internal-tools provider. See AI Internal Tools for details on what the internal-tools system covers today.
API Reference
See Connection Tools API Reference for the full endpoint documentation.
Deferred / Out of Scope
findManyByIdsoptimization: the provider currently uses a per-connection-id lookup (N+1) instead of a batch query. This is correct but not optimal; afindManyByIdsmethod onIConnectionRepositorywould eliminate the N+1 for agents with many connection grants.- Per-tool CASL permission checks and audit trail for tool-triggered mutations are planned for a future iteration.