Skip to main content
Glama
CW-Codewalnut

Metabase MCP Server

create_metabase_card

Create visual charts and tables in Metabase using SQL or MBQL queries to display data insights through various visualization types.

Instructions

Create a new card (chart or table) in Metabase via the /api/card endpoint.

This function creates a visual card using either SQL or MBQL queries and supports all chart types including pie, donut, bar, table, and KPI-style metrics.

Args: name (str): Display name of the card in Metabase.

dataset_query (dict):
    Defines the query behind the chart.
    Required structure:
    - "type": "native" or "query"
    - "native": { "query": "..." }, for SQL
    - "query": {...}, for MBQL
    - "database": database ID

display (str):
    Visualization type. Common values:
    - "table", "bar", "line", "pie", "area", "scatter", "funnel", "pivot-table", "map"

type (str, optional):
    Card type, defaults to "question".
    - "question": general chart or table
    - "metric": for KPI display
    - "model": reserved/legacy

visualization_settings (dict, optional):
    Controls chart appearance and formatting. Structure varies by chart type.

    ── πŸ“Š Bar / Line / Area ──
    {
      "graph": {
        "x_axis": "destination",
        "y_axis": ["seatsSold"],
        "series": "flightType",
        "metrics": ["seatsSold"],
        "x_axis_label": "Destination",
        "y_axis_label": "Seats Sold",
        "x_axis_formatting": {
          "scale": "ordinal",
          "label_rotation": 45
        },
        "y_axis_formatting": {
          "number_style": "decimal",
          "suffix": " pax"
        }
      },
      "show_legend": true,
      "legend_position": "bottom"
    }

    ── πŸ₯§ Pie / Donut Charts ──
    {
      "pie": {
        "category": "destination",         # Label or group for slices
        "metric": "seatsSold",             # Size of each slice
        "labels": true,                    # Show category names
        "show_values": true,               # Show numeric values inside slices
        "inner_radius": 0.6,               # Enables donut (0 = full pie)
        "outer_radius": 0.95,              # Size scaling (0.0 to 1.0)
        "outer_ring": true                 # Enables dual-ring charts
      },
      "show_legend": true,
      "legend_position": "right"
    }

    Notes on ring options:
      - `inner_radius` creates a donut shape. Recommended: 0.5–0.8.
      - `outer_radius` controls the size of the entire chart area.
      - `outer_ring` enables comparison across rings, useful when the query returns multiple groupings/metrics.

    ── πŸ“‹ Table ──
    {
      "table.pivot_column": "flightType",
      "column_settings": {
        "seatsSold": {
          "number_style": "decimal",
          "suffix": " pax"
        }
      }
    }

collection_id (int, optional):
    Save card into a specific Metabase collection (folder).

description (str, optional):
    Description or help text for the card.

parameter_mappings (list, optional):
    Used when linking dashboard filters to this card.
    Example:
    [
      {
        "parameter_id": "flightType",
        "card_id": 123,
        "target": ["dimension", ["template-tag", "flightType"]]
      }
    ]

collection_position (int, optional):
    Optional order in the collection.

result_metadata (list, optional):
    Optional field metadata describing result set.

cache_ttl (int, optional):
    Cache duration (in seconds). 0 disables caching.

parameters (list, optional):
    List of query parameters for SQL or MBQL filters.
    Example: [{"name": "region", "type": "category", "slug": "region"}]

dashboard_id (int, optional):
    Adds this card to an existing dashboard.

dashboard_tab_id (int, optional):
    If the dashboard has tabs, specify the tab ID to attach the card to.

entity_id (str, optional):
    External or custom ID for embedding/syncing cards.

Returns: Dict[str, Any]: A dictionary representing the created card including: - id (int) - name (str) - dataset_query (dict) - visualization_settings (dict) - created_at, updated_at, etc.

Example: >>> await create_metabase_card( name="Seats Sold by Destination (Donut with Outer Ring)", display="pie", dataset_query={ "type": "native", "native": { "query": "SELECT destination, SUM("seatsSold") AS total_seats_sold FROM "Flight" GROUP BY destination" }, "database": 2 }, visualization_settings={ "pie": { "category": "destination", "metric": "total_seats_sold", "labels": true, "inner_radius": 0.6, "outer_radius": 0.95, "show_values": true, "outer_ring": true }, "show_legend": true, "legend_position": "right" }, collection_id=3 )

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYes
dataset_queryYes
displayYes
typeNoquestion
visualization_settingsNo
collection_idNo
descriptionNo
parameter_mappingsNo
collection_positionNo
result_metadataNo
cache_ttlNo
parametersNo
dashboard_idNo
dashboard_tab_idNo
entity_idNo

Implementation Reference

  • The main handler function for 'create_metabase_card' tool. It creates a new card (chart or table) in Metabase via the /api/card endpoint. Accepts parameters for name, dataset_query, display type, visualization_settings, and other optional card properties. It builds a payload and calls make_metabase_request() to POST to the Metabase API.
    @mcp.tool()
    async def create_metabase_card(
        name: str,
        dataset_query: Dict[str, Any],
        display: str,
        type: str = "question",
        visualization_settings: Optional[Union[Dict[str, Any], str]] = None,
        collection_id: Optional[int] = None,
        description: Optional[str] = None,
        parameter_mappings: Optional[List] = None,
        collection_position: Optional[int] = None,
        result_metadata: Optional[List] = None,
        cache_ttl: Optional[int] = None,
        parameters: Optional[List] = None,
        dashboard_id: Optional[int] = None,
        dashboard_tab_id: Optional[int] = None,
        entity_id: Optional[str] = None
    ) -> Dict[str, Any]:
       
        """
        Create a new card (chart or table) in Metabase via the /api/card endpoint.
    
        This function creates a visual card using either SQL or MBQL queries and 
        supports all chart types including pie, donut, bar, table, and KPI-style metrics.
    
        Args:
            name (str):
                Display name of the card in Metabase.
    
            dataset_query (dict):
                Defines the query behind the chart.
                Required structure:
                - "type": "native" or "query"
                - "native": { "query": "..." }, for SQL
                - "query": {...}, for MBQL
                - "database": database ID
    
            display (str):
                Visualization type. Common values:
                - "table", "bar", "line", "pie", "area", "scatter", "funnel", "pivot-table", "map"
    
            type (str, optional):
                Card type, defaults to "question".
                - "question": general chart or table
                - "metric": for KPI display
                - "model": reserved/legacy
    
            visualization_settings (dict, optional):
                Controls chart appearance and formatting. Structure varies by chart type.
    
                ── πŸ“Š Bar / Line / Area ──
                {
                  "graph": {
                    "x_axis": "destination",
                    "y_axis": ["seatsSold"],
                    "series": "flightType",
                    "metrics": ["seatsSold"],
                    "x_axis_label": "Destination",
                    "y_axis_label": "Seats Sold",
                    "x_axis_formatting": {
                      "scale": "ordinal",
                      "label_rotation": 45
                    },
                    "y_axis_formatting": {
                      "number_style": "decimal",
                      "suffix": " pax"
                    }
                  },
                  "show_legend": true,
                  "legend_position": "bottom"
                }
    
                ── πŸ₯§ Pie / Donut Charts ──
                {
                  "pie": {
                    "category": "destination",         # Label or group for slices
                    "metric": "seatsSold",             # Size of each slice
                    "labels": true,                    # Show category names
                    "show_values": true,               # Show numeric values inside slices
                    "inner_radius": 0.6,               # Enables donut (0 = full pie)
                    "outer_radius": 0.95,              # Size scaling (0.0 to 1.0)
                    "outer_ring": true                 # Enables dual-ring charts
                  },
                  "show_legend": true,
                  "legend_position": "right"
                }
    
                Notes on ring options:
                  - `inner_radius` creates a donut shape. Recommended: 0.5–0.8.
                  - `outer_radius` controls the size of the entire chart area.
                  - `outer_ring` enables comparison across rings, useful when the query returns multiple groupings/metrics.
    
                ── πŸ“‹ Table ──
                {
                  "table.pivot_column": "flightType",
                  "column_settings": {
                    "seatsSold": {
                      "number_style": "decimal",
                      "suffix": " pax"
                    }
                  }
                }
    
            collection_id (int, optional):
                Save card into a specific Metabase collection (folder).
    
            description (str, optional):
                Description or help text for the card.
    
            parameter_mappings (list, optional):
                Used when linking dashboard filters to this card.
                Example:
                [
                  {
                    "parameter_id": "flightType",
                    "card_id": 123,
                    "target": ["dimension", ["template-tag", "flightType"]]
                  }
                ]
    
            collection_position (int, optional):
                Optional order in the collection.
    
            result_metadata (list, optional):
                Optional field metadata describing result set.
    
            cache_ttl (int, optional):
                Cache duration (in seconds). 0 disables caching.
    
            parameters (list, optional):
                List of query parameters for SQL or MBQL filters.
                Example: [{"name": "region", "type": "category", "slug": "region"}]
    
            dashboard_id (int, optional):
                Adds this card to an existing dashboard.
    
            dashboard_tab_id (int, optional):
                If the dashboard has tabs, specify the tab ID to attach the card to.
    
            entity_id (str, optional):
                External or custom ID for embedding/syncing cards.
    
        Returns:
            Dict[str, Any]:
                A dictionary representing the created card including:
                  - id (int)
                  - name (str)
                  - dataset_query (dict)
                  - visualization_settings (dict)
                  - created_at, updated_at, etc.
    
        Example:
            >>> await create_metabase_card(
                    name="Seats Sold by Destination (Donut with Outer Ring)",
                    display="pie",
                    dataset_query={
                        "type": "native",
                        "native": {
                            "query": "SELECT destination, SUM(\"seatsSold\") AS total_seats_sold FROM \"Flight\" GROUP BY destination"
                        },
                        "database": 2
                    },
                    visualization_settings={
                        "pie": {
                            "category": "destination",
                            "metric": "total_seats_sold",
                            "labels": true,
                            "inner_radius": 0.6,
                            "outer_radius": 0.95,
                            "show_values": true,
                            "outer_ring": true
                        },
                        "show_legend": true,
                        "legend_position": "right"
                    },
                    collection_id=3
                )
        """
    
        payload = {
            "name": name,
            "dataset_query": dataset_query,
            "display": display,
            "type": type,
        }
        
        # Ensure visualization_settings is a proper dict and not a string
        if visualization_settings is not None:
            if isinstance(visualization_settings, str):
                try:
                    import json
                    visualization_settings = json.loads(visualization_settings)
                except json.JSONDecodeError:
                    logger.error("Invalid JSON in visualization_settings")
                    raise ValueError("visualization_settings must be a valid JSON object")
            payload["visualization_settings"] = visualization_settings
        else:
            payload["visualization_settings"] = {}
            
        if collection_id is not None:
            payload["collection_id"] = collection_id
        if description is not None:
            payload["description"] = description
        if parameter_mappings is not None:
            payload["parameter_mappings"] = parameter_mappings
        if collection_position is not None:
            payload["collection_position"] = collection_position
        if result_metadata is not None:
            payload["result_metadata"] = result_metadata
        if cache_ttl is not None:
            payload["cache_ttl"] = cache_ttl
        if parameters is not None:
            payload["parameters"] = parameters
        if dashboard_id is not None:
            payload["dashboard_id"] = dashboard_id
        if dashboard_tab_id is not None:
            payload["dashboard_tab_id"] = dashboard_tab_id
        if entity_id is not None:
            payload["entity_id"] = entity_id
            
        logger.info(f"Creating card '{name}'")
        return await make_metabase_request(RequestMethod.POST, "/api/card", json=payload)
  • The @mcp.tool() decorator registers the create_metabase_card function as an MCP tool. The 'mcp' object is a FastMCP instance initialized at line 160.
    @mcp.tool()
  • FastMCP instance initialization: mcp = FastMCP("metabase", lifespan=app_lifespan). This creates the MCP server object that the @mcp.tool() decorator uses to register tools.
    mcp = FastMCP("metabase", lifespan=app_lifespan)
  • Helper function make_metabase_request() used by create_metabase_card to make HTTP requests to the Metabase API. Handles authentication, error handling, and response processing.
    async def make_metabase_request(
        method: RequestMethod,
        endpoint: str,
        data: Optional[Dict[str, Any] | bytes] = None,
        params: Optional[Dict[str, Any]] = None,
        json: Any = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """
        Make a request to the Metabase API.
        
        Args:
            method: HTTP method to use (GET, POST, PUT, DELETE)
            endpoint: API endpoint path
            data: Request data (for form data)
            params: URL parameters
            json: JSON request body
            headers: Additional headers
            
        Returns:
            Dict[str, Any]: Response data
            
        Raises:
            MetabaseConnectionError: When the Metabase server is unreachable
            MetabaseResponseError: When Metabase returns a non-2xx status code
            RuntimeError: For other errors
        """
        
        if not METABASE_URL or not METABASE_API_KEY:
            raise RuntimeError("METABASE_URL or METABASE_API_KEY environment variable is not set. Metabase API requests will fail.")
    
        if session is None:
            raise RuntimeError("HTTP session is not initialized. Ensure app_lifespan was called.")
    
        try:
            request_headers = headers or {}
            
            logger.debug(f"Making {method.name} request to {METABASE_URL}{endpoint}")
            
            # Log request payload for debugging (omit sensitive info)
            if json and logger.level <= logging.DEBUG:
                sanitized_json = {**json}
                if 'password' in sanitized_json:
                    sanitized_json['password'] = '********'
                logger.debug(f"Request payload: {sanitized_json}")
                
            response = await session.request(
                method=method.name,
                url=endpoint,
                timeout=aiohttp.ClientTimeout(total=30),
                headers=request_headers,
                data=data,
                params=params,
                json=json,
            )
    
            try:
                # Handle 500 errors with more detailed info
                if response.status >= 500:
                    error_text = await response.text()
                    logger.error(f"Server error {response.status}: {error_text[:200]}")
                    raise MetabaseResponseError(response.status, f"Server Error: {error_text[:200]}", endpoint)
                
                response.raise_for_status()
                response_data = await response.json()
                
                # Ensure the response is a dictionary for FastMCP compatibility
                return ensure_dict_response(response_data)
                
            except aiohttp.ContentTypeError:
                # Handle empty responses or non-JSON responses
                content = await response.text()
                if not content:
                    return {"data": {}}
                logger.warning(f"Received non-JSON response: {content}")
                return {"data": content}
    
        except aiohttp.ClientConnectionError as e:
            logger.error(f"Connection error: {str(e)}")

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/CW-Codewalnut/metabase-mcp-server'

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