# keboola/keboola-mcp-server [Health: Active]

**Category:** 📊 Data Platforms  
**Repository:** https://github.com/keboola/keboola-mcp-server  
**GitHub Stars:** 85  
**Views:** 3  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/keboola-keboola-mcp-server

## Description
interact with Keboola Connection Data Platform. This server provides tools for listing and accessing data from Keboola Storage API.

## Tools
Capabilities this server exposes over MCP:

- **get_components** — Retrieves detailed information about one or more components by their IDs.

RETURNS FOR EACH COMPONENT:
- Component metadata (name, type, description)
- Documentation and usage instructions
- Configuration JSON schema (required for creating/updating configurations)
- Links to component dashboard in Keboola UI

WHEN TO USE:
- Before creating a new configuration: fetch the component to get its configuration schema
- Before updating a configuration: fetch the component to understand valid configuration options
- When user asks about component capabilities or documentation

PREREQUISITES:
- You must know the component_id(s). If unknown, first use `find_component_id` or `docs` tool to discover them.

EXAMPLES:
- User: "Create a generic extractor configuration"
  → First call `find_component_id` to get the component_id, then call this tool to get the schema
- User: "What options does the Snowflake writer support?"
  → Call this tool with the Snowflake writer component_id to retrieve its documentation and schema
- **get_configs** — Retrieves component configurations in the project with optional filtering.

Can list summaries of multiple configurations (grouped by component) or retrieve full details
for specific configurations.

Returns a list of components, each containing:
- Component metadata (ID, name, type, description)
- Configurations for that component (summaries by default, full details if requested)
- Links to the Keboola UI

PARAMETER BEHAVIOR:
- If configs is provided (non-empty): Returns FULL details ONLY for those configs.
- Else if component_ids is provided (non-empty): Lists config summaries for those components.
- Else: Lists configs based on component_types (all types if empty).

WHEN TO USE:
- For listing: Use component_types/component_ids.
- For details: Use configs (can handle multiple).

WHEN NOT TO USE:
- Do NOT list all configs just to find a configuration by name. Use `search` with
  item_types=["configuration", "transformation"] instead.
- Only use broad listing (empty component_types and component_ids) when you need
  a complete inventory of all configurations in the project.

EXAMPLES:
- List all configs (summaries): component_types=[], component_ids=[]
- List extractors (summaries): component_types=["extractor"]
- Get details for specific configs:
  configs=[{"component_id": "keboola.ex-db-mysql", "configuration_id": "12345"}]
- **get_config_examples** — Retrieves sample configuration examples for a specific component.

USAGE:
- Use before calling `create_config` or `add_config_row` to understand the expected parameters structure.
- Use when you want to see example configurations for a specific component.

EXAMPLES:
- user_input: `Show me example configurations for component X`
    - set the component_id parameter accordingly
    - returns a markdown formatted string with configuration examples
- **create_config** — Creates a root component configuration using the specified name, component ID, configuration JSON, and description.
Not for SQL transformations (`keboola.snowflake-transformation` / `keboola.google-bigquery-transformation`),
data apps (`keboola.data-apps`) or flows — use the dedicated tools (see WHEN NOT TO USE). This IS the tool for
Python (`keboola.python-transformation-v2`), R (`keboola.r-transformation-v2`) and DuckDB
(`keboola.duckdb-transformation`) transformations.

BEFORE CALLING - REQUIRED STEPS:
1. Call `get_components([component_id])` to retrieve the component's `configuration_schema`.
2. Read `configuration_schema.required` to find ALL mandatory top-level fields.
3. Call `get_config_examples(component_id)` to see real-world parameter examples.
4. Populate `parameters` with every required field before calling this tool.
Skipping these steps will cause a schema validation error.

USAGE:
- Use when you want to create a new root configuration for a specific component.

WHEN NOT TO USE:
- `keboola.orchestrator` / `keboola.flow` → use `create_flow` / `create_conditional_flow`
- `keboola.data-apps` → use `modify_python_js_data_app` / `modify_streamlit_data_app` / `deploy_data_app`
- `keboola.snowflake-transformation` / `keboola.google-bigquery-transformation` → use `create_sql_transformation`

EXAMPLES:
- user_input: `Create a new configuration for component X with these settings`
    - set the component_id and configuration parameters accordingly
    - returns the created component configuration if successful.
- **update_config** — Updates an existing root component configuration by modifying its parameters, storage mappings, name or description.
Not for SQL transformations (`keboola.snowflake-transformation` / `keboola.google-bigquery-transformation`),
data apps (`keboola.data-apps`) or flows — use the dedicated tools (see WHEN NOT TO USE). This IS the tool for
updating Python (`keboola.python-transformation-v2`), R (`keboola.r-transformation-v2`) and DuckDB
(`keboola.duckdb-transformation`) transformations.

This tool allows PARTIAL parameter updates - you only need to provide the fields you want to change.
All other fields will remain unchanged.
Use this tool when modifying existing configurations; for configuration rows, use update_config_row instead.

WHEN TO USE:
- Modifying configuration parameters (credentials, settings, API keys, etc.)
- Updating storage mappings (input/output tables or files)
- Changing configuration name or description
- Any combination of the above

WHEN NOT TO USE:
- `keboola.orchestrator` / `keboola.flow` → use `update_flow`
- `keboola.data-apps` → use `modify_python_js_data_app` / `modify_streamlit_data_app` / `deploy_data_app`
- `keboola.snowflake-transformation` / `keboola.google-bigquery-transformation` → use `update_sql_transformation`

PREREQUISITES:
- Configuration must already exist (use create_config for new configurations)
- You must know both component_id and configuration_id
- For parameter updates: Review the component's root_configuration_schema using get_components.
- For storage updates: Ensure mappings are valid for the component type

IMPORTANT CONSIDERATIONS:
- Parameter updates are PARTIAL - only specify fields you want to change
- parameter_updates supports granular operations: set keys, replace strings, remove keys, or append to lists
- Parameters must conform to the component's root_configuration_schema
- Validate schemas before calling: use get_components to retrieve root_configuration_schema
- For row-based components, this updates the ROOT only (use update_config_row for individual rows)

WORKFLOW:
1. Retrieve current configuration using get_configs (to understand current state)
2. Identify specific parameters/storage mappings to modify
3. Prepare parameter_updates list with targeted operations
4. Call update_config with only the fields to change
- **add_config_row** — Creates a component configuration row in the specified configuration_id, using the specified name,
component ID, configuration JSON, and description.

BEFORE CALLING - REQUIRED STEPS:
1. Call `get_components([component_id])` to retrieve the component's `configuration_row_schema`.
2. Read `configuration_row_schema.required` to find ALL mandatory top-level fields.
3. Call `get_config_examples(component_id)` to see real-world row parameter examples.
4. Populate `parameters` with every required field before calling this tool.
Skipping these steps will cause a schema validation error.

USAGE:
- Use when you want to create a new row configuration for a specific component configuration.

WHEN NOT TO USE:
- `keboola.orchestrator` / `keboola.flow` → use `create_flow` / `create_conditional_flow`
- `keboola.data-apps` → use `modify_python_js_data_app` / `modify_streamlit_data_app` / `deploy_data_app`
- `keboola.snowflake-transformation` / `keboola.google-bigquery-transformation` → use `create_sql_transformation`

EXAMPLES:
- user_input: `Create a new configuration row for component X with these settings`
    - set the component_id, configuration_id and configuration parameters accordingly
    - returns the created component configuration if successful.
- **update_config_row** — Updates an existing component configuration row by modifying its parameters, storage mappings, name, or description.

This tool allows PARTIAL parameter updates - you only need to provide the fields you want to change.
All other fields will remain unchanged.
Configuration rows are individual items within a configuration, often representing separate data sources,
tables, or endpoints that share the same component type and parent configuration settings.

WHEN TO USE:
- Modifying row-specific parameters (table sources, filters, credentials, etc.)
- Updating storage mappings for a specific row (input/output tables or files)
- Changing row name or description
- Any combination of the above

WHEN NOT TO USE:
- `keboola.orchestrator` / `keboola.flow` → use `update_flow`
- `keboola.data-apps` → use `modify_python_js_data_app` / `modify_streamlit_data_app` / `deploy_data_app`
- `keboola.snowflake-transformation` / `keboola.google-bigquery-transformation` → use `update_sql_transformation`

PREREQUISITES:
- The configuration row must already exist (use add_config_row for new rows)
- You must know component_id, configuration_id, and configuration_row_id
- For parameter updates: Review the component's row_configuration_schema using get_components
- For storage updates: Ensure mappings are valid for row-level storage

IMPORTANT CONSIDERATIONS:
- Parameter updates are PARTIAL - only specify fields you want to change
- parameter_updates supports granular operations: set individual keys, replace strings, or remove keys
- Parameters must conform to the component's row_configuration_schema (not root schema)
- Validate schemas before calling: use get_components to retrieve row_configuration_schema
- Each row operates independently - changes to one row don't affect others
- Row-level storage is separate from root-level storage configuration

WORKFLOW:
1. Retrieve current configuration using get_configs to see existing rows
2. Identify the specific row to modify by its configuration_row_id
3. Prepare parameter_updates list with targeted operations for this row
4. Call update_config_row with only the fields to change
- **run_sync_action** — Executes a synchronous action for a component configuration or a component row configuration.
Effects depend on the component and action; execution can modify external resources.

WHEN TO USE:
- For finding available values of a configuration field
- For validating already configured values (e.g. testing a database connection)
- For listing remote resources such as endpoints, schemas or tables
- **create_sql_transformation** — Creates an SQL transformation using the specified name, SQL query following the current SQL dialect, a detailed
description, and a list of created table names.

CONSIDERATIONS:
- By default, SQL transformation must create at least one table to produce a result; omit only if the user
  explicitly indicates that no table creation is needed.
- Each SQL code block must include descriptive name that reflects its purpose and group one or more executable
  semantically related SQL statements.
- Each SQL query statement within a code block must be executable and follow the current SQL dialect.
- Use delimited identifiers for the current SQL dialect for all identifiers and FQN references.
- When referring to the input tables within the SQL query, use fully qualified table names, which can be
  retrieved using appropriate tools.
- When creating a new table within the SQL query (e.g. CREATE TABLE ...): use only the table name with
  delimited identifiers, without the fully qualified path; add the plain table name without delimiters
  to the `created_table_names` list.
- Unless otherwise specified by user, transformation name and description are generated based on the SQL query
  and user intent.
- If there are 20 or more SQL transformations in the project, consider organizing them with a folder: existing
  folder names are surfaced in the response's change_summary — use one of them or create a new one.

USAGE:
- Use when you want to create a new SQL transformation.
- This is THE tool for creating `keboola.snowflake-transformation` and `keboola.google-bigquery-transformation`
  components (do NOT use `create_config` for these); the transformation ID is derived automatically from the
  workspace SQL dialect.
- Snowflake/BigQuery only. For Python, R, or DuckDB transformations, use `create_config` with the appropriate
  `component_id` instead — this tool cannot create them.

EXAMPLES:
- user_input: `Can you create a new transformation out of this sql query?`
    - set the sql_code_blocks to the query, and set other parameters accordingly.
    - returns the created SQL transformation configuration if successful.
- user_input: `Generate me an SQL transformation which [USER INTENT]`
    - set the sql_code_blocks to the query based on the [USER INTENT], and set other parameters accordingly.
    - returns the created SQL transformation configuration if successful.
- **update_sql_transformation** — Updates an existing SQL transformation configuration by modifying its SQL code, storage mappings,
name or description.

This tool allows PARTIAL parameter updates for transformation SQL blocks and code - you only need to provide
the operations you want to perform. All other fields will remain unchanged.
Use this for modifying SQL transformations created with create_sql_transformation.

WHEN TO USE:
- SQL transformations only (Snowflake/BigQuery); use update_config for Python/R/DuckDB transformations
- Modifying SQL queries in transformation (add/edit/remove SQL statements)
- Updating transformation block or code block names
- Changing input/output table mappings for the transformation
- Updating the transformation name or description
- Any combination of the above

PREREQUISITES:
- Transformation must already exist (use create_sql_transformation for new transformations)
- You must know the configuration_id of the transformation
- SQL dialect is determined automatically from the workspace
- CRITICAL: Use get_configs first to see the current transformation structure and get block_id/code_id values

TRANSFORMATION STRUCTURE:
A transformation has this hierarchy:
  transformation
  └─ blocks[] - List of transformation blocks (each has a unique block_id)
     └─ block.name - Descriptive name for the block
     └─ block.codes[] - List of code blocks within the block (each has a unique code_id)
        └─ code.name - Descriptive name for the code block
        └─ code.script - SQL script (string with SQL statements)

Example structure from get_configs:
{
  "blocks": [
    {
      "id": "b0",  ← block_id needed for operations (format: b{index})
      "name": "Data Preparation",
      "codes": [
        {
          "id": "b0.c0",  ← code_id needed for operations (format: b{block_index}.c{code_index})
          "name": "Load customers",
          "script": "SELECT * FROM customers WHERE status = 'active';"
        }
      ]
    }
  ]
}

PARAMETER UPDATE OPERATIONS:
All operations use block_id and code_id to identify elements (get these from get_configs first).

ID Format:
- block_id: "b0", "b1", "b2", etc. (format: b{index})
- code_id: "b0.c0", "b0.c1", "b1.c0", etc. (format: b{block_index}.c{code_index})

1. BLOCK OPERATIONS:
   - add_block: Create a new block in the transformation
     {"op": "add_block", "block": {"name": "New Block", "codes": []}, "position": "end"}

   - remove_block: Delete an entire block
     {"op": "remove_block", "block_id": "b0"}

   - rename_block: Change a block's name
     {"op": "rename_block", "block_id": "b2", "block_name": "Updated Name"}

2. CODE BLOCK OPERATIONS:
   - add_code: Create a new code block within an existing block
     {"op": "add_code", "block_id": "b1", "code": {"name": "New Code", "script": "SELECT 1;"}, "position": "end"}

   - remove_code: Delete a code block
     {"op": "remove_code", "block_id": "b0", "code_id": "b0.c0"}

   - rename_code: Change a code block's name
     {"op": "rename_code", "block_id": "b1", "code_id": "b1.c2", "code_name": "Updated Name"}

3. SQL SCRIPT OPERATIONS:
   - set_code: Replace the entire SQL script (overwrites existing)
     {"op": "set_code", "block_id": "b0", "code_id": "b0.c0", "script": "SELECT * FROM new_table;"}

   - add_script: Append or prepend SQL to existing script (preserves existing)
     {"op": "add_script", "block_id": "b2", "code_id": "b2.c1", "script": "WHERE date > '2024-01-01'",
      "position": "end"}

   - str_replace: Find and replace text in SQL scripts
     {"op": "str_replace", "search_for": "old_table", "replace_with": "new_table", "block_id": "b0",'
      "code_id": "b0.c0"}
     - Omit code_id to replace in all codes of a block
     - Omit both block_id and code_id to replace everywhere

IMPORTANT CONSIDERATIONS:
- Parameter updates are PARTIAL - only the operations you specify are applied
- All other parts of the transformation remain unchanged
- Each SQL script must be executable and follow the current SQL dialect:
  - Use delimited identifiers for the current SQL dialect.
  - Never mix delimiter styles within a single query.
- Storage configuration is COMPLETE REPLACEMENT - include ALL mappings you want to keep
- Leave updated_description empty to preserve the original description
- SCHEMA CHANGES: Destructive schema changes (removing columns, changing types, renaming columns) require
  manually deleting the output table before running the updated transformation to avoid schema mismatch errors.
  Non-destructive changes (adding columns) typically do not require table deletion.

WORKFLOW:
1. Call get_configs to retrieve current transformation structure and identify block_id/code_id values
2. Identify what needs to change (SQL code, storage, description)
3. For SQL changes: Prepare parameter_updates list with targeted operations
4. For storage changes: Build complete storage configuration (include all mappings)
5. Call update_sql_transformation with change_description and only the fields to change

EXAMPLE WORKFLOWS:

Example 1 - Update SQL script in existing code block:
Step 1: Get current config
  result = get_configs(component_id="keboola.snowflake-transformation", configuration_id="12345")
  # Note the block_id (e.g., "b0") and code_id (e.g., "b0.c1") from result

Step 2: Update the SQL
  update_sql_transformation(
    configuration_id="12345",
    change_description="Updated WHERE clause to filter active customers only",
    parameter_updates=[
      {
        "op": "set_code",
        "block_id": "b0",      # from step 1
        "code_id": "b0.c0",    # from step 1
        "script": "SELECT * FROM customers WHERE status = 'active' AND region = 'US';"
      }
    ]
  )

Example 2 - Append a new code block to the second block of an existing transformation:
  update_sql_transformation(
    configuration_id="12345",
    change_description="Added aggregation step",
    parameter_updates=[
      {
        "op": "add_code",
        "block_id": "b1",  # second block
        "code": {
          "name": "Aggregate Sales",
          "script": "SELECT customer_id, SUM(amount) as total FROM orders GROUP BY customer_id;"
        },
        "position": "end"
      }
    ]
  )

Example 3 - Replace table name across all SQL scripts:
  update_sql_transformation(
    configuration_id="12345",
    change_description="Renamed source table from old_customers to customers",
    parameter_updates=[
      {
        "op": "str_replace",
        "search_for": "old_customers",
        "replace_with": "customers"
        # No block_id or code_id = applies to all scripts
      }
    ]
  )

Example 4 - Update storage mappings:
  update_sql_transformation(
    configuration_id="12345",
    change_description="Added new input table",
    storage={
      "input": {
        "tables": [
          {
            "source": "in.c-main.customers",
            "destination": "customers"
          },
          {
            "source": "in.c-main.orders",
            "destination": "orders"
          }
        ]
      },
      "output": {
        "tables": [
          {
            "source": "result",
            "destination": "out.c-main.customer_summary"
          }
        ]
      }
    }
  )
- **modify_streamlit_data_app** — Creates or updates a Streamlit data app.

Considerations:
- The `source_code` parameter must be a complete and runnable Streamlit app. It must include a placeholder
`{QUERY_DATA_FUNCTION}` where a `query_data` function will be injected. This function queries the workspace to get
data, it accepts a string of SQL query following current sql dialect and returns a pandas DataFrame with the results
from the workspace.
- Write SQL queries so they are compatible with the current workspace backend, you can ensure this by using the
`query_data` tool to inspect the data in the workspace before using it in the data app.
- If you're updating an existing data app, provide the `configuration_id` parameter and the `change_description`
parameter. To keep existing data app values during an update, leave them as empty strings, lists, or None
appropriately based on the parameter type.
- After creating or updating a data app with this tool, ALWAYS call
`deploy_data_app(action="deploy", configuration_id=...)` to start a new app or restart an existing app so
changes take effect. Without this step, a newly created app will not start, and an existing app will keep
running the previous deployment without the latest changes.
- New apps use the HTTP basic authentication by default for security unless explicitly specified otherwise; when
updating, set `authentication_type` to `default` to keep the existing authentication type configuration
(including OIDC setups) unless explicitly specified otherwise.

SQL & DATA TYPE RULES:
- Use delimited identifiers for the current SQL dialect for all column names and aliases in SQL.
  Match the exact identifier case used in SQL when referencing columns in Python code.
- `query_data` RETURNS ALL COLUMNS AS STRINGS regardless of SQL CAST. Always convert types in Python after loading:
`df["col"] = pd.to_numeric(df["col"], errors="coerce").fillna(0)` and
`df["date"] = pd.to_datetime(df["date"], errors="coerce")`.
- **modify_python_js_data_app** — Creates or updates a python-js data app.

Two-app project model. Every python-js project has a persistent **prod app** that owns the
only managed git repository for the project, and zero or more **drafts** parented to that
prod app. A draft is a Storage configuration with `parameters.dataApp.isDraft=true` and
`parameters.dataApp.parentConfigurationId=<prod cfg id>`; it's an *external-git* app that
clones the parent prod's repo at a pinned branch on every deploy. Drafts are surfaced in the
Keboola UI under their parent prod app. Use `deploy_data_app(mode='dev')` to deploy a draft
as a dev version of the data app (hot reload + auto-auth for iframe preview); use
`delete_python_js_data_app_draft` to tear a draft down after its branch has been promoted.

**MCP never runs git on your behalf.** All git work — clone, branch, commit, push, merge,
branch-delete — is yours. MCP gives you authenticated clone URLs and manages configs/deploys;
it never invokes git.

**The draft flow below is mandatory — never edit prod source directly.** Every source-code
change goes through a draft branch that the user previews and explicitly approves first. NEVER
push directly to `main`: `main` only ever advances by merging an approved draft branch, and
only after the user has approved that draft's preview.

Three scenarios the agent has to distinguish:

## Scenario A — Create a brand-new data app

1. `modify_python_js_data_app(slug='demo')` → `(configuration_id=PROD, repo_url=R)`.
   PROD owns the only managed repo for this app.
2. `modify_python_js_data_app(slug='demo-draft', parent_configuration_id=PROD)`
   → `(configuration_id=DRAFT, repo_url=R, git_clone_url=U, branch='draft-<hex>')`.
   The default branch is generated fresh per draft (`draft-<hex>`) so it can never collide
   with a branch left behind by an earlier draft. Override with `branch=<name>` for a
   descriptive name — if you do, YOU own uniqueness (see step 3).
3. YOU: `git clone U`; `git checkout -b <branch>` (the repo of a brand-new prod app is empty,
   so there is no `main` to branch from yet); write source; `git push origin <branch>`.
4. `deploy_data_app(action='deploy', configuration_id=DRAFT, mode='dev')`
   → preview URL serving the draft's pinned branch as a dev version. Iterate with the user.
5. Once approved — YOU: `git checkout main && git merge <branch>`. On a brand-new app (step 3
   above) `main` does not exist yet, so create it from the approved draft instead:
   `git checkout -b main <branch>`. Then `git push origin main`;
   `git push origin --delete <branch>` (branch deletes ARE permitted on managed repos).
6. `deploy_data_app(action='deploy', configuration_id=PROD)`
   → prod URL now serves the merged `main`.
7. `delete_python_js_data_app_draft(configuration_id=DRAFT)`
   → tears down the draft's config + data-app instance. Always run this once promoted.

## Scenario B — Edit an existing data app

You already have PROD's `configuration_id` (from `get_data_apps` or earlier conversation).

1. `create_python_js_data_app_git_credential(configuration_id=PROD)`
   → fresh `git_clone_url U` with an embedded one-time token.
2. `modify_python_js_data_app(
        slug='demo-draft-<short suffix>',
        parent_configuration_id=PROD,
        branch='<describes-the-change>',   # e.g. 'add-revenue-filter'
   )` → `(DRAFT, R, U2, branch)`. Use U2 (it has its own fresh token).
3. YOU: `git clone U2`; `git fetch origin && git checkout -B <branch> --no-track origin/main` —
   state the base explicitly, and `--no-track` so `origin/main` does not become the branch's
   upstream (otherwise a bare `git push` on the draft branch would target `main`). NEVER a bare
   `git checkout <branch>`: when that branch already exists on the remote (a descriptive name
   reused from an earlier session), git silently checks out its stale tip instead of branching
   from `main`, and the preview then serves outdated code with no error. Edit source;
   `git push origin <branch>`. If that push is rejected as non-fast-forward, the remote branch
   still holds unmerged commits from an abandoned earlier draft — either
   `git push --force-with-lease origin <branch>` or `git push origin --delete <branch>` first.
   Never resolve a rejected push with a bare `git checkout`.
4–7. Same as Scenario A steps 4–7.

## Scenario C — Continue an unfinished draft

The previous sandbox is gone. You have PROD's `configuration_id` but no working clone and no
draft handle.

1. `get_data_apps(configuration_ids=[PROD])` → returns PROD's detail including `drafts: [...]`.
   Pick the draft the user means (ask if multiple and unclear). Each entry exposes its
   `configuration_id`, slug, and pinned branch.
2. `create_python_js_data_app_git_credential(configuration_id=PROD)`
   → fresh `git_clone_url U` (the previous one was minted in a wiped sandbox and is lost).
   Drafts have no managed repo of their own — always mint against PROD.
3. YOU: `git clone U`; `git checkout <draft's pinned branch>`. Here you DO want the existing
   branch — but confirm it is current before you build on it:
   `git rev-list --count <branch>..origin/main` must print 0, otherwise `git merge origin/main`
   first. Resume work; `git push`.
4. `deploy_data_app(action='deploy', configuration_id=<DRAFT>, mode='dev')` → preview URL.
   The draft's branch is already pinned in its config.
5–7. Same promote/cleanup sequence as Scenario A steps 5–7.

## Argument rules

- `parent_configuration_id` is **create-only**. Rejected on update.
- `branch` on **create** is only valid when `parent_configuration_id` is set (pins the new
  draft's branch). Defaults to a generated, collision-free `'draft-<hex>'`. Must not be
  `'main'`. Rejected on prod create. A branch you supply yourself may already exist on the
  repo — branch it off `origin/main` explicitly (see Scenario B step 3).
  On **update** `branch` repoints an existing **external-git** app's pinned branch (see below).
- `slug` is optional on create (auto-derived from `name` when omitted; drafts get a unique
  suffix). See "Slug on update" below.
- The **update path** (passing `configuration_id`) is for changing `name`, `description`,
  `authentication_type`, `auto_suspend_after_seconds`, `storage` on either a prod app or
  a draft, for changing a prod app's `slug` (see "Slug on update"), and for repointing an
  **external-git** app's `branch` (a draft, or an app bound to an external repository — an
  app on a Keboola-managed git repo is rejected, its branch is owned by the platform). Source code changes go through the git flow above, not this
  tool. After a `branch` repoint, call `deploy_data_app` to serve the new branch.

## Authentication

New apps default to HTTP basic authentication for safety. Pass `authentication_type='no-auth'`
to expose publicly -- only when the user explicitly asks for a public app. `'no-auth'` is
rejected on drafts: a draft inherits the prod app's data access, so disabling its
authentication would expose Storage-reading and Storage-writing endpoints publicly. Never
disable authentication to make the preview work -- the in-platform preview
(`deploy_data_app(mode='dev')`) authenticates on top of the configured auth. On update,
`authentication_type='default'` preserves the existing `authorization` block (including OIDC
setups configured outside the MCP); `'basic-auth'` / `'no-auth'` overwrite it.

## Slug constraint

Must be DNS-label-safe (lowercase letters, digits, hyphens). Optional on create: when omitted it
is auto-derived from `name` and capped at 50 characters (the data-app URL-prefix limit enforced
by the UI; drafts additionally get a short unique `-draft-<suffix>`, still within 50, to keep
slugs unique across the prod app and its drafts). Pass an explicit slug to override; an explicit
slug must be at most 63 characters (the DNS-label max), and note the UI's own URL-prefix limit is
50, so an explicit slug of 51-63 characters may still be rejected at deploy time.

## Slug on update

The slug is the app URL (`https://<slug>-<app id>.hub.<stack>`), so changing it moves the app.
1. Until the prod app is first deployed, a rename moves a slug that follows the name (e.g.
   `new-app` for "New App") to the new name automatically — nobody has the URL yet.
2. Once the app has been deployed, a rename keeps the slug. Change it only with an explicit
   `slug`, and only after the user approved the new URL.
3. Drafts never change their slug.
After an explicit `slug` the new URL applies on the next `deploy_data_app` and the old URL stops
working; tell the user both.
- **create_python_js_data_app_git_credential** — Mints a one-time HTTPS token on a python-js **prod** data app so the caller can clone, pull,
and push to the app's managed git repo over HTTPS.

**Always call against the prod app's configuration_id** — drafts have no managed repo of their
own, so calling this on a draft fails. The prod app is the canonical repo owner; drafts
iterate against branches of that same repo.

**MCP never runs git on your behalf.** All git work — clone, branch, commit, push, merge,
branch-delete — is yours. This tool only mints credentials.

Returns a ready-to-use `git_clone_url` of the form `https://kai:<secret>@<host>/<path>.git`
plus the raw `secret`. The token is returned **only** at creation — the platform cannot return
it again on any subsequent read. Stash the URL (or the secret) somewhere the LLM can reuse for
the rest of the session.

The data-science API accepts multiple credentials per app, so calling this again mints an
additional token without invalidating any tokens already held by other clients.

## When to call

1. **Right after `modify_python_js_data_app` create of a prod app** — the new prod has a
   managed repo but no credentials yet. Call this tool with the new app's `configuration_id`
   to enable git access. (Note: when creating a **draft**, the prod-side token is minted and
   embedded into the returned `git_clone_url` automatically — no separate call needed.)

2. **Recovery when the cached token is gone / continuing an unfinished draft** — e.g., a fresh
   sandbox continuing yesterday's work, with the previous sandbox's filesystem wiped. The
   cached `git_clone_url` is lost; the configuration ID for the prod app is all you have.
   Call this tool with the **prod app's** `configuration_id` to mint a fresh token (drafts
   have no managed repo, so always mint against prod). Existing credentials remain valid, so
   other clients are not disrupted.

## Constraints
- Only python-js prod data apps have a managed git repo. Streamlit apps reject the call with
  a clear error.
- Permissions are always `readWrite` — the LLM virtually always needs push access. The
  data-science API supports read-only credentials, but the tool does not expose that knob;
  revisit once a real use case appears.
- **get_data_apps** — Lists summaries of data apps in the project given the limit and offset or gets details of a data apps by
providing their configuration IDs.

WHEN NOT TO USE:
- Do NOT list all data apps just to find one by name. Use `search` with
  item_types=["data-app"] instead.
- Only list all data apps when you need a complete inventory.

Considerations:
- If configuration_ids are provided, the tool will return details of the data apps by their configuration IDs.
- If no configuration_ids are provided, the tool will list all data apps in the project given the limit and offset.
- Data App detail contains configuration, metadata, source code, links, and deployment info along with the latest
data app logs to investigate in-app errors. The logs may be updated after opening the data app URL.
- `deployment_info.last_run` carries the outcome of the most recent deployment attempt. For an app
  that fails to start, check its `failure_reason`/`failure_message` FIRST — they cover setup-phase
  failures (e.g. invalid secrets, git clone errors, failing setup scripts) that happen before the
  container starts and therefore never appear in the regular logs.
- `repo_url` (managed git repo URL for python-js apps) is ONLY populated on the detail path
  (when `configuration_ids` is provided). The inventory list always returns `repo_url=None`,
  even for python-js apps with a managed repo — to retrieve the URL, call this tool again
  with the target `configuration_ids`.
- When called with `configuration_ids=[<prod-cfg>]` for a python-js **prod** app, the response
  includes a `drafts: [...]` array of every draft (configs with `isDraft=true` and
  `parentConfigurationId == <prod-cfg>`) currently in the project. Drafts in trash are not
  included. Use this to discover existing drafts when continuing a previously abandoned
  iteration (Scenario C in `modify_python_js_data_app`). The array is empty for drafts
  themselves and for Streamlit apps.
- **deploy_data_app** — Deploys/redeploys a data app or stops a running data app in the Keboola environment asynchronously, given the
action and the configuration ID.

**MCP never runs git on your behalf.** All git work — clone, branch, commit, push, merge,
branch-delete — is yours. This tool only triggers deploys against existing git state.

## Mode (python-js apps)
- `mode='dev'` deploys the target as a **dev version of the data app** — the runtime uses a
  development `setup.sh` (hot reload) and the data-app proxy enables an auto-auth path so an
  iframe preview can render without a manual login. Only meaningful on **draft** configs
  (python-js apps with `isDraft=true`).
- For prod redeploys (including after merging a draft's branch into `main`), use no `mode` —
  the prod app picks up the current `main`.
- The branch a draft deploys from is pinned in `parameters.dataApp.git.branch` at create time;
  there is no deploy-time override.

## Configuration publishing
- Every deploy **publishes the latest saved configuration version** (for both python-js and
  Streamlit apps), so configuration-only changes -- secrets, Storage access, git branch, size --
  go live together with the code. For python-js apps the code itself is always re-pulled from git.
- `deployment_info.version` is the configuration version the app is actually running;
  `deployment_info.latest_config_version` is the latest saved one. If
  `deployment_info.has_unpublished_changes` is true, the configuration was changed after the
  deploy was triggered -- deploy again to publish it.

## Streamlit apps
Streamlit apps have no managed git repo, so `mode` has no effect on the deployed app.
`mode=None` is the expected call shape.

## General considerations
- Redeploying a data app takes some time, and the app may temporarily report status "stopped" during the
  restart.
- After deployment, the deployment info includes the app URL and the latest logs to help diagnose in-app
  errors.
- **delete_python_js_data_app_draft** — Deletes a python-js DRAFT data app — both the data-app instance (DSAPI) and its Storage
configuration.

**MCP never runs git on your behalf.** Deleting the feature branch on the remote is your job;
this tool only tears down the draft config and its data-app instance.

WHEN TO CALL: at the end of a promote-to-prod sequence, after you have merged the draft's
branch into `main`, pushed, deleted the feature branch from the remote, and redeployed the
prod app. The Keboola UI lists drafts under their parent prod app; once you call this tool,
the draft disappears from that list.

WHAT THIS TOOL REFUSES:
  - prod apps (no `isDraft` flag) — protects against accidental prod deletion;
  - Streamlit apps — they have no draft concept.

WHAT THIS TOOL DOES NOT DO:
  - Run git. Deleting the feature branch on the remote is your job.
  - Revoke the prod-side git credential minted when the draft was created. Credential
    rotation is the user's job via the Keboola UI.

After a successful call, pivot back to the parent prod app (its configuration_id is returned
in the response) or to `get_data_apps` for further work.
- **docs_query** — Answers a question using the Keboola documentation as a source.
- **create_flow** — Creates a new legacy (non-conditional) flow using `keboola.orchestrator`.

PRE-REQUISITES:
- Always use `get_flow_schema` with flow_type="keboola.orchestrator" and review `get_flow_examples` if unknown
- Collect component configuration IDs for every task you include

RULES:
- `phases` and `tasks` must follow the orchestrator schema; each entry must include `id` and `name`
- Phases run sequentially; tasks inside a phase run in parallel
- Use `dependsOn` on phases to sequence them; reference other phase ids
- Always share the returned links with the user

WHEN TO USE:
- Simple/linear orchestrations without branching or conditions
- ETL/ELT pipelines where phases just need ordering and parallel task groups
- **create_conditional_flow** — Creates a new conditional flow configuration using `keboola.flow`.

PRE-REQUISITES:
- Always use `get_flow_schema` with flow_type="keboola.flow" and review `get_flow_examples` if unknown
- Gather component configuration IDs for all tasks you include

RULES:
- `phases` and `tasks` must follow the keboola.flow schema; each entry needs `id` and `name`
- Exactly one entry phase (no incoming transitions); all phases must be reachable
- Connect phases via `next` transitions; no cycles or dangling phases; empty `next` means flow end
- Task/phase failures already stop the flow; add retries/conditions only if the user requests them
- Always share the returned links with the user

WHEN TO USE:
- Flows needing branching, conditions, retries, or notifications
- Default choice when user simply says "create a flow," unless they explicitly want legacy orchestrator behavior
- **get_flows** — Lists flows or retrieves full details for specific flows.

WHEN NOT TO USE:
- Do NOT call with `flow_ids=[]` just to find a flow by name. Use `search` with
  item_types=["flow"] instead.
- Only use `flow_ids=[]` when you need a complete list of all flows in the project.

OPTIONS:
- `flow_ids=[]` → summaries of all flows in the project
- `flow_ids=["id1", ...]` → full details (including phases/tasks) for those flows
- **update_flow** — Updates an existing flow configuration (either legacy `keboola.orchestrator` or conditional `keboola.flow`).

PRE-REQUISITES:
- Always use `get_flow_schema` (and `get_flow_examples`) for that flow type you want to update to follow the
required structure and see the examples if unknown
- Only pass `phases`/`tasks` when you want to replace them; omit to keep the existing ones unchanged

RULES (ALL FLOWS):
- `flow_type` must match the stored component id of the flow; do not switch flow types during update
- `phases` and `tasks` must follow the schema for the selected flow type; include at least `id` and `name`
- Tasks must reference existing component configurations; keep dependencies consistent
- Always provide a clear `change_description` and surface any links returned in the response to the user

CONDITIONAL FLOWS (`keboola.flow`):
- Maintain a single entry phase and ensure every phase is reachable; connect phases via `next` transitions
- No cycles or dangling phases; failed tasks already stop the flow, so only add retries/conditions if requested

LEGACY FLOWS (`keboola.orchestrator`):
- Phases run sequentially; tasks inside a phase run in parallel; `dependsOn` references other phase ids
- Use `continueOnFailure` or best-effort patterns only when the user explicitly asks for them

WHEN TO USE:
- Renaming a flow, updating descriptions, adding/removing phases or tasks, adjusting dependencies,
or enabling/disabling flow execution
- **modify_flow** — Updates an existing flow configuration (either legacy `keboola.orchestrator` or conditional `keboola.flow`) or
manages schedules for this flow.

PRE-REQUISITES:
- Always use `get_flow_schema` (and `get_flow_examples`) for that flow type you want to update to follow the
required structure and see the examples if unknown
- Only pass `phases`/`tasks` when you want to replace them; omit to keep the existing ones unchanged

RULES (ALL FLOWS):
- `flow_type` must match the stored component id of the flow; do not switch flow types during update
- `phases` and `tasks` must follow the schema for the selected flow type; include at least `id` and `name`
- Tasks must reference existing component configurations; keep dependencies consistent
- Always provide a clear `change_description` and surface any links returned in the response to the user
- A flow can have multiple schedules for automation runs. Add/update/remove schedules only if requested.
- When updating a flow or a schedule, specify only the fields you want to update, others will be kept unchanged.

CONDITIONAL FLOWS (`keboola.flow`):
- Maintain a single entry phase and ensure every phase is reachable; connect phases via `next` transitions
- No cycles or dangling phases; failed tasks already stop the flow, so only add retries/conditions if requested

LEGACY FLOWS (`keboola.orchestrator`):
- Phases run sequentially; tasks inside a phase run in parallel; `dependsOn` references other phase ids
- Use `continueOnFailure` or best-effort patterns only when the user explicitly asks for them

WHEN TO USE:
- Renaming a flow, updating descriptions, adding/removing phases or tasks, updating schedules,
adjusting dependencies, or enabling/disabling flow execution
- **get_flow_examples** — Retrieves examples of valid flow configurations.

PRE-REQUISITES:
- Unknown examples for the target flow type: `keboola.flow` (conditional) or `keboola.orchestrator` (legacy) to help
build the specific flow configuration by mirroring the structure/fields.

RULES:
- Conditional-flow examples require conditional flows to be enabled; otherwise use legacy orchestrator examples
- Present the examples or cite unavailability to the user
- **get_flow_schema** — Returns the JSON schema for the given flow type (markdown).

PRE-REQUISITES:
- Unknown schema for the target flow type: `keboola.flow` (conditional) or `keboola.orchestrator` (legacy)

RULES:
- Projects without conditional flows enabled cannot request `keboola.flow` schema
- Use the returned schema to shape `phases` and `tasks` for `create_flow` / `create_conditional_flow` /
`update_flow`
- **get_jobs** — Retrieves job execution information from the Keboola project.

CONTEXT:
Jobs in Keboola are execution records of components (extractors, transformations, writers, flows).
Each job represents a single run with its status, timing, configuration, and results.

TWO MODES OF OPERATION (controlled by job_ids parameter):

MODE 1: GET DETAILS FOR SPECIFIC JOBS (job_ids is non-empty)
- Provide one or more job IDs: job_ids=["12345", "67890"]
- Returns: FULL details for each job including status, config_data, results, timing, and metadata
- Ignores: All filtering/sorting parameters (status, component_id, config_id, limit, offset, sort_by, sort_order)
- Use when: You know specific job IDs and need complete information about them

MODE 2: LIST/SEARCH JOBS (job_ids is empty)
- Leave job_ids empty: job_ids=[]
- Returns: SUMMARY list of jobs (id, status, component_id, config_id, timing only - no config_data or results)
- Supports: Filtering by status/component_id/config_id, pagination with limit/offset, sorting
- Use when: You need to find jobs, see recent executions, or monitor job history

DECISION GUIDE:
- Start with MODE 2 (list) to find jobs → then use MODE 1 (details) if you need full information
- If you already know job IDs → use MODE 1 directly
- For monitoring/browsing → use MODE 2 with filters

NOTE: Jobs cannot be found by name using the `search` tool. However, always use the filtering
parameters (status, component_id, config_id) to narrow results rather than listing all jobs
with no filters. If you need to find jobs for a specific configuration but only know its name,
first use `search` to find the configuration ID, then filter jobs by that config_id.

COMMON WORKFLOWS:
1. Find failed jobs: job_ids=[], status="error" → identify problematic job IDs → get details with MODE 1
2. Check recent runs: job_ids=[], component_id="...", limit=10 → see latest executions
3. Monitor specific job: job_ids=["123"] → poll for status and results
4. Troubleshoot config: job_ids=[], component_id="...", config_id="...", status="error" → find which runs failed

EXAMPLES:

MODE 1 - Get full details:
- job_ids=["12345"] → detailed info for job 12345
- job_ids=["12345", "67890"] → detailed info for multiple jobs

MODE 2 - List/search jobs:
- job_ids=[] → list latest 100 jobs (default)
- job_ids=[], status="error" → list only failed jobs
- job_ids=[], status="processing" → list currently running jobs
- job_ids=[], component_id="keboola.ex-aws-s3" → list jobs for S3 extractor
- job_ids=[], component_id="keboola.ex-aws-s3", config_id="12345" → list jobs for specific configuration
- job_ids=[], limit=50, offset=100 → pagination (skip first 100, get next 50)
- job_ids=[], sort_by="endTime", sort_order="asc" → oldest completed first
- job_ids=[], sort_by="durationSeconds", sort_order="desc" → longest running first

LOG RETRIEVAL (only in MODE 1):
- Set include_logs=True to fetch execution logs from the Storage API events
- Logs are fetched using the job's runId and returned in chronological order
- Use log_tail_lines to control how many recent log events to return (default 50, max 500)
- Use log_event_types to filter by event type: ["error"] for just errors, ["error", "warn"] for errors and warnings
- If a job has no runId (e.g., not yet started), logs will be None

EXAMPLES WITH LOGS:
- job_ids=["12345"], include_logs=True → job details + last 50 log events
- job_ids=["12345"], include_logs=True, log_event_types=["error"] → job details + only error events
- job_ids=["12345"], include_logs=True, log_tail_lines=200 → job details + last 200 log events
- **run_job** — Starts a new job for a given component or transformation.
- **create_oauth_url** — Generates an OAuth authorization URL for a Keboola component configuration.

When using this tool, be very concise in your response. Just guide the user to click the
authorization link.

Note that this tool should be called specifically for the OAuth-requiring components after their
configuration is created e.g. keboola.ex-google-analytics-v4 and keboola.ex-gmail.
- **get_project_info** — Retrieves structured information about the current project,
including essential context and base instructions for working with it
(e.g., transformations, components, workflows, and dependencies).

Always call this tool at least once at the start of a conversation
to establish the project context before using other tools. Reports on exactly one project;
pass `project_id` to pick which when the session is scoped to 2+ projects.
- **update_project_description** — Updates the description of the current Keboola project.
- **get_accessible_projects** — Lists the Keboola projects the current login can access across the stack, each with its SQL
dialect and organization.

Only call this when a data tool call has actually failed asking you to confirm a project scope --
the session may already be pre-scoped (e.g. the user chose specific projects at `login` time), in
which case data tools already work and this call would just be extra, unnecessary API traffic.
When a scope genuinely is needed: present the projects, ask whether the user wants to work across
all of them or a subset, then call `set_project_scope` with their choice. This tool compacts
several API calls (token introspection plus a per-project token verify for the SQL dialect and
organization) into one result, so the assistant does not need a separate get_project_info call per
project. Pass with_llm_instruction=true on the first call to also receive the base working
instructions grouped by dialect.
- **set_project_scope** — Scopes the current session to a set of Keboola projects.

Mints a scoped access token (narrowed to `project_ids`, optionally read-only) that is used for the
rest of the conversation. Read-only tools then run against every scoped project in a single call;
write/modify/delete tools take a `project_id` argument naming which scoped project to target (required
once 2+ projects are scoped). Call this when the user states which projects to work on; it can be
called again any time to re-scope.

On most transports the server does not remember this scope between calls: pass the returned
`scope_token` as the `scope_token` argument on every subsequent tool call in this conversation
to keep it in effect. Not needed for a local server or an OAuth-authenticated session, both of
which persist the confirmed scope server-side instead -- `scope_token` is null there.
- **create_project** — Creates a brand-new Keboola project for a session that has no Keboola credentials yet, and
signs this session in to it.

Use this ONLY when a tool call has reported that the session has no Keboola credentials and the
user has no project/token to give you -- it is how a first-time user gets started without
leaving the conversation. Never call it to add a project to a session that already works: it
refuses, because it would replace the credentials that session is using.

Not every Keboola stack offers this -- it is gated by a stack feature. On a stack without it the
tool says so and the user has to bring their own project instead; that is a fact about the
stack, so do not retry.

The project starts out owned by nobody. Show the user the returned `confirm_url` and tell them
to open it: signing in there makes the project permanently theirs. Until they do, the project is
temporary and Keboola may reclaim it, and once they do, the session created here is revoked and
they continue with their own login. Data tools work against the new project in the meantime.
- **find_component_id** — Returns list of component IDs that match the given query.

WHEN TO USE:
- Use when you want to find the component for a specific purpose.

USAGE EXAMPLES:
- user_input: "I am looking for a salesforce extractor component"
  → Returns a list of component IDs that match the query, ordered by relevance/best match.
- **search** — Searches for Keboola items (tables, buckets, components, configurations, transformations, flows, data-apps, etc.)
in the current project and returns matching ID + metadata.

This tool supports two complementary search types:

1) textual
- Searches items by name, server-side (fast, independent of project size).
- Tokenized full-text name matching, case- and diacritics-insensitive. Pass the plain name; do NOT build
  regex (rejected). It is NOT typo-corrected — misspellings may not match.
- Prefers the current branch context; when nothing is found there, automatically widens the search to all
  branches of the project — such hits carry `branch_id`/`branch_name` so you can tell where they live.

2) config-based
- Searches item configurations (JSON objects) by matching patterns against the configuration values converted
  to a string, optionally narrowed by JSON path `scopes`.
- Returns also `match_scopes` with JSON paths and matched patterns per scope.

THIS IS THE PRIMARY DISCOVERY TOOL. Always use it BEFORE any get_* tool when you need to find items
by name or specific configuration content. Do NOT enumerate items with get_buckets, get_tables, get_configs,
get_flows, or get_data_apps just to locate a specific item — use this tool instead.

WHEN TO USE:
- User asks to "find", "locate", or "search for" something by name, keyword, text pattern, configuration content or
value
- User mentions a partial name and you need to find the full item (e.g., "find the customer table")
- User asks "what tables/configs/flows do I have with X in the name?"
- You need to discover items before performing operations on them
- User asks to "list all items with [name] or [configuration value/part] in it"
- User asks where a value, table, component, specific configuration ID, or specific settings is used in components,
data-apps, flows, or transformations
- You need to trace lineage by searching for IDs referenced in configurations, or to find flows using a
  specific component, or find usage of a bucket/table in transformations or components, or to find items with
  specific parameters.
- User asks to "what is the genesis of this item?" or "explain me business logic of this item?"

HOW IT WORKS:
- Supports two types:
  - search_type="textual": tokenized full-text name search, server-side. Names only — descriptions, column
    names, IDs and configuration contents are NOT searched (use config-based search for configuration contents,
    or get_tables for columns). Matching is case- and diacritics-insensitive but NOT typo-corrected.
  - search_type="config-based": matches inside configuration JSON objects, optionally narrowed by JSON path `scopes`
- case-insensitive search
- mode for pattern search: applies to config-based only — `literal` (default) or `regex`. Textual search ignores
  `mode` (always full-text) and rejects `regex`.
- Multiple patterns work as OR condition - matches items containing ANY of the patterns
- Each result includes the item's ID, name, creation date, and relevant metadata; the response also carries
  `total` and `by_type` counts and the `branch_scope` the hits come from
- textual search prefers the current branch; on zero hits it automatically retries across all branches of the
  project and marks the response with branch_scope="all-branches"
- scopes (config-based) narrow matching to specific JSONPath areas within configurations; matching is performed
  against the stringified JSON node content in those areas.
- config-based always returns all matched paths per item in `match_scopes` (including matched patterns)

IMPORTANT:
- Always use this tool when the user mentions a name but you don't have the exact ID
- The search returns IDs that you can use with other tools (e.g., get_tables, get_configs, get_flows)
- Results are ordered by the `updated` field, most recent first. `updated` is the item's last update time
  when available, or its creation time otherwise (textual/global-search hits expose only the creation time).
- Textual search matches names only, with tokenized full-text matching (case/diacritics-insensitive; not
  typo-corrected; no regex). It may not return every item the legacy enumeration did. To find items by
  description or by table column, use get_tables; to find items by configuration content, use config-based search.
- For exact ID lookups, use specific tools like get_tables, get_configs, get_flows instead
- Use specific `scopes` only when you know the config structure (schema or real example); otherwise run config-based
  search without scopes.
- Use find_component_id and get_configs tools to find configurations related to a specific component
- If results are too numerous or empty, ask the user to refine their query rather than enumerating all items.

USAGE EXAMPLES:
1) textual search examples:
- user_input: "Find all tables with 'customer' in the name"
    → patterns=["customer"], item_types=["table"]
    → Returns all tables whose name matches "customer"

- user_input: "Search for the sales transformation"
    → patterns=["sales"], item_types=["transformation"]
    → Returns transformations with "sales" in the name

- user_input: "Find items named 'daily report' or 'weekly summary'"
    → patterns=["daily report", "weekly summary"], item_types=[]
    → Returns all items matching any of these patterns

- user_input: "Show me all configurations related to Google Analytics"
    → patterns=["google analytics"], item_types=["configuration"]
    → Returns configurations with matching names

2) config-based search examples:
- user_input: "Find transformations/configs/components referencing table in.c-prod.customers"
    -> patterns=["in.c-prod.customers"], item_types=["transformation", "configuration"],
    search_type="config-based"
    -> No scopes = search whole stringified config; result includes `match_scopes` with exact paths + patterns

- user_input: "Find configurations/transformations (etc.) using specific setting / id anywhere"
    -> patterns=["setting", "id"], item_types=["configuration", "transformations"], search_type="config-based",

- user_input: "Find configurations/transformations (etc.) using specific setting / id in parameters"
-> patterns=["setting", "id"], item_types=["configuration", "transformations"], search_type="config-based",
scopes=["parameters"]

- user_input: "Find configurations/transformations (etc.) using specific setting / id in storage"
-> patterns=["setting", "id"], item_types=["configuration", "transformations"], search_type="config-based",
scopes=["storage"]

- user_input: "Find configurations/transformations (etc.) using specific setting / id in authorization"
    -> patterns=["setting", "id"], item_types=["configuration", "transformations"], search_type="config-based",
    scopes=["parameters.authorization", "authorization"]

- user_input: "Find components/transformations using my_bucket in input or output mappings"
    -> patterns=["my_bucket"], item_types=["configuration", "transformation"], search_type="config-based",
    scopes=["storage.input", "storage.output"]
    -> Returns matches with paths like `storage.input.tables[0].source`, `storage.input.files[0].source`,
    or `storage.output.tables[0].destination`

- user_input: "Find flows using configuration ID 01k9cz233cvd1rga3zzx40g8qj"
    -> patterns=["01k9cz233cvd1rga3zzx40g8qj"], item_types=["flow"], search_type="config-based",
    scopes=["tasks", "phases"]

- user_input: "Find transformations using this table / column / specific code in its script"
    -> patterns=["element"], item_types=["transformation"], search_type="config-based",
    scopes=["parameters", "storage"]

- user_input: "Find data apps using something in its config / python code / setting"
    -> patterns=["something"], item_types=["data-app"], search_type="config-based"
    -> Returns data apps where script/config sections contain the keyword and includes `match_scopes`
- **search_semantic_context** — Searches semantic models and semantic objects using regex patterns matched against their names, descriptions and
stringified JSON attributes.

Returns compact matches grouped by semantic model. Each match includes the semantic object type,
the paths where the patterns matched, and compact object view.

CONSIDERATIONS:
- The search is case-insensitive by default. Use `case_sensitive=True` when exact casing matters.
- The search is performed against semantic object names and data attributes which are stringified JSON objects
following their corresponding JSON schema.
- The search can be scoped to specific semantic models or semantic object types but prefer broader search without
scoping unless required by the context.
- Matches carry the same `scope`/`project_id`/`source_project_id`/`target_project_ids` fields as
`get_semantic_context` -- see that tool's CONSIDERATIONS for what they mean and imply.

WHEN TO USE:
- When you need to discover which semantic objects are relevant to a user request.
- When you know business terms, column names, metric fragments, or rule names, but not exact object UUIDs.
- When you need to find semantic objects by keyword or values used in their attributes.

WHEN NOT TO USE:
- When you know the exact IDs.

EXAMPLES:
- Find semantic objects by business concepts for revenue or sales:
  `patterns=["revenue", "sales"]`
- Find semantic objects using a Keboola table ID:
  `patterns=["out.c-sales-main.fact_orders"]`
- Find semantic dataset for a certain table:
  `patterns=["in.c-sales-main.fact_orders"], semantic_types=["semantic-dataset"]`
- Find semantic datasets that mention a column name:
  `patterns=["column_name"], semantic_types=["semantic-dataset"]`
- Search semantic objects e.g. semantic metrics, relationships, and constraints using a certain semantic dataset:
  `patterns=["table-id-of-the-dataset"], semantic_types=["semantic-metric",`
  `"semantic-relationship", "semantic-constraint"]`
- Search semantic constraints using e.g. certain semantic metrics and certain semantic datasets:
  `patterns=["metric-name-1", "metric-name-2", "table-id-from-the-dataset"],`
  `semantic_types=["semantic-metric", "semantic-relationship"]`
- Search something within specific semantic models only:
  `patterns=["something"], semantic_model_ids=["<semantic-model-uuid-1>", "<semantic-model-uuid-2>"]`
- **get_semantic_context** — Loads semantic objects grouped by semantic object type.

CONSIDERATIONS:
- If a selection has empty `ids`, the tool returns all objects of that type in compact form.
- If a selection has non-empty `ids`, the tool returns only those specific objects with full attributes.
- `semantic_model_ids` optionally narrows the lookup to specific semantic models.
- An object returned here carries `scope` ("project", "organization", or "targeted"), `project_id`
  (the project it was created in -- present regardless of scope), `source_project_id` (which
  project an "organization"-scope object came from, if known), and `target_project_ids` (the
  sibling projects granted read access, for "targeted" scope). This is reported only for an
  object that appears in the current project's listing or that was loaded by id -- an
  "organization"-scope object owned by another project is not surfaced by the listing path
  today, only via a direct id lookup. `scope` describes visibility of the metastore object
  itself, not whether its underlying Keboola Storage table is actually reachable from every
  project that can see it -- a "targeted"/"organization" object's data may still need its
  bucket separately shared and linked (`get_shared_buckets`/`link_shared_bucket`) before a
  query against it will work outside the owning project. Pass `resolve_data_location=True` to
  check this directly instead of inferring it from scope alone.
- An object's `scope_elevation_requested_at` being set means a project has asked an
  organization admin to promote it from "project" to "organization" scope, and the request is
  still pending. Treat this as a forward-looking signal: once approved, the object (a
  semantic-model's promotion also carries its datasets' underlying tables along) becomes
  visible org-wide, which may require separately sharing the physical data too -- promotion
  alone does not do that automatically.

WHEN TO USE:
- When you already know IDs of the semantic objects you want to load and want to inspect them in detail.
- When you want to list all semantic objects of certain types or specific semantic models.
- When you want to list semantic models.

WHEN NOT TO USE:
- When you need to discover semantic objects.

EXAMPLES:
- List all semantic models:
  `semantic_objects=[{"object_type": "semantic-model"}]`
- List semantic datasets and metrics for specific semantic models:
  `semantic_objects=[{"object_type": "semantic-dataset"}, {"object_type": "semantic-metric"}],`
  `semantic_model_ids=["model-uuid-1", "model-uuid-2"]`
- Get detailed context for specific semantic objects by their id:
  `semantic_objects=[{"object_type": "semantic-dataset", "ids": ["dataset-uuid-1"]},`
  `{"object_type": "semantic-metric", "ids": ["metric-uuid-1", "metric-uuid-2"]}]`
- List all constraints for specific semantic models:
  `semantic_objects=[{"object_type": "semantic-constraint"}], semantic_model_ids=["model-uuid-1"]`
- **get_semantic_schema** — Returns JSON schemas for the requested semantic object types.

WHEN TO USE:
- When you want to know the JSON schema of a semantic object type, e.g. before searching something specific.
- **validate_semantic_query** — Performs best-effort semantic validation of an SQL query against one or more semantic models and compares it with
the expected semantic objects provided.

RETURNS:
- `validation_auto_detected`: semantic validation built from objects heuristically detected in the SQL
- `validation_detected_from_expected`: semantic validation built only from explicitly provided expected object IDs
- expected semantic objects that were matched or missing in the auto-detected result
- unexpected auto-detected objects outside the expected semantic scope

LIMITATIONS:
- Detection is heuristic and based on string matching over SQL and semantic metadata.
- The tool does not parse SQL semantically and does not execute the query.
- Auto-detected objects, missing objects, and relationship matches may therefore be imperfect.
- Use the result as a best-effort semantic check, not as a formal proof that the query is correct.

CONSIDERATIONS:
-  Prefer calling this tool before executing any SQL that touches semantic objects.
- This tool confirms the SQL dialect, surfaces semantic constraint violations, and provides post-execution checks.
- Only proceed to query_data once this tool returns valid=True and violations is empty. If violations are found,
fix the query first or consider the limitations of this tool.
- Entries under `semantic_models` carry `scope`/`project_id`/`source_project_id`/
`target_project_ids` -- see `get_semantic_context`'s CONSIDERATIONS for what they mean. A
"targeted"/"organization"-scope model does not guarantee the query is actually runnable from
every project that can see it; this tool validates against the semantic layer, not against
whether the underlying Storage tables are reachable here. Pass `resolve_data_location=True` to
check that directly for every used dataset instead of inferring it from scope alone.

WHEN TO USE:
- Before generating or approving a query that should follow a semantic model.
- When you want to validate a SQL query against the semantic objects before executing it using "query_data" tool
or creating a new SQL transformation out of it, especially when investigating data quality issues.
- When you want to verify that a query uses the intended semantic objects.
- When you need to surface semantic business-rule violations or follow-up checks.

EXAMPLES:
- Validate a SQL query against one semantic model:
  `sql_query="SELECT SUM(\"REVENUE\") FROM ...", semantic_model_ids=["semantic-model-uuid"],`
  `expected_semantic_objects=[{"object_type": "semantic-dataset"}]`
- Validate a cross-model query against two semantic models:
  `sql_query="SELECT * FROM ...", semantic_model_ids=["model-uuid-1", "model-uuid-2"],`
  `expected_semantic_objects=[{"object_type": "semantic-dataset", "ids": ["dataset-uuid-1"]}]`
- Validate a query and compare it against expected objects:
  `sql_query="SELECT SUM(\"REVENUE\") FROM ...", semantic_model_ids=["semantic-model-uuid"],`
  `expected_semantic_objects=[{"object_type": "semantic-metric", "ids": ["metric-uuid-1"]}]`
- **query_data** — Executes an SQL SELECT query to get the data from the underlying database.

BEFORE QUERYING:
* Always verify the table has a non-null fullyQualifiedName from get_tables tool.
  If it does not, the table is not SQL-accessible from this workspace — do not attempt the query and inform user.

CRITICAL SQL REQUIREMENTS:

* ALWAYS check the SQL dialect before constructing queries.
* Do not include any comments in the SQL code
* Use delimited identifiers and FQN format for the current SQL dialect.

TABLE AND COLUMN REFERENCES:
* Always use fully qualified table names in the exact FQN format provided by table information tools
* Follow the identifier structure exactly as shown by table info tools for the current SQL dialect
* For every column reference, call get_tables first and copy that column's `quotedName` value VERBATIM.
  Do not guess the name, re-case it, or hand-quote it yourself — use the exact `quotedName` from get_tables.
* Snowflake case trap: unquoted identifiers fold to UPPERCASE while quoted identifiers are case-sensitive,
  so hand-quoting a guessed lowercase name (e.g. "date") fails with `invalid identifier` even though the
  column physically exists as DATE. The `quotedName` from get_tables already carries the correct case and
  dialect quoting, so copying it avoids this.

CTE (WITH CLAUSE) RULES:
* ALL column references in main query MUST match exact case used in the CTE
* If you alias a column in a CTE, reference it under the aliased name in the subsequent queries
* Define all column aliases explicitly in CTEs
* Use delimited identifiers in both CTE definition and references to preserve case

FUNCTION COMPATIBILITY:
* Check data types before using date functions (DATE_TRUNC, EXTRACT require proper date/timestamp types)
* Cast VARCHAR columns to appropriate types before using in date/numeric functions

ERROR PREVENTION:
* Keboola Storage stores empty cells as empty strings ('') rather than NULL — even for columns reported as
  VARCHAR/STRING with nullable=false — so casting a text column straight to numeric/date/timestamp throws
  (`Numeric value '' is not recognized` on Snowflake, `Bad double value` on BigQuery).
* When casting, use the dialect-safe cast: on Snowflake `TRY_CAST(x AS ...)` (or `TRY_TO_DATE`/`TRY_TO_NUMBER`),
  which returns NULL instead of erroring; on BigQuery `SAFE_CAST(x AS ...)` — BigQuery has NO `TRY_CAST`.
  Alternatively wrap the column with `NULLIF(col, '')` before CAST. This is defensive against empty strings,
  not a guarantee for genuinely non-numeric or non-date values.
* For numeric casts always specify precision and scale so fractional values survive: on Snowflake use
  `TRY_CAST(x AS NUMBER(38,9))` or `TRY_TO_NUMBER(x, 38, 9)` (never bare `NUMBER`/`DECIMAL`/`NUMERIC`, which
  mean `NUMBER(38,0)` and silently round `'3.75'` to 4); on BigQuery use `NUMERIC`/`BIGNUMERIC` (never bare
  `INT64`, which truncates). Use `FLOAT`/`DOUBLE`/`FLOAT64` only for ratios/averages.
* Check for division by zero using NULLIF(denominator, 0)
* Always use the LIMIT clause in your SELECT statements when fetching data. There are hard limits imposed
  by this tool on the maximum number of rows that can be fetched and the maximum number of characters.
  The tool will truncate the data if those limits are exceeded. Pick the LIMIT to match the actual need:
  - Use a small LIMIT (<=100) for inspection, sampling, or checking distinct values.
  - Compute aggregates (COUNT, GROUP BY, SUM, AVG, etc.) in SQL rather than pulling raw rows to aggregate
    them yourself — a large LIMIT does not make row-level aggregation in context correct or efficient.
  - Only use a large LIMIT when the user explicitly asked for row-level data.
  - A truncated result is a contiguous prefix of the query's output, not a random sample: any aggregate
    computed over truncated data is wrong, not just incomplete.

DATA VALIDATION:
* When querying columns with categorical values, use query_data tool to inspect distinct values beforehand
* Ensure valid filtering by checking actual data values first
- **get_buckets** — Lists buckets or retrieves full details of specific buckets, including descriptions,
lineage references (created/updated by), and links.

WHEN NOT TO USE:
- Do NOT call with `bucket_ids=[]` just to find a bucket by name. Use `search` with
  item_types=["bucket"] instead.
- Only use `bucket_ids=[]` when you need a complete inventory of all buckets in the project.

EXAMPLES:
- `bucket_ids=[]` → summaries of all buckets in the project
- `bucket_ids=["id1", ...]` → full details of the buckets with the specified IDs
- **get_tables** — Lists tables in buckets or retrieves full details of specific tables, including fully qualified database name,
column definitions, lineage references (created/updated by) and links.

WHEN NOT TO USE:
- Do NOT list tables across buckets just to find a table by name. Use `search` with
  item_types=["table"] instead — it also matches column names and descriptions.
- Only use `bucket_ids` listing when you need all tables in specific known buckets.

RETURNS:
- With `bucket_ids`: Summaries of tables (ID, name, description, primary key).
- With `table_ids`: Full details including columns, data types, and fully qualified database names.
- With `table_ids` and `include_usage`: Full details plus components / transformations that use the tables
  in their input / output mappings. Use only when explicitly needed or evident from context; usage calculation
  might be demanding in big projects.

COLUMN DATA TYPES:
- database_native_type: The actual type in the storage backend (Snowflake, BigQuery, etc.)
  with precision, scale, and other implementation details
- keboola_base_type: Standardized type indicating the semantic data type. May not always be
  available. When present, it reveals the actual type of data stored in the column - for example,
  a column with database_native_type VARCHAR might have keboola_base_type INTEGER, indicating
  it stores integer values despite being stored as text in the backend.

QUERYABILITY RULE:
- A table is directly queryable via query_data tool only if fullyQualifiedName is present and non-null
  in the response.
- If fullyQualifiedName is absent or null (e.g. for linked/alias tables from other projects),
  the table cannot be queried via SQL from this workspace.
- Do not attempt to construct or guess the FQN — it will not work. In that case,
  inform the user of the limitation immediately.

EXAMPLES:
- `bucket_ids=["id1", ...]` → summary info of the tables in the buckets with the specified IDs
- `table_ids=["id1", ...]` → detailed info of the tables specified by their IDs
- `bucket_ids=[]` and `table_ids=[]` → empty list; you have to specify at least one filter
- **update_descriptions** — Updates the description for a Keboola storage item.

This tool supports three item types, inferred from the provided item_id:

- bucket: item_id = "in.c-bucket"
- table: item_id = "in.c-bucket.table"
- column: item_id = "in.c-bucket.table.column"

Usage examples (payload uses a list of DescriptionUpdate objects):
- Update a bucket:
  updates=[DescriptionUpdate(item_id="in.c-my-bucket", description="New bucket description")]
- Update a table:
  updates=[DescriptionUpdate(item_id="in.c-my-bucket.my-table", description="New table description")]
- Update a column:
  updates=[DescriptionUpdate(item_id="in.c-my-bucket.my-table.my_column", description="New column description")]
- **get_shared_buckets** — Lists buckets shared with this project by other Keboola projects that are not necessarily
linked into this project yet (the Data Catalog "Shared with me" view). Use this to answer
"what data is shared with this project?" or "what could I link?" — `get_buckets` only
returns buckets already in this project (including already-linked ones).

Results are paginated (`limit`/`offset`) because the number of shared buckets can be large
for projects in big organizations — always check `total_count` and the `message` to see
whether more results exist, and page through with `offset` rather than assuming a single
call returns everything.

Use `link_shared_bucket` to link a shared bucket returned here into this project.
- **link_shared_bucket** — Links a bucket shared with this project (found via `get_shared_buckets`) into this project
as a new local bucket, so its tables become directly queryable/joinable like any other
bucket in the project.

Not idempotent: calling this again with the same `target_bucket_name` fails once that name
already exists, so don't retry blindly on error without checking whether the link succeeded.

Note: unlike `get_buckets`, the returned bucket does not participate in this server's
prod/dev branch-shading (its `branch_id` is not resolved) -- linking always targets the
caller's current branch context directly.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "keboola-mcp-server": {
    "command": "uvx",
    "args": ["keboola-mcp-server"]
  }
}
```

## Documentation

## What keboola/keboola-mcp-server does

Keboola Connection becomes accessible to MCP-compatible assistants through tools for data operations and project administration. The server can query storage tables, manage table and bucket descriptions, search project documentation and metadata, and retrieve project context. It also covers component configurations, SQL transformations, job execution records, workflow pipelines, and Keboola data apps.

Configuration workflows include inspecting component schemas and examples, listing or retrieving configurations, creating and updating configurations and rows, and running synchronous component actions. SQL transformation tools distinguish Snowflake and BigQuery transformations from Python, R, and DuckDB transformations. Dedicated tools cover Streamlit and Python-JS data apps, including deployment and Python-JS draft workflows.

## How it works

The hosted service is exposed at a region-specific URL under `/mcp` and communicates using remote Streamable HTTP. OAuth redirects the user to Keboola authentication, after which project selection can happen in the conversation. Operations are scoped to the selected project and can target a development branch by sending its branch ID in the `X-Branch-Id` header.

HTTP clients can restrict the exposed tool surface with `X-Allowed-Tools`, `X-Disallowed-Tools`, or `X-Read-Only-Mode`. `X-Deferred-Tools` changes tool-list metadata for deferred loading in supported Claude clients; it does not grant or revoke access.

## Setup and configuration

For the remote service, open a Keboola project’s Settings and MCP Server tab, copy the regional server URL, and add it to the MCP client. Claude Code can be registered from a terminal with:

```bash
claude mcp add --transport http keboola https://mcp.<YOUR_REGION>.keboola.com/mcp
```

Claude Desktop adds the connection through Settings and Integrations. Cursor offers an installation action from Keboola’s MCP Server settings, while Windsurf accepts the remote URL through its MCP configuration. Other MCP clients that support remote Streamable HTTP and OAuth can use the same endpoint.

## Tools and capabilities

- Query Keboola storage data and inspect project context.
- Search, read, and update project documentation and object metadata.
- Discover components, schemas, configuration examples, configurations, and configuration rows.
- Create and modify component configurations, SQL transformations, flows, and data apps.
- Run components or transformations and inspect job status, results, and optional logs.
- Create OAuth authorization links for components that require them.
- Deploy, stop, inspect, and manage Streamlit and Python-JS data apps.
- Work with legacy Orchestrator flows and conditional Keboola flows.

## Limitations and notes

The server does not itself read or apply Keboola row-level or column-level security policies. Where the project uses row-level security, `query_data` relies on a governed workspace and Query Service to enforce filtering. BigQuery projects use an ordinary, unfiltered workspace in this model.

Operations that change projects can be limited at the HTTP layer, including read-only mode. Python-JS source changes require external Git operations; the MCP tools manage configurations, credentials, and deployments but do not clone, commit, merge, push, or delete Git branches. Draft changes must be promoted through the documented draft-and-approval workflow before production deployment.

Remote development-branch targeting depends on sending the branch ID with each request. Without it, the remote service uses the production branch by default. Data-app deployments are asynchronous, and a redeployment may temporarily report a stopped state while the application restarts.

_Full upstream README: https://allmcps.com/mcp/keboola-keboola-mcp-server/readme_

