airbyte.mcp.agents
Airbyte Agents MCP operations.
⚠️ Experimental Tools — Insiders Only
The Airbyte Agents MCP tools are experimental and hidden by default. They are advertised only when insiders mode is enabled (
AIRBYTE_MCP_INSIDERSfor stdio servers,X-MCP-Insidersfor hosted servers) or when the include-modules setting explicitly namesagents. Tool names, arguments, and result shapes may change or be removed without notice between minor versions of PyAirbyte. Pin an exact PyAirbyte version if you depend on them.These tools are also Cloud-only: they are hidden whenever
AIRBYTE_CLOUD_API_URL/AIRBYTE_CLOUD_CONFIG_API_URLare overridden, unlessAIRBYTE_AGENTS_API_URLis set.
agents module
MCP primitives registered by the agents module of the airbyte-mcp server: 7 tool(s), 0 prompt(s), 0 resource(s).
Tools (7)
execute_agent_connector
Hints: open-world
Availability: experimental, insiders only (AIRBYTE_MCP_INSIDERS=1 for stdio, X-MCP-Insiders: 1 for hosted servers, or name the module in the include-modules setting; AIRBYTE_MCP_INSIDERS=0 disables it regardless).
Execute a single action against an Airbyte Agents connector, including writes.
Prefer `execute_agent_connector_ro` when only reading, since it is available in
read-only mode. Entity types and actions are connector-specific, so call
`inspect_agent_connector` first. The connector must belong to the given workspace.
The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting to a hosted MCP server, provide a bearer token via the Authorization header, or client credentials via the transport Client-Id and Client-Secret headers. For local or stdio connections, set the AIRBYTE_CLOUD_BEARER_TOKEN environment variable, or both AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET. Call list_agent_connectors to discover connector IDs, then inspect_agent_connector to learn which entities a connector supports, before calling execute_agent_connector. Use list_agent_skills to discover skills, and pass the docs.skill_id reported by inspect_agent_connector to read_agent_skill_docs for connector usage docs.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
connector_id |
string |
yes | — | The ID of the Airbyte Agents connector. |
entity_type |
string |
yes | — | The type of entity to act on, for example 'issues'. Call inspect_agent_connector to see the entity types a connector supports. |
action |
enum("list", "get", "search", "sql_select") | enum("create", "update", "delete") |
yes | — | The action to run against the entity type. The search action is the connector's native API search, parallel to get and list. For sql_select, pass sql and sql_dialect (snowflake or bigquery) in api_args and any value for entity_type. The download action is deliberately absent because it returns a binary stream rather than JSON. |
api_args |
object | string | null |
no | null |
Connector-specific arguments for the action, as an object or a JSON object string. For example {'repository': 'airbytehq/PyAirbyte'}. |
select_fields |
array<string> | string | null |
no | null |
Fields to keep in the response, as a list or a CSV string. |
exclude_fields |
array<string> | string | null |
no | null |
Fields to drop from the response, as a list or a CSV string. |
page_size |
integer | null |
no | null |
Maximum number of entities to return in this page. Honored by Context Store search actions (sent as limit). Direct connector actions take their own page-size argument, if any, in api_args. |
cursor |
string | null |
no | null |
Pagination cursor for Context Store search actions and sql_select, taken from end_cursor of a previous result. Direct connector actions such as list do not read this; pass their own cursor argument in api_args instead (for example GitHub's after), as named in the skill docs. |
intent |
string | null |
no | null |
A short description of why the action is being run. |
read_only |
boolean | null |
no | null |
Set to true to reject write actions before any request is sent, when the caller wants a read guarantee from this tool. |
workspace_id |
string | null |
no | null |
Workspace ID. Hosted MCP connections pass it via the X-Airbyte-Workspace-Id header; local or stdio connections use the AIRBYTE_CLOUD_WORKSPACE_ID environment variable. |
organization_id |
string | null |
no | null |
Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the X-Airbyte-Organization-Id header or the AIRBYTE_CLOUD_ORGANIZATION_ID environment variable. To discover organization IDs, call list_cloud_organizations to search organizations by name, or list_agent_workspaces, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted. |
Show input JSON schema
{
"additionalProperties": false,
"properties": {
"connector_id": {
"description": "The ID of the Airbyte Agents connector.",
"type": "string"
},
"entity_type": {
"description": "The type of entity to act on, for example 'issues'. Call `inspect_agent_connector` to see the entity types a connector supports.",
"type": "string"
},
"action": {
"anyOf": [
{
"description": "Connector actions that only read data.\n\nThe `search` action is the connector's native API search, parallel to `get` and `list`.\nThe `sql_select` action runs one read-only SQL statement (or `SHOW TABLES`) on the query\nengine behind a destination connector. Pass `sql` and `sql_dialect` (and optionally\n`dry_run`) in `api_args`; `entity_type` is ignored for this action.\n\nThe `download` action is deliberately absent even though it reads: it returns a binary\nstream rather than JSON, which PyAirbyte does not yet support.",
"enum": [
"list",
"get",
"search",
"sql_select"
],
"type": "string"
},
{
"description": "Connector actions that create, update, or delete data.",
"enum": [
"create",
"update",
"delete"
],
"type": "string"
}
],
"description": "The action to run against the entity type. The `search` action is the connector's native API search, parallel to `get` and `list`. For `sql_select`, pass `sql` and `sql_dialect` (snowflake or bigquery) in `api_args` and any value for `entity_type`. The `download` action is deliberately absent because it returns a binary stream rather than JSON."
},
"api_args": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Connector-specific arguments for the action, as an object or a JSON object string. For example {'repository': 'airbytehq/PyAirbyte'}."
},
"select_fields": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Fields to keep in the response, as a list or a CSV string."
},
"exclude_fields": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Fields to drop from the response, as a list or a CSV string."
},
"page_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Maximum number of entities to return in this page. Honored by Context Store `search` actions (sent as `limit`). Direct connector actions take their own page-size argument, if any, in `api_args`."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Pagination cursor for Context Store `search` actions and `sql_select`, taken from `end_cursor` of a previous result. Direct connector actions such as `list` do not read this; pass their own cursor argument in `api_args` instead (for example GitHub's `after`), as named in the skill docs."
},
"intent": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "A short description of why the action is being run."
},
"read_only": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Set to `true` to reject write actions before any request is sent, when the caller wants a read guarantee from this tool."
},
"workspace_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Workspace ID. Hosted MCP connections pass it via the `X-Airbyte-Workspace-Id` header; local or stdio connections use the `AIRBYTE_CLOUD_WORKSPACE_ID` environment variable."
},
"organization_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the `X-Airbyte-Organization-Id` header or the `AIRBYTE_CLOUD_ORGANIZATION_ID` environment variable. To discover organization IDs, call `list_cloud_organizations` to search organizations by name, or `list_agent_workspaces`, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted."
}
},
"required": [
"connector_id",
"entity_type",
"action"
],
"type": "object"
}
Show output JSON schema
{
"description": "Result of executing a single action against an Airbyte Agents connector.",
"properties": {
"status": {
"type": "string"
},
"result": {
"default": null,
"title": "Result"
},
"has_next_page": {
"default": false,
"type": "boolean"
},
"end_cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"execution_time_ms": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
},
"warning": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null
},
"message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"status"
],
"type": "object"
}
execute_agent_connector_ro
Hints: read-only · idempotent · open-world
Availability: experimental, insiders only (AIRBYTE_MCP_INSIDERS=1 for stdio, X-MCP-Insiders: 1 for hosted servers, or name the module in the include-modules setting; AIRBYTE_MCP_INSIDERS=0 disables it regardless).
Read data from an Airbyte Agents connector, without modifying anything.
This tool only accepts read actions, so it stays available in read-only mode. Use
`execute_agent_connector` for actions that create, update, or delete data. Entity types
are connector-specific, so call `inspect_agent_connector` first. The connector must
belong to the given workspace.
To query a destination:
1. Run `SHOW TABLES` to discover tables.
2. Run `SELECT * FROM <table> LIMIT 1` with `"dry_run": true` in `api_args` to get the
real column names; never guess them.
3. Select data using the discovered names.
Use the destination's `connector_id` and `sql_dialect` as reported by
`list_agent_connectors`. On Snowflake, identifiers are upper-cased; write them unquoted
(double-quoting makes them case-sensitive). Quote a name only when discovery returns it in
mixed or lower case, exactly as returned.
The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting to a hosted MCP server, provide a bearer token via the Authorization header, or client credentials via the transport Client-Id and Client-Secret headers. For local or stdio connections, set the AIRBYTE_CLOUD_BEARER_TOKEN environment variable, or both AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET. Call list_agent_connectors to discover connector IDs, then inspect_agent_connector to learn which entities a connector supports, before calling execute_agent_connector. Use list_agent_skills to discover skills, and pass the docs.skill_id reported by inspect_agent_connector to read_agent_skill_docs for connector usage docs.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
connector_id |
string |
yes | — | The ID of the Airbyte Agents connector, from list_agent_connectors. For sql_select, pass the connector_id of a destination entry (connector_kind="destination") from that listing. |
entity_type |
string |
yes | — | The type of entity to act on, for example 'issues'. Call inspect_agent_connector to see the entity types a connector supports. |
action |
enum("list", "get", "search", "sql_select") |
yes | — | The read action to run against the entity type. The search action is the connector's native API search, parallel to get and list. For sql_select, pass sql and sql_dialect (snowflake or bigquery) in api_args and any value for entity_type; the connector_id is a destination listed by list_agent_connectors; SHOW TABLES lists its tables and SELECT * FROM <table> LIMIT 1 with "dry_run": true in api_args returns its columns without reading rows; never guess columns. On Snowflake, write identifiers unquoted unless discovery returned mixed case. The download action is deliberately absent because it returns a binary stream rather than JSON. |
api_args |
object | string | null |
no | null |
Connector-specific arguments for the action, as an object or a JSON object string. For example {'repository': 'airbytehq/PyAirbyte'}. |
select_fields |
array<string> | string | null |
no | null |
Fields to keep in the response, as a list or a CSV string. |
exclude_fields |
array<string> | string | null |
no | null |
Fields to drop from the response, as a list or a CSV string. |
page_size |
integer | null |
no | null |
Maximum number of entities to return in this page. Honored by Context Store search actions (sent as limit). Direct connector actions take their own page-size argument, if any, in api_args. |
cursor |
string | null |
no | null |
Pagination cursor for Context Store search actions and sql_select, taken from end_cursor of a previous result. Direct connector actions such as list do not read this; pass their own cursor argument in api_args instead (for example GitHub's after), as named in the skill docs. |
intent |
string | null |
no | null |
A short description of why the action is being run. |
workspace_id |
string | null |
no | null |
Workspace ID. Hosted MCP connections pass it via the X-Airbyte-Workspace-Id header; local or stdio connections use the AIRBYTE_CLOUD_WORKSPACE_ID environment variable. |
organization_id |
string | null |
no | null |
Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the X-Airbyte-Organization-Id header or the AIRBYTE_CLOUD_ORGANIZATION_ID environment variable. To discover organization IDs, call list_cloud_organizations to search organizations by name, or list_agent_workspaces, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted. |
Show input JSON schema
{
"additionalProperties": false,
"properties": {
"connector_id": {
"description": "The ID of the Airbyte Agents connector, from `list_agent_connectors`. For `sql_select`, pass the `connector_id` of a destination entry (`connector_kind=\"destination\"`) from that listing.",
"type": "string"
},
"entity_type": {
"description": "The type of entity to act on, for example 'issues'. Call `inspect_agent_connector` to see the entity types a connector supports.",
"type": "string"
},
"action": {
"description": "The read action to run against the entity type. The `search` action is the connector's native API search, parallel to `get` and `list`. For `sql_select`, pass `sql` and `sql_dialect` (snowflake or bigquery) in `api_args` and any value for `entity_type`; the `connector_id` is a destination listed by `list_agent_connectors`; `SHOW TABLES` lists its tables and `SELECT * FROM <table> LIMIT 1` with `\"dry_run\": true` in `api_args` returns its columns without reading rows; never guess columns. On Snowflake, write identifiers unquoted unless discovery returned mixed case. The `download` action is deliberately absent because it returns a binary stream rather than JSON.",
"enum": [
"list",
"get",
"search",
"sql_select"
],
"type": "string"
},
"api_args": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Connector-specific arguments for the action, as an object or a JSON object string. For example {'repository': 'airbytehq/PyAirbyte'}."
},
"select_fields": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Fields to keep in the response, as a list or a CSV string."
},
"exclude_fields": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Fields to drop from the response, as a list or a CSV string."
},
"page_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Maximum number of entities to return in this page. Honored by Context Store `search` actions (sent as `limit`). Direct connector actions take their own page-size argument, if any, in `api_args`."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Pagination cursor for Context Store `search` actions and `sql_select`, taken from `end_cursor` of a previous result. Direct connector actions such as `list` do not read this; pass their own cursor argument in `api_args` instead (for example GitHub's `after`), as named in the skill docs."
},
"intent": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "A short description of why the action is being run."
},
"workspace_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Workspace ID. Hosted MCP connections pass it via the `X-Airbyte-Workspace-Id` header; local or stdio connections use the `AIRBYTE_CLOUD_WORKSPACE_ID` environment variable."
},
"organization_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the `X-Airbyte-Organization-Id` header or the `AIRBYTE_CLOUD_ORGANIZATION_ID` environment variable. To discover organization IDs, call `list_cloud_organizations` to search organizations by name, or `list_agent_workspaces`, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted."
}
},
"required": [
"connector_id",
"entity_type",
"action"
],
"type": "object"
}
Show output JSON schema
{
"description": "Result of executing a single action against an Airbyte Agents connector.",
"properties": {
"status": {
"type": "string"
},
"result": {
"default": null,
"title": "Result"
},
"has_next_page": {
"default": false,
"type": "boolean"
},
"end_cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"execution_time_ms": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
},
"warning": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null
},
"message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"status"
],
"type": "object"
}
inspect_agent_connector
Hints: read-only · idempotent · open-world
Availability: experimental, insiders only (AIRBYTE_MCP_INSIDERS=1 for stdio, X-MCP-Insiders: 1 for hosted servers, or name the module in the include-modules setting; AIRBYTE_MCP_INSIDERS=0 disables it regardless).
Inspect an Airbyte Agents connector: metadata, readiness, warnings, and inline docs.
Call this before `execute_agent_connector` to learn what the connector exposes.
The connector's usage docs summary is returned inline in `docs`; use
`read_agent_skill_docs(skill_id=docs.skill_id, section=...)` for a single section's
full detail.
Airbyte Cloud destinations in the workspace are also accepted and resolve to
built-in docs under `connector-destination:<id>`.
The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting to a hosted MCP server, provide a bearer token via the Authorization header, or client credentials via the transport Client-Id and Client-Secret headers. For local or stdio connections, set the AIRBYTE_CLOUD_BEARER_TOKEN environment variable, or both AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET. Call list_agent_connectors to discover connector IDs, then inspect_agent_connector to learn which entities a connector supports, before calling execute_agent_connector. Use list_agent_skills to discover skills, and pass the docs.skill_id reported by inspect_agent_connector to read_agent_skill_docs for connector usage docs.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
connector_id |
string |
yes | — | The ID of a supported Airbyte source or destination with agent features enabled. |
workspace_id |
string | null |
no | null |
Workspace ID. Hosted MCP connections pass it via the X-Airbyte-Workspace-Id header; local or stdio connections use the AIRBYTE_CLOUD_WORKSPACE_ID environment variable. |
organization_id |
string | null |
no | null |
Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the X-Airbyte-Organization-Id header or the AIRBYTE_CLOUD_ORGANIZATION_ID environment variable. To discover organization IDs, call list_cloud_organizations to search organizations by name, or list_agent_workspaces, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted. |
Show input JSON schema
{
"additionalProperties": false,
"properties": {
"connector_id": {
"description": "The ID of a supported Airbyte source or destination with agent features enabled.",
"type": "string"
},
"workspace_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Workspace ID. Hosted MCP connections pass it via the `X-Airbyte-Workspace-Id` header; local or stdio connections use the `AIRBYTE_CLOUD_WORKSPACE_ID` environment variable."
},
"organization_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the `X-Airbyte-Organization-Id` header or the `AIRBYTE_CLOUD_ORGANIZATION_ID` environment variable. To discover organization IDs, call `list_cloud_organizations` to search organizations by name, or `list_agent_workspaces`, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted."
}
},
"required": [
"connector_id"
],
"type": "object"
}
Show output JSON schema
{
"description": "Details about a single Airbyte Agents connector.",
"properties": {
"connector_id": {
"type": "string"
},
"connector_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"workspace_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"integration_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"docs": {
"anyOf": [
{
"description": "Docs summary embedded in `inspect_agent_connector` results.\n\nThe section outline is intentionally omitted here; `read_agent_skill_docs`\nreturns it as `outline`.",
"properties": {
"skill_id": {
"type": "string"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"content": {
"type": "string"
},
"guidance": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"warnings": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"errors": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"skill_id",
"content"
],
"type": "object"
},
{
"type": "null"
}
],
"default": null
},
"warnings": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"errors": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"connector_id"
],
"type": "object"
}
list_agent_connectors
Hints: read-only · idempotent · open-world
Availability: experimental, insiders only (AIRBYTE_MCP_INSIDERS=1 for stdio, X-MCP-Insiders: 1 for hosted servers, or name the module in the include-modules setting; AIRBYTE_MCP_INSIDERS=0 disables it regardless).
List the connectors configured in an Airbyte Agents workspace.
Sources come from the Agents API. SQL passthrough destinations (Snowflake, BigQuery) in
the Cloud workspace are appended with `connector_kind="destination"`; they support only
the `sql_select` action of `execute_agent_connector_ro`.
The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting to a hosted MCP server, provide a bearer token via the Authorization header, or client credentials via the transport Client-Id and Client-Secret headers. For local or stdio connections, set the AIRBYTE_CLOUD_BEARER_TOKEN environment variable, or both AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET. Call list_agent_connectors to discover connector IDs, then inspect_agent_connector to learn which entities a connector supports, before calling execute_agent_connector. Use list_agent_skills to discover skills, and pass the docs.skill_id reported by inspect_agent_connector to read_agent_skill_docs for connector usage docs.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
workspace_id |
string | null |
no | null |
Workspace ID. Hosted MCP connections pass it via the X-Airbyte-Workspace-Id header; local or stdio connections use the AIRBYTE_CLOUD_WORKSPACE_ID environment variable. |
organization_id |
string | null |
no | null |
Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the X-Airbyte-Organization-Id header or the AIRBYTE_CLOUD_ORGANIZATION_ID environment variable. To discover organization IDs, call list_cloud_organizations to search organizations by name, or list_agent_workspaces, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted. |
Show input JSON schema
{
"additionalProperties": false,
"properties": {
"workspace_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Workspace ID. Hosted MCP connections pass it via the `X-Airbyte-Workspace-Id` header; local or stdio connections use the `AIRBYTE_CLOUD_WORKSPACE_ID` environment variable."
},
"organization_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Organization ID. Omit it when the credentials belong to exactly one organization, or when it is already configured via the `X-Airbyte-Organization-Id` header or the `AIRBYTE_CLOUD_ORGANIZATION_ID` environment variable. To discover organization IDs, call `list_cloud_organizations` to search organizations by name, or `list_agent_workspaces`, which reports the owning organization of each workspace. Workspace-scoped tools derive it from the workspace's parent organization when omitted."
}
},
"type": "object"
}
Show output JSON schema
{
"description": "Result of listing connectors in an Airbyte Agents workspace.",
"properties": {
"connectors": {
"items": {
"description": "Information about a connector configured on the Airbyte Agents platform.",
"properties": {
"connector_id": {
"type": "string"
},
"connector_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"connector_kind": {
"default": "source",
"enum": [
"source",
"destination"
],
"type": "string"
},
"supported_actions": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"sql_dialect": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"connector_id"
],
"type": "object"
},
"type": "array"
},
"message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"connectors"
],
"type": "object"
}
list_agent_skills
Hints: read-only · idempotent · open-world
Availability: experimental, insiders only (AIRBYTE_MCP_INSIDERS=1 for stdio, X-MCP-Insiders: 1 for hosted servers, or name the module in the include-modules setting; AIRBYTE_MCP_INSIDERS=0 disables it regardless).
List all skills available to an Airbyte Agents workspace.
Skills are reusable documentation the Agents API serves, for example connector usage
docs. All pages are fetched, so no pagination arguments are needed. Pass a listed
skill's `skill_id` to `read_agent_skill_docs` to read it.
The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting to a hosted MCP server, provide a bearer token via the Authorization header, or client credentials via the transport Client-Id and Client-Secret headers. For local or stdio connections, set the AIRBYTE_CLOUD_BEARER_TOKEN environment variable, or both AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET. Call list_agent_connectors to discover connector IDs, then inspect_agent_connector to learn which entities a connector supports, before calling execute_agent_connector. Use list_agent_skills to discover skills, and pass the docs.skill_id reported by inspect_agent_connector to read_agent_skill_docs for connector usage docs.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
workspace_id |
string | null |
no | null |
Workspace ID. Hosted MCP connections pass it via the X-Airbyte-Workspace-Id header; local or stdio connections use the AIRBYTE_CLOUD_WORKSPACE_ID environment variable. |
Show input JSON schema
{
"additionalProperties": false,
"properties": {
"workspace_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Workspace ID. Hosted MCP connections pass it via the `X-Airbyte-Workspace-Id` header; local or stdio connections use the `AIRBYTE_CLOUD_WORKSPACE_ID` environment variable."
}
},
"type": "object"
}
Show output JSON schema
{
"description": "Result of listing or searching skills on the Airbyte Agents platform.",
"properties": {
"skills": {
"items": {
"description": "A skill discoverable on the Airbyte Agents platform.",
"properties": {
"skill_id": {
"type": "string"
},
"kind": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"summary": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"tags": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"skill_id",
"tags"
],
"type": "object"
},
"type": "array"
},
"message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"skills"
],
"type": "object"
}
list_agent_workspaces
Hints: read-only · idempotent · open-world
Availability: experimental, insiders only (AIRBYTE_MCP_INSIDERS=1 for stdio, X-MCP-Insiders: 1 for hosted servers, or name the module in the include-modules setting; AIRBYTE_MCP_INSIDERS=0 disables it regardless).
List the workspaces reachable through the Airbyte Agents API.
An organization ID is required when the credentials belong to more than one organization.
The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting to a hosted MCP server, provide a bearer token via the Authorization header, or client credentials via the transport Client-Id and Client-Secret headers. For local or stdio connections, set the AIRBYTE_CLOUD_BEARER_TOKEN environment variable, or both AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET. Call list_agent_connectors to discover connector IDs, then inspect_agent_connector to learn which entities a connector supports, before calling execute_agent_connector. Use list_agent_skills to discover skills, and pass the docs.skill_id reported by inspect_agent_connector to read_agent_skill_docs for connector usage docs.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
organization_id |
string | null |
no | null |
Organization ID. Required when the credentials belong to more than one organization; the Agents API rejects the call with HTTP 400 otherwise. Omit it when the credentials belong to exactly one organization or when it is already configured via the X-Airbyte-Organization-Id header or the AIRBYTE_CLOUD_ORGANIZATION_ID environment variable. Discover organization IDs with list_cloud_organizations. |
Show input JSON schema
{
"additionalProperties": false,
"properties": {
"organization_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Organization ID. Required when the credentials belong to more than one organization; the Agents API rejects the call with HTTP 400 otherwise. Omit it when the credentials belong to exactly one organization or when it is already configured via the `X-Airbyte-Organization-Id` header or the `AIRBYTE_CLOUD_ORGANIZATION_ID` environment variable. Discover organization IDs with `list_cloud_organizations`."
}
},
"type": "object"
}
Show output JSON schema
{
"description": "Result of listing workspaces on the Airbyte Agents platform.",
"properties": {
"workspaces": {
"items": {
"description": "Information about a workspace on the Airbyte Agents platform.",
"properties": {
"workspace_id": {
"type": "string"
},
"workspace_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"organization_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"workspace_id"
],
"type": "object"
},
"type": "array"
},
"message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"workspaces"
],
"type": "object"
}
read_agent_skill_docs
Hints: read-only · idempotent · open-world
Availability: experimental, insiders only (AIRBYTE_MCP_INSIDERS=1 for stdio, X-MCP-Insiders: 1 for hosted servers, or name the module in the include-modules setting; AIRBYTE_MCP_INSIDERS=0 disables it regardless).
Read a skill's docs in an Airbyte Agents workspace.
Without `section`, this returns the skill's metadata, guidance, and the outline of
sections, which is the cheapest way to orient before reading a specific section.
The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting to a hosted MCP server, provide a bearer token via the Authorization header, or client credentials via the transport Client-Id and Client-Secret headers. For local or stdio connections, set the AIRBYTE_CLOUD_BEARER_TOKEN environment variable, or both AIRBYTE_CLOUD_CLIENT_ID and AIRBYTE_CLOUD_CLIENT_SECRET. Call list_agent_connectors to discover connector IDs, then inspect_agent_connector to learn which entities a connector supports, before calling execute_agent_connector. Use list_agent_skills to discover skills, and pass the docs.skill_id reported by inspect_agent_connector to read_agent_skill_docs for connector usage docs.
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
skill_id |
string |
yes | — | Skill ID, e.g. the docs.skill_id reported by inspect_agent_connector, or a skill_id from list_agent_skills. inspect_agent_connector returns only a docs summary (docs.content plus docs.guidance); call this tool with no section for the full section outline, or with section for one section's full detail. SQL passthrough destinations use connector-destination:<destination_id> and merge the server docs with built-in sql_select guidance. |
section |
string | null |
no | null |
Omit to get metadata, guidance, and the outline of available sections. Pass an exact section id from the outline to read that section. |
workspace_id |
string | null |
no | null |
Workspace ID. Hosted MCP connections pass it via the X-Airbyte-Workspace-Id header; local or stdio connections use the AIRBYTE_CLOUD_WORKSPACE_ID environment variable. |
Show input JSON schema
{
"additionalProperties": false,
"properties": {
"skill_id": {
"description": "Skill ID, e.g. the `docs.skill_id` reported by `inspect_agent_connector`, or a `skill_id` from `list_agent_skills`. `inspect_agent_connector` returns only a docs summary (`docs.content` plus `docs.guidance`); call this tool with no `section` for the full section outline, or with `section` for one section's full detail. SQL passthrough destinations use `connector-destination:<destination_id>` and merge the server docs with built-in `sql_select` guidance.",
"type": "string"
},
"section": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Omit to get metadata, guidance, and the outline of available sections. Pass an exact section `id` from the outline to read that section."
},
"workspace_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Workspace ID. Hosted MCP connections pass it via the `X-Airbyte-Workspace-Id` header; local or stdio connections use the `AIRBYTE_CLOUD_WORKSPACE_ID` environment variable."
}
},
"required": [
"skill_id"
],
"type": "object"
}
Show output JSON schema
{
"description": "Documentation for a single skill on the Airbyte Agents platform.",
"properties": {
"skill_id": {
"type": "string"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"section_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"outline": {
"items": {
"description": "A section of a skill's docs, as listed in the docs outline.",
"properties": {
"section_id": {
"type": "string"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"summary": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"available": {
"default": true,
"type": "boolean"
}
},
"required": [
"section_id"
],
"type": "object"
},
"type": "array"
},
"content": {
"type": "string"
},
"guidance": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"warnings": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"errors": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"skill_id",
"outline",
"content"
],
"type": "object"
}
1# Copyright (c) 2026 Airbyte, Inc., all rights reserved. 2"""Airbyte Agents MCP operations. 3 4> ## ⚠️ Experimental Tools — Insiders Only 5> 6> **The Airbyte Agents MCP tools are experimental and hidden by default.** They are advertised 7> only when insiders mode is enabled (`AIRBYTE_MCP_INSIDERS` for stdio servers, `X-MCP-Insiders` 8> for hosted servers) or when the include-modules setting explicitly names `agents`. Tool names, 9> arguments, and result shapes may change or be removed without notice between minor versions of 10> PyAirbyte. Pin an exact PyAirbyte version if you depend on them. 11> 12> These tools are also Cloud-only: they are hidden whenever 13> `AIRBYTE_CLOUD_API_URL` / `AIRBYTE_CLOUD_CONFIG_API_URL` are overridden, unless 14> `AIRBYTE_AGENTS_API_URL` is set. 15 16.. include:: ../../docs/mcp-generated/agents.md 17""" 18 19# No public Python API — MCP primitives are registered via decorators and 20# documented via the generated Markdown include above. Setting `__all__` to an 21# empty list tells pdoc (and other doc tools) not to surface the individual 22# tool / helper definitions as a redundant "API Documentation" list. 23__all__: list[str] = [] 24 25import json 26from http import HTTPStatus 27from typing import Annotated, Any, Literal 28 29import requests 30from fastmcp import Context, FastMCP 31from fastmcp_extensions import get_mcp_config, mcp_tool, register_mcp_tools 32from pydantic import BaseModel, Field 33 34from airbyte._direct_connectors.connector_docs import ( 35 DESTINATION_SKILL_PREFIX, 36 LOCAL_DESTINATION_SECTION_IDS, 37 SQL_PASSTHROUGH_DESTINATION_DEFINITION_IDS, 38 SQL_PASSTHROUGH_DESTINATION_DIALECTS, 39 build_destination_connector_details, 40 build_destination_skill_docs, 41 connector_id_from_skill_id, 42 destination_skill_id, 43 merge_destination_skill_docs, 44) 45from airbyte._direct_connectors.docs_markdown import render_docs_content_markdown 46from airbyte._direct_connectors.models import AgentSkillDocs, AgentSkillInfo 47from airbyte.agents.connectors import AgentAction, AgentConnector, AgentReadAction 48from airbyte.agents.organizations import AgentOrganization 49from airbyte.agents.workspaces import AgentWorkspace 50from airbyte.cloud.connectors import CloudDestination, CloudSource 51from airbyte.constants import ( 52 CLOUD_BEARER_TOKEN_ENV_VAR, 53 CLOUD_CLIENT_ID_ENV_VAR, 54 CLOUD_CLIENT_SECRET_ENV_VAR, 55 CLOUD_ORGANIZATION_ID_ENV_VAR, 56 CLOUD_WORKSPACE_ID_ENV_VAR, 57 MCP_BEARER_TOKEN_HEADER, 58 MCP_CONFIG_API_URL, 59 MCP_CONFIG_BEARER_TOKEN, 60 MCP_CONFIG_CLIENT_ID, 61 MCP_CONFIG_CLIENT_SECRET, 62 MCP_CONFIG_CONFIG_API_URL, 63 MCP_CONFIG_ORGANIZATION_ID, 64 MCP_CONFIG_WORKSPACE_ID, 65 MCP_ORGANIZATION_ID_HEADER, 66 MCP_WORKSPACE_ID_HEADER, 67) 68from airbyte.exceptions import AirbyteError, PyAirbyteInputError 69from airbyte.mcp._arg_resolvers import resolve_list_of_strings 70from airbyte.mcp._tool_utils import AIRBYTE_CLOUD_WORKSPACE_ID_IS_SET 71from airbyte.mcp.cloud import ( 72 _add_defaults_for_exclude_args, 73 _get_cloud_client, 74 _get_cloud_workspace, 75) 76 77 78AGENTS_AUTH_TIP_TEXT = ( 79 f"The Airbyte Agents API authenticates with Airbyte Cloud credentials. When connecting " 80 f"to a hosted MCP server, provide a bearer token via the `{MCP_BEARER_TOKEN_HEADER}` " 81 f"header, or client credentials via the transport `Client-Id` and `Client-Secret` " 82 f"headers. For local or stdio connections, set the `{CLOUD_BEARER_TOKEN_ENV_VAR}` " 83 f"environment variable, or both `{CLOUD_CLIENT_ID_ENV_VAR}` and " 84 f"`{CLOUD_CLIENT_SECRET_ENV_VAR}`. Call `list_agent_connectors` to discover connector " 85 f"IDs, then `inspect_agent_connector` to learn which entities a connector supports, " 86 f"before calling `execute_agent_connector`. Use `list_agent_skills` " 87 f"to discover skills, and pass the `docs.skill_id` reported by " 88 f"`inspect_agent_connector` to `read_agent_skill_docs` for connector usage docs." 89) 90WORKSPACE_ID_TIP_TEXT = ( 91 f"Workspace ID. Hosted MCP connections pass it via the `{MCP_WORKSPACE_ID_HEADER}` " 92 f"header; local or stdio connections use the `{CLOUD_WORKSPACE_ID_ENV_VAR}` " 93 f"environment variable." 94) 95ORGANIZATION_ID_TIP_TEXT = ( 96 f"Organization ID. Omit it when the credentials belong to exactly one organization, or " 97 f"when it is already configured via the " 98 f"`{MCP_ORGANIZATION_ID_HEADER}` header or the `{CLOUD_ORGANIZATION_ID_ENV_VAR}` " 99 f"environment variable. To discover organization IDs, call `list_cloud_organizations` " 100 f"to search organizations by name, or `list_agent_workspaces`, which reports the owning " 101 f"organization of each workspace. Workspace-scoped tools derive it from the workspace's " 102 f"parent organization when omitted." 103) 104LIST_WORKSPACES_ORGANIZATION_ID_TIP_TEXT = ( 105 f"Organization ID. Required when the credentials belong to more than one organization; " 106 f"the Agents API rejects the call with HTTP 400 otherwise. Omit it when the credentials " 107 f"belong to exactly one organization or when it is already configured via the " 108 f"`{MCP_ORGANIZATION_ID_HEADER}` header or the `{CLOUD_ORGANIZATION_ID_ENV_VAR}` " 109 f"environment variable. Discover organization IDs with `list_cloud_organizations`." 110) 111 112AGENTS_ACCESS_DENIED_STATUS = "access_denied" 113"""The `status` reported when the Agents API refused the request.""" 114 115AGENTS_EXECUTION_FAILED_STATUS = "error" 116"""The `status` reported when the Agents API rejected an action request.""" 117 118AGENTS_UNAUTHORIZED_MESSAGE = ( 119 "The Airbyte Agents API rejected these credentials. Verify the Airbyte Cloud " 120 "credentials, or ask the user for valid ones." 121) 122AGENTS_FORBIDDEN_MESSAGE = ( 123 "The Airbyte Agents API authenticated these credentials but denied access. Either the " 124 "organization does not have an Airbyte Agents subscription, or these credentials lack " 125 "access to this workspace. Ask the user to confirm which applies rather than retrying." 126) 127"""Fallback explanation for a 403 whose response body carries no `message`/`detail`.""" 128 129INSPECT_DOCS_GUIDANCE_TEMPLATE = ( 130 "`docs` is a summary of the connector's usage docs. Before calling " 131 "`execute_agent_connector`, read the target action's section for its exact parameter names: " 132 "`read_agent_skill_docs(skill_id={skill_id!r}, section=<section_id>)`, e.g. " 133 "`section={example!r}`. Call `read_agent_skill_docs(skill_id={skill_id!r})` with no " 134 "`section` for the full section outline." 135) 136INSPECT_DOCS_GUIDANCE_NO_SECTIONS_TEMPLATE = ( 137 "`docs` is a summary. No sections are currently available; " 138 "`read_agent_skill_docs(skill_id={skill_id!r})` returns the same summary." 139) 140 141AGENTS_ACTOR_NOT_ENABLED_DETAIL = "Actor is not enabled for Agents access." 142"""The Agents API `detail` when a source or destination exists but is not toggled on.""" 143 144AGENTS_ENABLE_ACTOR_GUIDANCE = ( 145 "The connector or destination exists in the Airbyte Cloud workspace but has not been " 146 "enabled for Agents access. An organization admin must enable it in Airbyte Cloud under " 147 "Settings -> Context layer, by selecting the workspace and turning on the toggle for that " 148 "source or destination. Do not retry until the user confirms it has been enabled." 149) 150AGENTS_DESTINATION_ACCESS_NOTE = ( 151 'Query with `execute_agent_connector_ro` and `action="sql_select"` only; `inspect` returns ' 152 "built-in docs; `SHOW TABLES` lists tables and " 153 '`SELECT * FROM <table> LIMIT 1` with `"dry_run": true` returns columns. If ' 154 "`sql_select` returns `access_denied`, an organization admin must enable the destination " 155 "in Airbyte Cloud under Settings -> Context layer (workspace -> Destinations toggle)." 156) 157"""Attached to destinations in `list_agent_connectors`, whose enabled state is not exposed.""" 158 159 160CONNECTOR_NOT_FOUND_MESSAGE = "No connector found with the given ID or name." 161"""The `AgentWorkspace.get_connector` message when the Agents API does not list the ID.""" 162 163CONTEXT_LAYER_SETTINGS_URL_TEMPLATE = ( 164 "https://cloud.airbyte.com/organization/{organization_id}/settings/context-layer" 165) 166"""The Airbyte Cloud webapp page where an organization admin enables Agents access.""" 167 168 169def _context_layer_location(organization_id: str | None) -> str: 170 """Name the Context layer settings page, with its URL when the organization is known.""" 171 location = "Organization settings -> Context layer" 172 if organization_id: 173 url = CONTEXT_LAYER_SETTINGS_URL_TEMPLATE.format(organization_id=organization_id) 174 return f"{location} ({url})" 175 return location 176 177 178def context_layer_enable_guidance(organization_id: str | None) -> str: 179 """How the human, not the agent, turns on Agents access for an organization or connector.""" 180 return ( 181 "Agents access cannot be enabled from this tool. Ask the user to have an Airbyte Cloud " 182 "organization admin enable it in the Airbyte Cloud webapp under " 183 f"{_context_layer_location(organization_id)} (or from the connector's own settings " 184 "page), then retry." 185 ) 186 187 188def agents_no_connectors_enabled_message(organization_id: str | None) -> str: 189 """For a workspace whose Cloud sources exist but none are enabled for Agents access.""" 190 return ( 191 "No connectors in this workspace are enabled for Agents access, so there is nothing to " 192 f"list, inspect, or execute. {context_layer_enable_guidance(organization_id)}" 193 ) 194 195 196def agents_workspace_has_no_sources_message(organization_id: str | None) -> str: 197 """For a workspace with no Cloud sources at all; enablement guidance alone cannot help.""" 198 return ( 199 "This Airbyte Cloud workspace has no source connectors, so there is nothing to enable " 200 "for Agents access. Ask the user to create a source in the Airbyte Cloud webapp first, " 201 "then have an organization admin enable it for Agents access under " 202 f"{_context_layer_location(organization_id)}." 203 ) 204 205 206class _ConnectorNotEnabledError(AirbyteError): 207 """A connector exists in the Cloud workspace but is not enabled for Agents access.""" 208 209 210class AgentWorkspaceResult(BaseModel): 211 """Information about a workspace on the Airbyte Agents platform.""" 212 213 workspace_id: str 214 """The workspace ID.""" 215 216 workspace_name: str | None = None 217 """Display name of the workspace.""" 218 219 organization_id: str | None = None 220 """The organization that owns the workspace, when reported.""" 221 222 223class AgentWorkspaceListResult(BaseModel): 224 """Result of listing workspaces on the Airbyte Agents platform.""" 225 226 workspaces: list[AgentWorkspaceResult] 227 """Workspaces reachable through the Agents API with these credentials.""" 228 229 message: str | None = None 230 """Why the listing is empty, when the Agents API denied the request.""" 231 232 233class AgentConnectorResult(BaseModel): 234 """Information about a connector configured on the Airbyte Agents platform.""" 235 236 connector_id: str 237 """The connector ID, used as `connector_id` in the other Agents tools.""" 238 239 connector_name: str | None = None 240 """Display name of the connector.""" 241 242 connector_kind: Literal["source", "destination"] = "source" 243 """`source` for Agents API connectors; `destination` for SQL passthrough destinations.""" 244 245 supported_actions: list[str] | None = None 246 """Actions the connector supports, when limited. Destinations support only `sql_select`.""" 247 248 sql_dialect: str | None = None 249 """The `sql_dialect` to pass in `api_args` for `sql_select`. Destinations only.""" 250 251 note: str | None = None 252 """How to use this connector, when it differs from the standard Agents flow.""" 253 254 255class AgentConnectorListResult(BaseModel): 256 """Result of listing connectors in an Airbyte Agents workspace.""" 257 258 connectors: list[AgentConnectorResult] 259 """Connectors configured in the workspace.""" 260 261 message: str | None = None 262 """Why the listing is empty, when the Agents API denied the request.""" 263 264 265class AgentSkillResult(BaseModel): 266 """A skill discoverable on the Airbyte Agents platform.""" 267 268 skill_id: str 269 """The skill ID, used as `skill_id` in `read_agent_skill_docs`.""" 270 271 kind: str | None = None 272 """The skill category, for example `static` or `connector_source`.""" 273 274 title: str | None = None 275 """The human-readable skill title.""" 276 277 summary: str | None = None 278 """A short summary of what the skill documents.""" 279 280 tags: list[str] 281 """Search and categorization tags for the skill.""" 282 283 284class AgentSkillListResult(BaseModel): 285 """Result of listing or searching skills on the Airbyte Agents platform.""" 286 287 skills: list[AgentSkillResult] 288 """Skills matching the listing or search, across all pages.""" 289 290 message: str | None = None 291 """Why the listing is empty, when the Agents API denied the request.""" 292 293 294class AgentSkillSectionResult(BaseModel): 295 """A section of a skill's docs, as listed in the docs outline.""" 296 297 section_id: str 298 """The section ID, passed as `section` to `read_agent_skill_docs`.""" 299 300 title: str | None = None 301 """The human-readable section title.""" 302 303 summary: str | None = None 304 """A short summary of the section content.""" 305 306 available: bool = True 307 """Whether this section can currently be read.""" 308 309 310class AgentSkillDocsResult(BaseModel): 311 """Documentation for a single skill on the Airbyte Agents platform.""" 312 313 skill_id: str 314 """The skill ID that was read.""" 315 316 title: str | None = None 317 """The human-readable skill title.""" 318 319 section_id: str | None = None 320 """The section that was read, or `None` for the default docs response.""" 321 322 outline: list[AgentSkillSectionResult] 323 """The sections available for this skill.""" 324 325 content: str 326 """The docs content rendered as a single Markdown document.""" 327 328 guidance: str | None = None 329 """How to read more of this skill's docs with `read_agent_skill_docs`, when applicable.""" 330 331 warnings: list[str] | None = None 332 """Non-fatal issues reported while building or reading the docs.""" 333 334 errors: list[str] | None = None 335 """Why the result is empty, when the request failed (for example access denied 336 or connector not found).""" 337 338 339class AgentConnectorDocsResult(BaseModel): 340 """Docs summary embedded in `inspect_agent_connector` results. 341 342 The section outline is intentionally omitted here; `read_agent_skill_docs` 343 returns it as `outline`. 344 """ 345 346 skill_id: str 347 """The skill ID of the connector's usage docs.""" 348 349 title: str | None = None 350 """The human-readable docs title.""" 351 352 content: str 353 """The docs content rendered as a single Markdown document.""" 354 355 guidance: str | None = None 356 """How to read more of this connector's docs with `read_agent_skill_docs`.""" 357 358 warnings: list[str] | None = None 359 """Non-fatal issues reported while building or reading the docs.""" 360 361 errors: list[str] | None = None 362 """Why the result is empty, when the request failed (for example access denied 363 or connector not found).""" 364 365 366class AgentConnectorDetailsResult(BaseModel): 367 """Details about a single Airbyte Agents connector.""" 368 369 connector_id: str 370 """The connector ID.""" 371 372 connector_name: str | None = None 373 """Display name of the connector.""" 374 375 workspace_id: str | None = None 376 """The workspace that owns the connector.""" 377 378 integration_name: str | None = None 379 """Name of the underlying integration, for example `GitHub` or `Snowflake`.""" 380 381 docs: AgentConnectorDocsResult | None = None 382 """Summary of the connector's usage docs, when available. 383 384 Pass `docs.skill_id` to `read_agent_skill_docs` for the section outline and a 385 section's full detail. 386 """ 387 388 warnings: list[str] | None = None 389 """Warnings the Agents API reported about this connector.""" 390 391 errors: list[str] | None = None 392 """Why the result is empty, when the request failed (for example access denied 393 or connector not found).""" 394 395 396class AgentExecuteToolResult(BaseModel): 397 """Result of executing a single action against an Airbyte Agents connector.""" 398 399 status: str 400 """The execution status reported by the Agents API, for example `success`.""" 401 402 result: Any = None 403 """The action's payload. Entity-returning actions put a list of entities here.""" 404 405 has_next_page: bool = False 406 """Whether the connector reported more entities after this page.""" 407 408 end_cursor: str | None = None 409 """The cursor for the next page, when one is available. Pass it as `cursor` for Context Store 410 `search`, or as the connector's own cursor argument in `api_args` for direct connector 411 actions.""" 412 413 execution_time_ms: int | None = None 414 """How long the connector took to execute the action, when reported.""" 415 416 warning: dict[str, Any] | None = None 417 """A warning reported alongside an otherwise successful result.""" 418 419 message: str | None = None 420 """Why the action did not run, when the Agents API denied or rejected the request.""" 421 422 423def _resolve_api_args(api_args: dict[str, Any] | str | None) -> dict[str, Any] | None: 424 """Resolve `api_args` from a dictionary or a JSON object string.""" 425 if api_args is None or isinstance(api_args, dict): 426 return api_args 427 428 try: 429 parsed: Any = json.loads(api_args) 430 except json.JSONDecodeError as ex: 431 raise PyAirbyteInputError( 432 message="The `api_args` string is not valid JSON.", 433 guidance="Pass `api_args` as an object, or as a JSON object string.", 434 ) from ex 435 436 if not isinstance(parsed, dict): 437 raise PyAirbyteInputError( 438 message="The `api_args` string is not a JSON object.", 439 guidance="Pass `api_args` as an object, or as a JSON object string.", 440 context={"parsed_type": type(parsed).__name__}, 441 ) 442 return parsed 443 444 445def _agents_access_message(error: AirbyteError) -> str | None: 446 """Return a concise explanation of an Agents API authorization failure. 447 448 Returns `None` when the failure is not an authorization failure, so the caller can 449 re-raise it with a bare `raise` and keep the original traceback. 450 """ 451 if isinstance(error, _ConnectorNotEnabledError): 452 return error.get_message() 453 context = error.context or {} 454 status_code = context.get("status_code") 455 if status_code == HTTPStatus.UNAUTHORIZED: 456 return AGENTS_UNAUTHORIZED_MESSAGE 457 if status_code == HTTPStatus.FORBIDDEN: 458 detail = _agents_error_detail(context.get("response_text")) 459 if detail is None: 460 return AGENTS_FORBIDDEN_MESSAGE 461 if detail == AGENTS_ACTOR_NOT_ENABLED_DETAIL: 462 return f"{detail} {AGENTS_ENABLE_ACTOR_GUIDANCE}" 463 return f"The Airbyte Agents API denied access: {detail}" 464 return None 465 466 467def _agents_error_detail(response_text: object) -> str | None: 468 """Extract the human-readable reason from an Agents API error body. 469 470 The API wraps `HTTPException.detail` as `{"message": ..., "errors": [{"message": ...}]}`; 471 plain FastAPI bodies use `{"detail": ...}`. Returns `None` for anything else, including 472 non-JSON bodies, so the caller falls back to the generic explanation. 473 """ 474 if not isinstance(response_text, str) or not response_text.strip(): 475 return None 476 try: 477 body = json.loads(response_text) 478 except json.JSONDecodeError: 479 return None 480 if not isinstance(body, dict): 481 return None 482 for key in ("message", "detail"): 483 value = body.get(key) 484 if isinstance(value, str) and value.strip(): 485 return value.strip() 486 errors = body.get("errors") 487 if isinstance(errors, list) and errors and isinstance(errors[0], dict): 488 value = errors[0].get("message") 489 if isinstance(value, str) and value.strip(): 490 return value.strip() 491 return None 492 493 494def _sql_error_guidance(detail: str) -> str | None: 495 """Return actionable guidance for a known SQL execution error.""" 496 normalized_detail = detail.casefold() 497 if "000904" in normalized_detail or "invalid identifier" in normalized_detail: 498 return ( 499 "Snowflake identifiers are upper-cased by Airbyte and double-quoted identifiers " 500 "are case-sensitive; write column names unquoted (or upper-cased), or run " 501 '`SELECT * FROM <table> LIMIT 1` with `"dry_run": true` to list the real columns.' 502 ) 503 if "002003" in normalized_detail or "does not exist or not authorized" in normalized_detail: 504 return ( 505 "Run `SHOW TABLES` to list the tables this destination exposes, and qualify tables " 506 "in another schema as `<database>.<schema>.<table>`." 507 ) 508 if "unrecognized name" in normalized_detail: 509 return ( 510 'Run `SELECT * FROM <table> LIMIT 1` with `"dry_run": true` to list the real ' 511 "columns, then use one of the listed column names in the query." 512 ) 513 if "not found: table" in normalized_detail or "not found: dataset" in normalized_detail: 514 return ( 515 "Run `SHOW TABLES` to list the tables this destination exposes, and qualify tables " 516 "in another dataset as `<project>.<dataset>.<table>` (backticked)." 517 ) 518 return None 519 520 521def _agents_execution_failure_message( 522 error: AirbyteError, 523 action: AgentAction, 524) -> str | None: 525 """Return a concise explanation for a rejected Agents API action request.""" 526 context = error.context or {} 527 if context.get("status_code") not in { 528 HTTPStatus.BAD_REQUEST, 529 HTTPStatus.UNPROCESSABLE_ENTITY, 530 }: 531 return None 532 detail = _agents_error_detail(context.get("response_text")) 533 if detail is None: 534 return None 535 guidance = _sql_error_guidance(detail) if action == AgentReadAction.SQL_SELECT else None 536 return f"{detail} {guidance}" if guidance is not None else detail 537 538 539def _is_not_found(error: AirbyteError) -> bool: 540 """Return whether the Agents API reported the connector or skill as not found.""" 541 return (error.context or {}).get("status_code") == HTTPStatus.NOT_FOUND 542 543 544def _resolve_cloud_destination( 545 ctx: Context, 546 connector_id: str, 547 workspace_id: str | None = None, 548 organization_id: str | None = None, 549) -> CloudDestination | None: 550 """Look a connector ID up in the Cloud workspace's destinations. 551 552 Destinations are targets of `sql_select` and are not listed by the Agents API. Returns 553 `None` when the ID does not match any destination in the workspace; `list_destinations` 554 is used so a bogus ID does not raise on lazy fetch. 555 """ 556 workspace = _get_agent_workspace(ctx, workspace_id, organization_id) 557 for destination in _get_cloud_workspace(ctx, workspace.workspace_id).list_destinations(): 558 if destination.connector_id == connector_id: 559 return destination 560 return None 561 562 563def _resolve_cloud_source( 564 ctx: Context, 565 connector_id: str, 566 workspace_id: str, 567) -> CloudSource | None: 568 """Look a connector ID up in the Cloud workspace's sources. 569 570 Returns `None` when the ID does not match any source in the workspace. 571 """ 572 for source in _get_cloud_workspace(ctx, workspace_id).list_sources(): 573 if source.connector_id == connector_id: 574 return source 575 return None 576 577 578def _source_not_enabled_message( 579 source: CloudSource, 580 workspace_id: str, 581 organization_id: str | None, 582) -> str: 583 """Explain that a Cloud source exists but is not enabled for Agents access.""" 584 return ( 585 f"Source '{source.name}' ({source.connector_id}) exists in Airbyte Cloud " 586 f"workspace {workspace_id} but is not enabled for Agents access. " 587 f"{context_layer_enable_guidance(organization_id)}" 588 ) 589 590 591def _connector_unavailable_error( 592 ctx: Context, 593 connector_id: str, 594 workspace_id: str, 595 organization_id: str | None, 596) -> AirbyteError: 597 """Build the error for a connector ID the Agents API does not list. 598 599 A `_ConnectorNotEnabledError` when the ID is a source in the Cloud workspace, so the 600 caller can report the disabled state instead of a bare miss; otherwise a not-found 601 error pointing at `list_agent_connectors`. 602 """ 603 source = _resolve_cloud_source(ctx, connector_id, workspace_id) 604 if source is not None: 605 return _ConnectorNotEnabledError( 606 message=_source_not_enabled_message(source, workspace_id, organization_id), 607 context={"connector_id": connector_id, "workspace_id": workspace_id}, 608 ) 609 return AirbyteError( 610 message=CONNECTOR_NOT_FOUND_MESSAGE, 611 guidance=( 612 "Use `list_agent_connectors` to see the connectors enabled for Agents access in " 613 "this workspace." 614 ), 615 context={"connector_id": connector_id, "workspace_id": workspace_id}, 616 ) 617 618 619def _or_none(items: list[str]) -> list[str] | None: 620 """Return `None` for empty lists so optional list fields stay unset.""" 621 return items or None 622 623 624def _skill_docs_result(docs: AgentSkillDocs) -> AgentSkillDocsResult: 625 """Shape an `AgentSkillDocs` into an `AgentSkillDocsResult`.""" 626 return AgentSkillDocsResult( 627 skill_id=docs.metadata.id, 628 title=docs.metadata.title, 629 section_id=docs.section_id, 630 outline=[ 631 AgentSkillSectionResult( 632 section_id=docs_section.id, 633 title=docs_section.title, 634 summary=docs_section.summary, 635 available=docs_section.available, 636 ) 637 for docs_section in docs.outline 638 ], 639 content=render_docs_content_markdown(docs.content), 640 warnings=_or_none([str(warning) for warning in docs.metadata.warnings]), 641 ) 642 643 644def _inspect_docs_guidance(skill_id: str, outline: list[AgentSkillSectionResult]) -> str: 645 """Build the `guidance` hint for a docs summary embedded in an inspect result.""" 646 example_section = next((section for section in outline if section.available), None) 647 if example_section is None: 648 return INSPECT_DOCS_GUIDANCE_NO_SECTIONS_TEMPLATE.format(skill_id=skill_id) 649 return INSPECT_DOCS_GUIDANCE_TEMPLATE.format( 650 skill_id=skill_id, example=example_section.section_id 651 ) 652 653 654def _connector_docs_result(docs: AgentSkillDocsResult) -> AgentConnectorDocsResult: 655 """Shape a skill docs result into the docs summary embedded in inspect results.""" 656 return AgentConnectorDocsResult( 657 skill_id=docs.skill_id, 658 title=docs.title, 659 content=docs.content, 660 guidance=_inspect_docs_guidance(docs.skill_id, docs.outline), 661 warnings=docs.warnings, 662 errors=docs.errors, 663 ) 664 665 666def _inspect_destination_fallback( 667 ctx: Context, 668 connector_id: str, 669 workspace_id: str | None = None, 670 organization_id: str | None = None, 671) -> AgentConnectorDetailsResult: 672 """Build an inspect result for a connector ID the Agents API returned 404 for. 673 674 SQL passthrough destinations get built-in details; anything else gets an `errors` 675 entry instead of raising. 676 """ 677 agent_workspace = _get_agent_workspace(ctx, workspace_id, organization_id) 678 resolved_workspace_id = agent_workspace.workspace_id 679 destination = _resolve_cloud_destination(ctx, connector_id, workspace_id, organization_id) 680 if ( 681 destination is not None 682 and destination.definition_id in SQL_PASSTHROUGH_DESTINATION_DEFINITION_IDS 683 ): 684 details = build_destination_connector_details(destination) 685 warnings: list[str] = [] 686 try: 687 skill_docs = _skill_docs_result( 688 _read_destination_skill_docs(agent_workspace, destination, None) 689 ) 690 except (AirbyteError, requests.RequestException) as error: 691 detail = error.get_message() if isinstance(error, AirbyteError) else str(error) 692 docs_unavailable = f"Connector docs are unavailable: {detail}" 693 warnings.append(docs_unavailable) 694 docs_result = AgentConnectorDocsResult( 695 skill_id=destination_skill_id(destination.connector_id), 696 content="", 697 warnings=[docs_unavailable], 698 ) 699 else: 700 docs_result = _connector_docs_result(skill_docs) 701 return AgentConnectorDetailsResult( 702 connector_id=details.connector_id, 703 connector_name=details.name, 704 workspace_id=details.workspace_id, 705 integration_name=details.integration_name, 706 docs=docs_result, 707 warnings=_or_none(warnings), 708 ) 709 if destination is not None: 710 message = ( 711 f"Destination '{destination.name}' (definition {destination.definition_id}) is not " 712 "a SQL passthrough destination; only Snowflake and BigQuery destinations support " 713 "`sql_select`." 714 ) 715 elif (source := _resolve_cloud_source(ctx, connector_id, resolved_workspace_id)) is not None: 716 message = _source_not_enabled_message( 717 source, resolved_workspace_id, agent_workspace.organization_id 718 ) 719 else: 720 message = ( 721 f"Connector {connector_id} was not found in the Agents API and is not a source or " 722 f"destination in workspace {resolved_workspace_id}. Use `list_agent_connectors` to " 723 "see the connectors enabled for Agents access, or " 724 f"`read_agent_skill_docs(skill_id='connector-source:{connector_id}')`." 725 ) 726 return AgentConnectorDetailsResult( 727 connector_id=connector_id, 728 errors=[message], 729 ) 730 731 732def _read_destination_skill_docs( 733 workspace: AgentWorkspace, 734 destination: CloudDestination, 735 section: str | None, 736) -> AgentSkillDocs: 737 """Read a SQL passthrough destination's docs, merging server docs with local guidance. 738 739 Local-only sections are built without calling the Agents API; otherwise server 740 docs are augmented with local guidance, and a 404 falls back to local docs. 741 """ 742 if section in LOCAL_DESTINATION_SECTION_IDS: 743 return build_destination_skill_docs(destination, section=section) 744 skill_id = destination_skill_id(destination.connector_id) 745 try: 746 server_docs = workspace.read_skill_docs(skill_id, section=section) 747 except AirbyteError as error: 748 if _is_not_found(error): 749 return build_destination_skill_docs(destination, section=section) 750 raise 751 return merge_destination_skill_docs(server_docs, destination) 752 753 754def _destination_skill_docs_fallback( 755 ctx: Context, 756 skill_id: str, 757 section: str | None, 758 workspace_id: str | None = None, 759 *, 760 destination: CloudDestination | None = None, 761 destination_resolved: bool = False, 762) -> AgentSkillDocsResult: 763 """Build a skill docs result for a skill ID the Agents API returned 404 for.""" 764 connector_id = connector_id_from_skill_id(skill_id) 765 resolved_workspace_id = _get_agent_workspace(ctx, workspace_id).workspace_id 766 if not destination_resolved: 767 destination = _resolve_cloud_destination(ctx, connector_id, workspace_id) 768 if ( 769 destination is not None 770 and destination.definition_id in SQL_PASSTHROUGH_DESTINATION_DEFINITION_IDS 771 ): 772 docs = build_destination_skill_docs(destination, section=section) 773 return _skill_docs_result(docs) 774 if destination is not None: 775 message = ( 776 f"Destination '{destination.name}' (definition {destination.definition_id}) is not " 777 "a SQL passthrough destination; only Snowflake and BigQuery destinations support " 778 "`sql_select`." 779 ) 780 else: 781 message = ( 782 f"Skill {skill_id} was not found in the Agents API and connector {connector_id} " 783 f"is not a destination in workspace {resolved_workspace_id}." 784 ) 785 return AgentSkillDocsResult( 786 skill_id=skill_id, 787 section_id=section, 788 outline=[], 789 content="", 790 errors=[message], 791 ) 792 793 794def _get_agent_organization(ctx: Context, organization_id: str | None) -> AgentOrganization: 795 """Build an `AgentOrganization` from MCP config.""" 796 return AgentOrganization( 797 organization_id=organization_id or get_mcp_config(ctx, MCP_CONFIG_ORGANIZATION_ID), 798 client_id=get_mcp_config(ctx, MCP_CONFIG_CLIENT_ID), 799 client_secret=get_mcp_config(ctx, MCP_CONFIG_CLIENT_SECRET), 800 bearer_token=get_mcp_config(ctx, MCP_CONFIG_BEARER_TOKEN), 801 public_api_root=get_mcp_config(ctx, MCP_CONFIG_API_URL), 802 config_api_root=get_mcp_config(ctx, MCP_CONFIG_CONFIG_API_URL), 803 ) 804 805 806def _get_agent_workspace( 807 ctx: Context, 808 workspace_id: str | None, 809 organization_id: str | None = None, 810) -> AgentWorkspace: 811 """Build an `AgentWorkspace`, deriving an absent organization from its workspace.""" 812 resolved_workspace_id = workspace_id or get_mcp_config(ctx, MCP_CONFIG_WORKSPACE_ID) 813 resolved_organization_id = organization_id or get_mcp_config(ctx, MCP_CONFIG_ORGANIZATION_ID) 814 if not resolved_workspace_id or not resolved_organization_id: 815 client = _get_cloud_client(ctx) 816 if not resolved_workspace_id: 817 resolved_workspace_id = client.resolve_default_workspace_id() 818 if resolved_workspace_id and not resolved_organization_id: 819 resolved_organization_id = client.get_workspace_parent_organization_id( 820 resolved_workspace_id 821 ) 822 return AgentWorkspace( 823 workspace_id=resolved_workspace_id, 824 organization_id=resolved_organization_id, 825 client_id=get_mcp_config(ctx, MCP_CONFIG_CLIENT_ID), 826 client_secret=get_mcp_config(ctx, MCP_CONFIG_CLIENT_SECRET), 827 bearer_token=get_mcp_config(ctx, MCP_CONFIG_BEARER_TOKEN), 828 public_api_root=get_mcp_config(ctx, MCP_CONFIG_API_URL), 829 config_api_root=get_mcp_config(ctx, MCP_CONFIG_CONFIG_API_URL), 830 ) 831 832 833def _get_agent_connector( 834 ctx: Context, 835 connector_id: str, 836 workspace_id: str | None = None, 837 organization_id: str | None = None, 838) -> AgentConnector: 839 """Get an `AgentConnector` from its workspace, using MCP config. 840 841 The Agents API addresses a connector by ID alone, but the connector is fetched through 842 its workspace anyway, so a connector ID belonging to another workspace raises before 843 any action runs. 844 845 Destination connectors (targets of `sql_select`) are not listed by the Agents API, so IDs it 846 does not know are verified against the Cloud workspace's destinations instead. An ID that 847 is a Cloud source the Agents API does not list is reported as not enabled for Agents 848 access, rather than as missing. 849 """ 850 workspace = _get_agent_workspace(ctx, workspace_id, organization_id) 851 try: 852 return workspace.get_connector(connector_id) 853 except AirbyteError as error: 854 if error.get_message() != CONNECTOR_NOT_FOUND_MESSAGE: 855 raise 856 cloud_destination_ids = { 857 destination.connector_id 858 for destination in _get_cloud_workspace(ctx, workspace.workspace_id).list_destinations() 859 } 860 if connector_id not in cloud_destination_ids: 861 raise _connector_unavailable_error( 862 ctx, connector_id, workspace.workspace_id, workspace.organization_id 863 ) from error 864 return workspace.get_connector(connector_id=connector_id) 865 866 867def _execute( # noqa: PLR0913 # Mirrors the tool signatures it serves. 868 ctx: Context, 869 *, 870 connector_id: str, 871 workspace_id: str | None, 872 organization_id: str | None, 873 entity_type: str, 874 action: AgentAction, 875 api_args: dict[str, Any] | str | None, 876 select_fields: list[str] | str | None, 877 exclude_fields: list[str] | str | None, 878 page_size: int | None, 879 cursor: str | None, 880 intent: str | None, 881 read_only: bool | None = None, 882) -> AgentExecuteToolResult: 883 """Execute one connector action and shape it into an `AgentExecuteToolResult`. 884 885 When `read_only` is `True`, write actions are rejected before any request is sent. 886 """ 887 if read_only and action not in set(AgentReadAction): 888 raise PyAirbyteInputError( 889 message="This action writes data and cannot run in read-only mode.", 890 guidance=( 891 "Read-only actions are: " 892 f"{', '.join(member.value for member in AgentReadAction)}." 893 ), 894 context={"action": action}, 895 ) 896 897 try: 898 result = _get_agent_connector( 899 ctx=ctx, 900 connector_id=connector_id, 901 workspace_id=workspace_id, 902 organization_id=organization_id, 903 ).execute( 904 entity_type=entity_type, 905 action=action, 906 api_args=_resolve_api_args(api_args), 907 select_fields=resolve_list_of_strings(select_fields), 908 exclude_fields=resolve_list_of_strings(exclude_fields), 909 page_size=page_size, 910 cursor=cursor, 911 workspace_id=workspace_id, 912 intent=intent, 913 ) 914 except AirbyteError as error: 915 message = _agents_access_message(error) 916 if message is not None: 917 return AgentExecuteToolResult( 918 status=AGENTS_ACCESS_DENIED_STATUS, 919 message=message, 920 ) 921 message = _agents_execution_failure_message(error, action) 922 if message is not None: 923 return AgentExecuteToolResult( 924 status=AGENTS_EXECUTION_FAILED_STATUS, 925 message=message, 926 ) 927 raise 928 929 return AgentExecuteToolResult( 930 status=result.status, 931 result=result.result, 932 has_next_page=result.has_next_page, 933 end_cursor=result.end_cursor, 934 execution_time_ms=result.execution_metadata.execution_time_ms, 935 warning=result.warning, 936 ) 937 938 939@mcp_tool( 940 read_only=True, 941 idempotent=True, 942 open_world=True, 943 extra_help_text=AGENTS_AUTH_TIP_TEXT, 944) 945def list_agent_workspaces( 946 ctx: Context, 947 *, 948 organization_id: Annotated[ 949 str | None, 950 Field( 951 description=LIST_WORKSPACES_ORGANIZATION_ID_TIP_TEXT, 952 default=None, 953 ), 954 ], 955) -> AgentWorkspaceListResult: 956 """List the workspaces reachable through the Airbyte Agents API. 957 958 An organization ID is required when the credentials belong to more than one organization. 959 """ 960 organization = _get_agent_organization(ctx, organization_id) 961 try: 962 workspaces = organization.list_workspaces() 963 except AirbyteError as error: 964 message = _agents_access_message(error) 965 if message is None: 966 raise 967 return AgentWorkspaceListResult(workspaces=[], message=message) 968 969 return AgentWorkspaceListResult( 970 workspaces=[ 971 AgentWorkspaceResult( 972 workspace_id=workspace.workspace_id, 973 workspace_name=workspace.name, 974 organization_id=workspace.organization_id, 975 ) 976 for workspace in workspaces 977 ] 978 ) 979 980 981@mcp_tool( 982 read_only=True, 983 idempotent=True, 984 open_world=True, 985 extra_help_text=AGENTS_AUTH_TIP_TEXT, 986) 987def list_agent_connectors( 988 ctx: Context, 989 *, 990 workspace_id: Annotated[ 991 str | None, 992 Field( 993 description=WORKSPACE_ID_TIP_TEXT, 994 default=None, 995 ), 996 ], 997 organization_id: Annotated[ 998 str | None, 999 Field( 1000 description=ORGANIZATION_ID_TIP_TEXT, 1001 default=None, 1002 ), 1003 ], 1004) -> AgentConnectorListResult: 1005 """List the connectors configured in an Airbyte Agents workspace. 1006 1007 Sources come from the Agents API. SQL passthrough destinations (Snowflake, BigQuery) in 1008 the Cloud workspace are appended with `connector_kind="destination"`; they support only 1009 the `sql_select` action of `execute_agent_connector_ro`. 1010 """ 1011 workspace = _get_agent_workspace(ctx, workspace_id, organization_id) 1012 try: 1013 connectors = workspace.list_connectors() 1014 except AirbyteError as error: 1015 message = _agents_access_message(error) 1016 if message is None: 1017 raise 1018 return AgentConnectorListResult(connectors=[], message=message) 1019 1020 results = [ 1021 AgentConnectorResult( 1022 connector_id=connector.connector_id, 1023 connector_name=connector.name, 1024 ) 1025 for connector in connectors 1026 ] 1027 results.extend( 1028 _destination_connector_result(destination) 1029 for destination in _list_sql_passthrough_destinations(ctx, workspace.workspace_id) 1030 ) 1031 if not results: 1032 return AgentConnectorListResult( 1033 connectors=[], 1034 message=_empty_connector_list_message( 1035 ctx, workspace.workspace_id, workspace.organization_id 1036 ), 1037 ) 1038 return AgentConnectorListResult(connectors=results) 1039 1040 1041def _empty_connector_list_message( 1042 ctx: Context, 1043 workspace_id: str, 1044 organization_id: str | None, 1045) -> str: 1046 """Explain an empty Agents connector list. 1047 1048 Enablement guidance only helps when the Cloud workspace actually has sources; an empty 1049 Cloud workspace needs a source created first. 1050 """ 1051 if _get_cloud_workspace(ctx, workspace_id).list_sources(): 1052 return agents_no_connectors_enabled_message(organization_id) 1053 return agents_workspace_has_no_sources_message(organization_id) 1054 1055 1056def _list_sql_passthrough_destinations( 1057 ctx: Context, 1058 workspace_id: str, 1059) -> list[CloudDestination]: 1060 """Return the Cloud workspace's destinations that `sql_select` can query.""" 1061 return [ 1062 destination 1063 for destination in _get_cloud_workspace(ctx, workspace_id).list_destinations() 1064 if destination.definition_id in SQL_PASSTHROUGH_DESTINATION_DEFINITION_IDS 1065 ] 1066 1067 1068def _destination_connector_result(destination: CloudDestination) -> AgentConnectorResult: 1069 """Describe a SQL passthrough destination as a `sql_select`-only connector.""" 1070 return AgentConnectorResult( 1071 connector_id=destination.connector_id, 1072 connector_name=destination.name, 1073 connector_kind="destination", 1074 supported_actions=["sql_select"], 1075 sql_dialect=SQL_PASSTHROUGH_DESTINATION_DIALECTS[destination.definition_id], 1076 note=AGENTS_DESTINATION_ACCESS_NOTE, 1077 ) 1078 1079 1080@mcp_tool( 1081 read_only=True, 1082 idempotent=True, 1083 open_world=True, 1084 extra_help_text=AGENTS_AUTH_TIP_TEXT, 1085) 1086def inspect_agent_connector( 1087 ctx: Context, 1088 connector_id: Annotated[ 1089 str, 1090 Field( 1091 description=( 1092 "The ID of a supported Airbyte source or destination with agent features enabled." 1093 ), 1094 ), 1095 ], 1096 *, 1097 workspace_id: Annotated[ 1098 str | None, 1099 Field( 1100 description=WORKSPACE_ID_TIP_TEXT, 1101 default=None, 1102 ), 1103 ], 1104 organization_id: Annotated[ 1105 str | None, 1106 Field( 1107 description=ORGANIZATION_ID_TIP_TEXT, 1108 default=None, 1109 ), 1110 ], 1111) -> AgentConnectorDetailsResult: 1112 """Inspect an Airbyte Agents connector: metadata, readiness, warnings, and inline `docs`. 1113 1114 Call this before `execute_agent_connector` to learn what the connector exposes. 1115 The connector's usage docs summary is returned inline in `docs`; use 1116 `read_agent_skill_docs(skill_id=docs.skill_id, section=...)` for a single section's 1117 full detail. 1118 Airbyte Cloud destinations in the workspace are also accepted and resolve to 1119 built-in docs under `connector-destination:<id>`. 1120 """ 1121 try: 1122 workspace = _get_agent_workspace(ctx, workspace_id, organization_id) 1123 details = workspace.get_connector(connector_id).inspect() 1124 except AirbyteError as error: 1125 if _is_not_found(error) or error.get_message() == CONNECTOR_NOT_FOUND_MESSAGE: 1126 return _inspect_destination_fallback(ctx, connector_id, workspace_id, organization_id) 1127 message = _agents_access_message(error) 1128 if message is None: 1129 raise 1130 return AgentConnectorDetailsResult( 1131 connector_id=connector_id, 1132 errors=[message], 1133 ) 1134 1135 warnings = [str(warning) for warning in details.warnings] 1136 docs_result: AgentConnectorDocsResult | None = None 1137 if details.docs_skill_id: 1138 try: 1139 connector_docs = workspace.read_skill_docs(details.docs_skill_id) 1140 except (AirbyteError, requests.RequestException) as error: 1141 detail = error.get_message() if isinstance(error, AirbyteError) else str(error) 1142 docs_unavailable = f"Connector docs are unavailable: {detail}" 1143 warnings.append(docs_unavailable) 1144 docs_result = AgentConnectorDocsResult( 1145 skill_id=details.docs_skill_id, 1146 content="", 1147 warnings=[docs_unavailable], 1148 ) 1149 else: 1150 docs_result = _connector_docs_result(_skill_docs_result(connector_docs)) 1151 1152 return AgentConnectorDetailsResult( 1153 connector_id=details.connector_id, 1154 connector_name=details.name, 1155 workspace_id=details.workspace_id, 1156 integration_name=details.integration_name, 1157 docs=docs_result, 1158 warnings=_or_none(warnings), 1159 ) 1160 1161 1162@mcp_tool( 1163 read_only=True, 1164 idempotent=True, 1165 open_world=True, 1166 extra_help_text=AGENTS_AUTH_TIP_TEXT, 1167) 1168def execute_agent_connector_ro( # noqa: PLR0913 # Explicit args are the point of this tool. 1169 ctx: Context, 1170 connector_id: Annotated[ 1171 str, 1172 Field( 1173 description=( 1174 "The ID of the Airbyte Agents connector, from `list_agent_connectors`. For " 1175 "`sql_select`, pass the `connector_id` of a destination entry " 1176 '(`connector_kind="destination"`) from that listing.' 1177 ), 1178 ), 1179 ], 1180 entity_type: Annotated[ 1181 str, 1182 Field( 1183 description=( 1184 "The type of entity to act on, for example 'issues'. Call " 1185 "`inspect_agent_connector` to see the entity types a connector supports." 1186 ), 1187 ), 1188 ], 1189 action: Annotated[ 1190 AgentReadAction, 1191 Field( 1192 description=( 1193 "The read action to run against the entity type. " 1194 "The `search` action is the connector's native API search, parallel to `get` " 1195 "and `list`. " 1196 "For `sql_select`, pass `sql` and `sql_dialect` (snowflake or bigquery) in " 1197 "`api_args` and any value for `entity_type`; the `connector_id` is a " 1198 "destination listed by `list_agent_connectors`; `SHOW TABLES` lists its tables " 1199 'and `SELECT * FROM <table> LIMIT 1` with `"dry_run": true` in `api_args` ' 1200 "returns its columns without reading rows; never guess columns. On Snowflake, " 1201 "write identifiers unquoted unless discovery returned mixed case. The `download` " 1202 "action " 1203 "is deliberately absent because it returns a binary stream rather than JSON." 1204 ), 1205 ), 1206 ], 1207 api_args: Annotated[ 1208 dict[str, Any] | str | None, 1209 Field( 1210 description=( 1211 "Connector-specific arguments for the action, as an object or a JSON " 1212 "object string. For example {'repository': 'airbytehq/PyAirbyte'}." 1213 ), 1214 default=None, 1215 ), 1216 ], 1217 *, 1218 select_fields: Annotated[ 1219 list[str] | str | None, 1220 Field( 1221 description="Fields to keep in the response, as a list or a CSV string.", 1222 default=None, 1223 ), 1224 ], 1225 exclude_fields: Annotated[ 1226 list[str] | str | None, 1227 Field( 1228 description="Fields to drop from the response, as a list or a CSV string.", 1229 default=None, 1230 ), 1231 ], 1232 page_size: Annotated[ 1233 int | None, 1234 Field( 1235 description=( 1236 "Maximum number of entities to return in this page. Honored by Context Store " 1237 "`search` actions (sent as `limit`). Direct connector actions take their own " 1238 "page-size argument, if any, in `api_args`." 1239 ), 1240 default=None, 1241 ), 1242 ], 1243 cursor: Annotated[ 1244 str | None, 1245 Field( 1246 description=( 1247 "Pagination cursor for Context Store `search` actions and `sql_select`, taken " 1248 "from `end_cursor` of a previous result. Direct connector " 1249 "actions such as `list` do not read this; pass their own cursor argument in " 1250 "`api_args` instead (for example GitHub's `after`), as named in the skill docs." 1251 ), 1252 default=None, 1253 ), 1254 ], 1255 intent: Annotated[ 1256 str | None, 1257 Field( 1258 description="A short description of why the action is being run.", 1259 default=None, 1260 ), 1261 ], 1262 workspace_id: Annotated[ 1263 str | None, 1264 Field( 1265 description=WORKSPACE_ID_TIP_TEXT, 1266 default=None, 1267 ), 1268 ], 1269 organization_id: Annotated[ 1270 str | None, 1271 Field( 1272 description=ORGANIZATION_ID_TIP_TEXT, 1273 default=None, 1274 ), 1275 ], 1276) -> AgentExecuteToolResult: 1277 """Read data from an Airbyte Agents connector, without modifying anything. 1278 1279 This tool only accepts read actions, so it stays available in read-only mode. Use 1280 `execute_agent_connector` for actions that create, update, or delete data. Entity types 1281 are connector-specific, so call `inspect_agent_connector` first. The connector must 1282 belong to the given workspace. 1283 1284 To query a destination: 1285 1. Run `SHOW TABLES` to discover tables. 1286 2. Run `SELECT * FROM <table> LIMIT 1` with `"dry_run": true` in `api_args` to get the 1287 real column names; never guess them. 1288 3. Select data using the discovered names. 1289 1290 Use the destination's `connector_id` and `sql_dialect` as reported by 1291 `list_agent_connectors`. On Snowflake, identifiers are upper-cased; write them unquoted 1292 (double-quoting makes them case-sensitive). Quote a name only when discovery returns it in 1293 mixed or lower case, exactly as returned. 1294 """ 1295 return _execute( 1296 ctx, 1297 connector_id=connector_id, 1298 workspace_id=workspace_id, 1299 organization_id=organization_id, 1300 entity_type=entity_type, 1301 action=action, 1302 api_args=api_args, 1303 select_fields=select_fields, 1304 exclude_fields=exclude_fields, 1305 page_size=page_size, 1306 cursor=cursor, 1307 intent=intent, 1308 read_only=True, 1309 ) 1310 1311 1312@mcp_tool( 1313 open_world=True, 1314 extra_help_text=AGENTS_AUTH_TIP_TEXT, 1315) 1316def execute_agent_connector( # noqa: PLR0913 # Explicit args are the point of this tool. 1317 ctx: Context, 1318 connector_id: Annotated[ 1319 str, 1320 Field(description="The ID of the Airbyte Agents connector."), 1321 ], 1322 entity_type: Annotated[ 1323 str, 1324 Field( 1325 description=( 1326 "The type of entity to act on, for example 'issues'. Call " 1327 "`inspect_agent_connector` to see the entity types a connector supports." 1328 ), 1329 ), 1330 ], 1331 action: Annotated[ 1332 AgentAction, 1333 Field( 1334 description=( 1335 "The action to run against the entity type. " 1336 "The `search` action is the connector's native API search, parallel to `get` " 1337 "and `list`. " 1338 "For `sql_select`, pass `sql` and `sql_dialect` (snowflake or bigquery) in " 1339 "`api_args` and any value for `entity_type`. The `download` action " 1340 "is deliberately absent because it returns a binary stream rather than JSON." 1341 ), 1342 ), 1343 ], 1344 api_args: Annotated[ 1345 dict[str, Any] | str | None, 1346 Field( 1347 description=( 1348 "Connector-specific arguments for the action, as an object or a JSON " 1349 "object string. For example {'repository': 'airbytehq/PyAirbyte'}." 1350 ), 1351 default=None, 1352 ), 1353 ], 1354 *, 1355 select_fields: Annotated[ 1356 list[str] | str | None, 1357 Field( 1358 description="Fields to keep in the response, as a list or a CSV string.", 1359 default=None, 1360 ), 1361 ], 1362 exclude_fields: Annotated[ 1363 list[str] | str | None, 1364 Field( 1365 description="Fields to drop from the response, as a list or a CSV string.", 1366 default=None, 1367 ), 1368 ], 1369 page_size: Annotated[ 1370 int | None, 1371 Field( 1372 description=( 1373 "Maximum number of entities to return in this page. Honored by Context Store " 1374 "`search` actions (sent as `limit`). Direct connector actions take their own " 1375 "page-size argument, if any, in `api_args`." 1376 ), 1377 default=None, 1378 ), 1379 ], 1380 cursor: Annotated[ 1381 str | None, 1382 Field( 1383 description=( 1384 "Pagination cursor for Context Store `search` actions and `sql_select`, taken " 1385 "from `end_cursor` of a previous result. Direct connector " 1386 "actions such as `list` do not read this; pass their own cursor argument in " 1387 "`api_args` instead (for example GitHub's `after`), as named in the skill docs." 1388 ), 1389 default=None, 1390 ), 1391 ], 1392 intent: Annotated[ 1393 str | None, 1394 Field( 1395 description="A short description of why the action is being run.", 1396 default=None, 1397 ), 1398 ], 1399 read_only: Annotated[ 1400 bool | None, 1401 Field( 1402 description=( 1403 "Set to `true` to reject write actions before any request is sent, when the " 1404 "caller wants a read guarantee from this tool." 1405 ), 1406 default=None, 1407 ), 1408 ], 1409 workspace_id: Annotated[ 1410 str | None, 1411 Field( 1412 description=WORKSPACE_ID_TIP_TEXT, 1413 default=None, 1414 ), 1415 ], 1416 organization_id: Annotated[ 1417 str | None, 1418 Field( 1419 description=ORGANIZATION_ID_TIP_TEXT, 1420 default=None, 1421 ), 1422 ], 1423) -> AgentExecuteToolResult: 1424 """Execute a single action against an Airbyte Agents connector, including writes. 1425 1426 Prefer `execute_agent_connector_ro` when only reading, since it is available in 1427 read-only mode. Entity types and actions are connector-specific, so call 1428 `inspect_agent_connector` first. The connector must belong to the given workspace. 1429 """ 1430 return _execute( 1431 ctx, 1432 connector_id=connector_id, 1433 workspace_id=workspace_id, 1434 organization_id=organization_id, 1435 entity_type=entity_type, 1436 action=action, 1437 api_args=api_args, 1438 select_fields=select_fields, 1439 exclude_fields=exclude_fields, 1440 page_size=page_size, 1441 cursor=cursor, 1442 intent=intent, 1443 read_only=read_only, 1444 ) 1445 1446 1447def _agent_skill_result(skill: AgentSkillInfo) -> AgentSkillResult: 1448 """Shape an `AgentSkillInfo` into an `AgentSkillResult`.""" 1449 return AgentSkillResult( 1450 skill_id=skill.id, 1451 kind=skill.kind, 1452 title=skill.title, 1453 summary=skill.summary, 1454 tags=skill.tags, 1455 ) 1456 1457 1458@mcp_tool( 1459 read_only=True, 1460 idempotent=True, 1461 open_world=True, 1462 extra_help_text=AGENTS_AUTH_TIP_TEXT, 1463) 1464def list_agent_skills( 1465 ctx: Context, 1466 *, 1467 workspace_id: Annotated[ 1468 str | None, 1469 Field( 1470 description=WORKSPACE_ID_TIP_TEXT, 1471 default=None, 1472 ), 1473 ], 1474) -> AgentSkillListResult: 1475 """List all skills available to an Airbyte Agents workspace. 1476 1477 Skills are reusable documentation the Agents API serves, for example connector usage 1478 docs. All pages are fetched, so no pagination arguments are needed. Pass a listed 1479 skill's `skill_id` to `read_agent_skill_docs` to read it. 1480 """ 1481 workspace = _get_agent_workspace(ctx, workspace_id) 1482 try: 1483 skills = workspace.list_skills() 1484 except AirbyteError as error: 1485 message = _agents_access_message(error) 1486 if message is None: 1487 raise 1488 return AgentSkillListResult(skills=[], message=message) 1489 1490 return AgentSkillListResult( 1491 skills=[_agent_skill_result(skill.info) for skill in skills], 1492 ) 1493 1494 1495@mcp_tool( 1496 read_only=True, 1497 idempotent=True, 1498 open_world=True, 1499 extra_help_text=AGENTS_AUTH_TIP_TEXT, 1500) 1501def read_agent_skill_docs( 1502 ctx: Context, 1503 skill_id: Annotated[ 1504 str, 1505 Field( 1506 description=( 1507 "Skill ID, e.g. the `docs.skill_id` reported by `inspect_agent_connector`, " 1508 "or a `skill_id` from `list_agent_skills`. `inspect_agent_connector` returns " 1509 "only a docs summary (`docs.content` plus `docs.guidance`); call " 1510 "this tool with no `section` for the full section outline, or with " 1511 "`section` for one section's full detail. SQL passthrough destinations " 1512 "use `connector-destination:<destination_id>` and merge the server docs " 1513 "with built-in `sql_select` guidance." 1514 ), 1515 ), 1516 ], 1517 *, 1518 section: Annotated[ 1519 str | None, 1520 Field( 1521 description=( 1522 "Omit to get metadata, guidance, and the outline of available sections. " 1523 "Pass an exact section `id` from the outline to read that section." 1524 ), 1525 default=None, 1526 ), 1527 ], 1528 workspace_id: Annotated[ 1529 str | None, 1530 Field( 1531 description=WORKSPACE_ID_TIP_TEXT, 1532 default=None, 1533 ), 1534 ], 1535) -> AgentSkillDocsResult: 1536 """Read a skill's docs in an Airbyte Agents workspace. 1537 1538 Without `section`, this returns the skill's metadata, guidance, and the outline of 1539 sections, which is the cheapest way to orient before reading a specific section. 1540 """ 1541 workspace = _get_agent_workspace(ctx, workspace_id) 1542 destination: CloudDestination | None = None 1543 destination_resolved = False 1544 if skill_id.startswith(DESTINATION_SKILL_PREFIX): 1545 destination_resolved = True 1546 destination = _resolve_cloud_destination( 1547 ctx, connector_id_from_skill_id(skill_id), workspace_id 1548 ) 1549 if ( 1550 destination is not None 1551 and destination.definition_id in SQL_PASSTHROUGH_DESTINATION_DEFINITION_IDS 1552 ): 1553 try: 1554 docs = _read_destination_skill_docs(workspace, destination, section) 1555 except AirbyteError as error: 1556 message = _agents_access_message(error) 1557 if message is None: 1558 raise 1559 return AgentSkillDocsResult( 1560 skill_id=skill_id, 1561 section_id=section, 1562 outline=[], 1563 content="", 1564 errors=[message], 1565 ) 1566 return _skill_docs_result(docs) 1567 try: 1568 docs = workspace.read_skill_docs(skill_id, section=section) 1569 except AirbyteError as error: 1570 if _is_not_found(error): 1571 return _destination_skill_docs_fallback( 1572 ctx, 1573 skill_id, 1574 section, 1575 workspace_id, 1576 destination=destination, 1577 destination_resolved=destination_resolved, 1578 ) 1579 message = _agents_access_message(error) 1580 if message is None: 1581 raise 1582 return AgentSkillDocsResult( 1583 skill_id=skill_id, 1584 section_id=section, 1585 outline=[], 1586 content="", 1587 errors=[message], 1588 ) 1589 1590 return _skill_docs_result(docs) 1591 1592 1593def register_agents_tools(app: FastMCP) -> None: 1594 """Register the Airbyte Agents tools with the FastMCP app.""" 1595 exclude_args = ["workspace_id"] if AIRBYTE_CLOUD_WORKSPACE_ID_IS_SET else None 1596 if exclude_args: 1597 _add_defaults_for_exclude_args(exclude_args) 1598 register_mcp_tools( 1599 app, 1600 mcp_module=__name__, 1601 exclude_args=exclude_args, 1602 )