Skip to main content
Glama
voducdan

metabase-mcp

by voducdan

update_card

Update a saved Metabase card's properties: name, description, SQL query, database, display type, collection, visualization settings, and archive status. Preserve template tags on query change or replace them atomically with new tags.

Instructions

Update properties of a saved question/card in Metabase.

When query is updated, existing template-tags on the card are preserved by default (re-sent alongside the new SQL) so filters wired to the card aren't silently wiped. Pass template_tags to set/replace them atomically with the SQL update.

Args: card_id: The ID of the card to update. name: New name for the card. description: New description for the card. query: New SQL query for the card. database_id: New database ID (required if changing query). display: Display type (e.g. "table", "bar", "line", "pie", "scalar", "row", "area", "combo", "pivot", "smartscalar", "funnel", "waterfall", "map"). collection_id: Move the card to a different collection. visualization_settings: Visualization settings to apply. archived: Set to true to archive the card, false to unarchive. template_tags: Replacement template-tags map (tag-name -> config). If provided, replaces the card's template-tags. If omitted while query is set, existing template-tags are preserved unchanged.

Returns: The updated card object.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
card_idYes
nameNo
descriptionNo
queryNo
database_idNo
displayNo
collection_idNo
visualization_settingsNo
archivedNo
template_tagsNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The main handler for the 'update_card' tool. It accepts card_id, name, description, query, database_id, display, collection_id, visualization_settings, archived, and template_tags. It builds a payload, optionally fetches the current card to merge dataset_query changes (preserving existing template-tags if not explicitly replaced), and sends a PUT request to /api/card/{card_id}.
    @mcp.tool
    async def update_card(
        card_id: int,
        ctx: Context,
        name: str | None = None,
        description: str | None = None,
        query: str | None = None,
        database_id: int | None = None,
        display: str | None = None,
        collection_id: int | None = None,
        visualization_settings: dict[str, Any] | None = None,
        archived: bool | None = None,
        template_tags: dict[str, dict[str, Any]] | None = None,
    ) -> dict[str, Any]:
        """
        Update properties of a saved question/card in Metabase.
    
        When `query` is updated, existing template-tags on the card are preserved
        by default (re-sent alongside the new SQL) so filters wired to the card
        aren't silently wiped. Pass `template_tags` to set/replace them atomically
        with the SQL update.
    
        Args:
            card_id: The ID of the card to update.
            name: New name for the card.
            description: New description for the card.
            query: New SQL query for the card.
            database_id: New database ID (required if changing query).
            display: Display type (e.g. "table", "bar", "line", "pie", "scalar", "row", "area", "combo", "pivot", "smartscalar", "funnel", "waterfall", "map").
            collection_id: Move the card to a different collection.
            visualization_settings: Visualization settings to apply.
            archived: Set to true to archive the card, false to unarchive.
            template_tags: Replacement template-tags map (tag-name -> config). If
                provided, replaces the card's template-tags. If omitted while
                `query` is set, existing template-tags are preserved unchanged.
    
        Returns:
            The updated card object.
        """
        try:
            await ctx.info(f"Updating card {card_id}")
    
            payload: dict[str, Any] = {}
    
            if name is not None:
                payload["name"] = name
            if description is not None:
                payload["description"] = description
            if display is not None:
                payload["display"] = display
            if collection_id is not None:
                payload["collection_id"] = collection_id
            if visualization_settings is not None:
                payload["visualization_settings"] = visualization_settings
            if archived is not None:
                payload["archived"] = archived
            if query is not None or template_tags is not None:
                current_card = await metabase_client.request("GET", f"/card/{card_id}")
                current_dq = current_card.get("dataset_query") or {}
                current_sql, _ = _read_native_stage(current_dq)
    
                new_sql = query if query is not None else current_sql
                if not new_sql:
                    raise ToolError(
                        f"Cannot update card {card_id} dataset_query: no SQL text available "
                        "(neither `query` provided nor existing SQL found on the card)."
                    )
    
                new_tags_arg: dict[str, Any] | None = None
                if template_tags is not None:
                    new_tags_arg = {}
                    for tag_name, cfg in template_tags.items():
                        entry = dict(cfg)
                        entry.setdefault("name", tag_name)
                        new_tags_arg[tag_name] = entry
    
                new_dq = _merge_native_stage(
                    current_dq, sql=new_sql, template_tags=new_tags_arg
                )
                if database_id is not None:
                    new_dq["database"] = database_id
                elif "database" not in new_dq:
                    new_dq["database"] = current_card.get("database_id")
                new_dq.setdefault("type", "native")
    
                payload["dataset_query"] = new_dq
    
            if not payload:
                raise ToolError("No update fields provided. Specify at least one field to update.")
    
            result = await metabase_client.request("PUT", f"/card/{card_id}", json=payload)
            await ctx.info(f"Successfully updated card {card_id}")
    
            return result
        except ToolError:
            raise
        except Exception as e:
            error_msg = f"Error updating card {card_id}: {e}"
            await ctx.error(error_msg)
            raise ToolError(error_msg) from e
  • server.py:597-597 (registration)
    The @mcp.tool decorator on the async function registers 'update_card' as a tool with the FastMCP server.
    @mcp.tool
  • The template_tags parameter type: dict[str, dict[str, Any]] defines the schema for optional template-tag replacement.
    ) -> dict[str, Any]:
  • Helper function _read_native_stage used by update_card to extract current SQL and template-tags from a card's dataset_query (handling both stages and legacy native shapes).
    def _read_native_stage(
        dataset_query: dict[str, Any],
    ) -> tuple[str | None, dict[str, Any]]:
        """
        Return (sql, template_tags) from a card's dataset_query.
    
        Handles both Metabase shapes:
          - stages shape: dataset_query.stages[0] = {"native": "<SQL>", "template-tags": {...}, ...}
          - legacy shape: dataset_query.native = {"query": "<SQL>", "template-tags": {...}}
        """
        stages = dataset_query.get("stages")
        if isinstance(stages, list) and stages:
            stage = stages[0] or {}
            return stage.get("native"), dict(stage.get("template-tags") or {})
        native = dataset_query.get("native") or {}
        return native.get("query"), dict(native.get("template-tags") or {})
  • Helper function _merge_native_stage used by update_card to merge new SQL and/or template-tags into a dataset_query while preserving sibling fields.
    def _merge_native_stage(
        dataset_query: dict[str, Any],
        *,
        sql: str | None = None,
        template_tags: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """
        Return a shallow-copy of `dataset_query` with `sql` and/or `template-tags`
        applied to the native stage, preserving all sibling fields (native/stages,
        lib/type, collection, etc.). Leaves root-level keys like database/type
        untouched — callers override those explicitly if needed.
    
        Pass only the fields you want to change: unset args keep their current values.
        """
        new_dq = dict(dataset_query)
    
        stages = dataset_query.get("stages")
        if isinstance(stages, list) and stages:
            new_stages = [dict(s) if isinstance(s, dict) else s for s in stages]
            stage0 = new_stages[0] if isinstance(new_stages[0], dict) else {}
            if sql is not None:
                stage0["native"] = sql
            if template_tags is not None:
                stage0["template-tags"] = template_tags
            new_stages[0] = stage0
            new_dq["stages"] = new_stages
            return new_dq
    
        new_native = dict(dataset_query.get("native") or {})
        if sql is not None:
            new_native["query"] = sql
        if template_tags is not None:
            new_native["template-tags"] = template_tags
        new_dq["native"] = new_native
        return new_dq
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description includes important behavioral details about template tag preservation when updating the query. However, it omits other traits like side effects of archiving or update permissions, and there are no annotations to supplement.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a concise summary, a behavioral note, a clear arg list, and a return statement. Every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers key parameter semantics and critical behavior (template tag preservation). With an existing output schema, return details are sufficient. Missing minor context like partial update semantics or error conditions.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description adds full meaning for all 10 parameters, e.g., 'database_id: New database ID (required if changing query).' This compensates for the schema's lack of parameter documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Update properties of a saved question/card in Metabase,' specifying the verb (update) and resource (saved question/card). It distinguishes from sibling tools like create_card or list_cards.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description does not provide explicit guidance on when to use this tool versus alternatives such as update_card_display or set_card_template_tags. It lacks context for when-not-to-use or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/voducdan/matebase-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server