aminghadersohi commented on code in PR #36933:
URL: https://github.com/apache/superset/pull/36933#discussion_r2891590137


##########
superset/config.py:
##########
@@ -559,6 +559,8 @@ class D3TimeFormat(TypedDict, total=False):
     # This feature flag is stil in beta and is not recommended for production 
use.
     "GLOBAL_ASYNC_QUERIES": False,
     "EMBEDDED_SUPERSET": False,
+    # Enables MCP tool for creating embeddable chart iframes with guest tokens
+    "EMBEDDABLE_CHARTS_MCP": False,

Review Comment:
   Fixed in a8b866e. Changed `EMBEDDABLE_CHARTS` to default to `False` so 
operators must explicitly opt in after configuring a strong 
`GUEST_TOKEN_JWT_SECRET`.



##########
superset/mcp_service/embedded_chart/tool/__init__.py:
##########
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from .get_embeddable_chart import get_embeddable_chart
+
+__all__ = ["get_embeddable_chart"]

Review Comment:
   This pattern is the same used by all other tool `__init__.py` files in the 
MCP service (chart/tool/__init__.py, dashboard/tool/__init__.py, etc.). The 
circular import risk doesn't apply here because `get_embeddable_chart` only 
imports from external modules, not back into the tool package.



##########
superset/mcp_service/embedded_chart/tool/get_embeddable_chart.py:
##########
@@ -0,0 +1,228 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+MCP tool: get_embeddable_chart
+
+Creates an embeddable chart iframe with guest token authentication.
+This enables AI agents to generate charts that can be displayed in
+external applications via iframe.
+"""
+
+import logging
+from datetime import datetime, timedelta, timezone
+
+from fastmcp import Context
+from flask import g
+from superset_core.mcp import tool
+
+from superset import is_feature_enabled
+from superset.mcp_service.auth import has_dataset_access
+from superset.mcp_service.embedded_chart.schemas import (
+    GetEmbeddableChartRequest,
+    GetEmbeddableChartResponse,
+)
+from superset.mcp_service.utils.schema_utils import parse_request
+from superset.mcp_service.utils.url_utils import get_superset_base_url
+
+logger = logging.getLogger(__name__)
+
+
+@tool(tags=["core"])
+@parse_request(GetEmbeddableChartRequest)
+async def get_embeddable_chart(
+    request: GetEmbeddableChartRequest,
+    ctx: Context,
+) -> GetEmbeddableChartResponse:
+    """Create an embeddable chart iframe URL with guest token authentication.
+
+    This tool creates an ephemeral chart visualization that can be embedded
+    in external applications via iframe. The chart is configured via form_data
+    and stored as a permalink with TTL.
+
+    IMPORTANT:
+    - Requires EMBEDDABLE_CHARTS_MCP feature flag to be enabled
+    - The iframe_html can be directly embedded in web pages
+    - Guest token must be passed to the iframe for authentication
+    - Chart expires after ttl_minutes (default: 60 minutes)
+
+    Example usage:
+    ```json
+    {
+        "datasource_id": 123,
+        "viz_type": "echarts_timeseries_line",
+        "form_data": {
+            "metrics": ["count"],
+            "groupby": ["category"],
+            "time_range": "Last 7 days"
+        },
+        "ttl_minutes": 120,
+        "height": 500
+    }
+    ```
+
+    Returns iframe_url, guest_token, and ready-to-use iframe_html snippet.
+    """
+    await ctx.info(
+        f"Creating embeddable chart: datasource_id={request.datasource_id}, "
+        f"viz_type={request.viz_type}"
+    )
+
+    # Check feature flag
+    if not is_feature_enabled("EMBEDDABLE_CHARTS_MCP"):
+        await ctx.error("EMBEDDABLE_CHARTS_MCP feature flag is not enabled")
+        return GetEmbeddableChartResponse(
+            success=False,
+            error="EMBEDDABLE_CHARTS_MCP feature flag is not enabled",
+        )
+
+    try:
+        # Import here to avoid circular imports
+        from superset.commands.explore.permalink.create import (
+            CreateExplorePermalinkCommand,
+        )
+        from superset.daos.dataset import DatasetDAO
+        from superset.extensions import security_manager
+        from superset.security.guest_token import GuestTokenResourceType
+
+        # Resolve dataset
+        dataset = None
+        if isinstance(request.datasource_id, int) or (
+            isinstance(request.datasource_id, str) and 
request.datasource_id.isdigit()
+        ):
+            dataset_id = (
+                int(request.datasource_id)
+                if isinstance(request.datasource_id, str)
+                else request.datasource_id
+            )
+            dataset = DatasetDAO.find_by_id(dataset_id)
+        else:
+            # Try UUID lookup
+            dataset = DatasetDAO.find_by_id(request.datasource_id, 
id_column="uuid")
+
+        if not dataset:
+            await ctx.error(f"Dataset not found: {request.datasource_id}")
+            return GetEmbeddableChartResponse(
+                success=False,
+                error=f"Dataset not found: {request.datasource_id}",
+            )
+
+        # Check access
+        if not has_dataset_access(dataset):
+            await ctx.error("Access denied to dataset")
+            return GetEmbeddableChartResponse(
+                success=False,
+                error="Access denied to dataset",
+            )
+
+        # Build complete form_data
+        form_data = {
+            **request.form_data,
+            "viz_type": request.viz_type,
+            "datasource": f"{dataset.id}__table",
+        }
+
+        # Apply overrides if provided
+        if request.form_data_overrides:
+            form_data.update(request.form_data_overrides)
+
+        # Create permalink
+        state = {"formData": form_data}
+        permalink_key = CreateExplorePermalinkCommand(state).run()
+
+        await ctx.debug(f"Created permalink: {permalink_key}")
+
+        # Calculate expiration
+        expires_at = datetime.now(timezone.utc) + 
timedelta(minutes=request.ttl_minutes)

Review Comment:
   Fixed in a8b866e. Added a safe default: `ttl = request.ttl_minutes if 
request.ttl_minutes else 60`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to