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