update_model
Update an existing Metabase model by modifying its name, description, SQL query, database, collection, column metadata, visualization settings, or archive status.
Instructions
Update an existing model in Metabase.
Args: card_id: The ID of the model to update. name: New name for the model. description: New description for the model. query: New SQL query for the model. database_id: New database ID (required if changing query). collection_id: Move the model to a different collection. result_metadata: Updated column metadata list. Each dict can include keys like "name", "display_name", "base_type", "semantic_type", "description", and "field_ref". visualization_settings: Visualization settings to apply. archived: Set to true to archive the model, false to unarchive.
Returns: The updated model object.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| card_id | Yes | ||
| name | No | ||
| description | No | ||
| query | No | ||
| database_id | No | ||
| collection_id | No | ||
| result_metadata | No | ||
| visualization_settings | No | ||
| archived | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- server.py:766-840 (handler)The update_model tool handler function. It is an MCP tool decorated with @mcp.tool that updates an existing model (card with type 'model') in Metabase. It accepts card_id, name, description, query, database_id, collection_id, result_metadata, visualization_settings, and archived parameters. It builds a payload with only the provided fields and sends a PUT request to /card/{card_id}. When query is updated, it either uses the provided database_id or fetches the current card to get the existing database_id.
@mcp.tool async def update_model( card_id: int, ctx: Context, name: str | None = None, description: str | None = None, query: str | None = None, database_id: int | None = None, collection_id: int | None = None, result_metadata: list[dict[str, Any]] | None = None, visualization_settings: dict[str, Any] | None = None, archived: bool | None = None, ) -> dict[str, Any]: """ Update an existing model in Metabase. Args: card_id: The ID of the model to update. name: New name for the model. description: New description for the model. query: New SQL query for the model. database_id: New database ID (required if changing query). collection_id: Move the model to a different collection. result_metadata: Updated column metadata list. Each dict can include keys like "name", "display_name", "base_type", "semantic_type", "description", and "field_ref". visualization_settings: Visualization settings to apply. archived: Set to true to archive the model, false to unarchive. Returns: The updated model object. """ try: await ctx.info(f"Updating model {card_id}") payload: dict[str, Any] = {} if name is not None: payload["name"] = name if description is not None: payload["description"] = description if collection_id is not None: payload["collection_id"] = collection_id if result_metadata is not None: payload["result_metadata"] = result_metadata await ctx.debug(f"Updating {len(result_metadata)} column metadata entries") if visualization_settings is not None: payload["visualization_settings"] = visualization_settings if archived is not None: payload["archived"] = archived if query is not None: db = database_id if db is None: current_card = await metabase_client.request("GET", f"/card/{card_id}") db = current_card.get("database_id") await ctx.debug(f"Using existing database_id {db} for query update") payload["dataset_query"] = { "database": db, "type": "native", "native": {"query": query}, } 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 model {card_id}") return result except ToolError: raise except Exception as e: error_msg = f"Error updating model {card_id}: {e}" await ctx.error(error_msg) raise ToolError(error_msg) from e - server.py:766-767 (registration)The tool is registered via the @mcp.tool decorator on line 766, which registers 'update_model' with the FastMCP server instance named 'mcp'.
@mcp.tool async def update_model( - server.py:767-797 (schema)The function signature and docstring define the input schema for the update_model tool: card_id (int, required), ctx (Context), and optional parameters name, description, query, database_id, collection_id, result_metadata, visualization_settings, archived. The docstring describes each parameter's purpose.
async def update_model( card_id: int, ctx: Context, name: str | None = None, description: str | None = None, query: str | None = None, database_id: int | None = None, collection_id: int | None = None, result_metadata: list[dict[str, Any]] | None = None, visualization_settings: dict[str, Any] | None = None, archived: bool | None = None, ) -> dict[str, Any]: """ Update an existing model in Metabase. Args: card_id: The ID of the model to update. name: New name for the model. description: New description for the model. query: New SQL query for the model. database_id: New database ID (required if changing query). collection_id: Move the model to a different collection. result_metadata: Updated column metadata list. Each dict can include keys like "name", "display_name", "base_type", "semantic_type", "description", and "field_ref". visualization_settings: Visualization settings to apply. archived: Set to true to archive the model, false to unarchive. Returns: The updated model object. """ - server.py:281-315 (helper)The _merge_native_stage helper function is used by update_card but not by update_model. update_model does not use template-tag preservation logic like update_card does; instead it simply replaces the entire dataset_query when a query is provided.
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 - server.py:263-278 (helper)The _read_native_stage helper function is used by update_card but not directly by update_model.
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 {})