You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2025/12/09 15:33:24 UTC

(superset) branch master updated: fix(mcp): Use config-based URL for MCP service instead of request auto-detection (#36460)

This is an automated email from the ASF dual-hosted git repository.

villebro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 8d7c83419c fix(mcp): Use config-based URL for MCP service instead of request auto-detection (#36460)
8d7c83419c is described below

commit 8d7c83419cb9be0ecb17d0bbb91a5b44452241ac
Author: Amin Ghadersohi <am...@gmail.com>
AuthorDate: Tue Dec 9 10:33:17 2025 -0500

    fix(mcp): Use config-based URL for MCP service instead of request auto-detection (#36460)
---
 superset/mcp_service/utils/url_utils.py            | 79 ++++++++++------------
 .../explore/tool/test_generate_explore_link.py     | 11 +++
 2 files changed, 46 insertions(+), 44 deletions(-)

diff --git a/superset/mcp_service/utils/url_utils.py b/superset/mcp_service/utils/url_utils.py
index 17f27a5aee..44eee2b6fe 100644
--- a/superset/mcp_service/utils/url_utils.py
+++ b/superset/mcp_service/utils/url_utils.py
@@ -19,8 +19,25 @@
 URL utilities for MCP service
 """
 
+import logging
+from urllib.parse import urlparse
+
 from flask import current_app
 
+logger = logging.getLogger(__name__)
+
+# Hostnames that indicate a development/local environment
+LOCAL_HOSTNAMES = {"localhost", "127.0.0.1", "0.0.0.0"}  # noqa: S104
+
+
+def _is_local_url(url: str) -> bool:
+    """Check if a URL points to a local/development host."""
+    try:
+        parsed = urlparse(url)
+        return parsed.hostname in LOCAL_HOSTNAMES if parsed.hostname else True
+    except Exception:
+        return True
+
 
 def get_superset_base_url() -> str:
     """
@@ -29,33 +46,14 @@ def get_superset_base_url() -> str:
     Returns:
         Base URL for Superset web server (e.g., "http://localhost:9001")
     """
-    # Default fallback to localhost:9001
     default_url = "http://localhost:9001"
 
     try:
-        # Try to get from configuration
         config = current_app.config
-
-        # Check for SUPERSET_WEBSERVER_ADDRESS first
-        webserver_address = config.get("SUPERSET_WEBSERVER_ADDRESS")
-        if webserver_address:
-            return webserver_address
-
-        # Fallback to other potential config keys
-        public_role_like_gamma = config.get("PUBLIC_ROLE_LIKE_GAMMA", False)
-        if public_role_like_gamma:
-            # If public access is enabled, might be on a different host
-            webserver_protocol = config.get("ENABLE_PROXY_FIX", False)
-            protocol = "https" if webserver_protocol else "http"
-            host = config.get("WEBSERVER_HOST", "localhost")
-            port = config.get("WEBSERVER_PORT", 9001)
-            return f"{protocol}://{host}:{port}"
-
+        if user_friendly_url := config["WEBDRIVER_BASEURL_USER_FRIENDLY"]:
+            return user_friendly_url.rstrip("/")
         return default_url
-
     except Exception:
-        # If we can't access Flask config (e.g., outside app context),
-        # return default
         return default_url
 
 
@@ -63,40 +61,33 @@ def get_mcp_service_url() -> str:
     """
     Get the MCP service base URL where screenshot endpoints are served.
 
-    The MCP service auto-detects its own host and port since it's running
-    this code. Falls back to explicit configuration or default port.
+    In production, the MCP service is typically accessed via the main
+    Superset URL with /mcp prefix. In development,
+    it's accessed directly on port 5008.
 
     Returns:
-        Base URL for MCP service (always independent of Superset URL)
+        Base URL for MCP service endpoints
     """
     try:
-        # Try to auto-detect from Flask request context
-        from flask import request
-
-        if request:
-            # Get the host and port from the current request
-            scheme = request.scheme  # http or https
-            host = request.host  # includes port if non-standard
-            return f"{scheme}://{host}"
-
-    except (RuntimeError, AttributeError):
-        # Not in request context or Flask not available
-        pass
-
-    try:
-        # Check for explicit MCP_SERVICE_URL in config
         config = current_app.config
+
+        # Check for explicit MCP_SERVICE_URL first (allows override)
         mcp_service_url = config.get("MCP_SERVICE_URL")
         if mcp_service_url:
             return mcp_service_url
 
-    except Exception as e:
-        # Log and fall back if config access fails
-        import logging
+        # In production, MCP service is accessed via main URL with /mcp prefix
+        # WEBDRIVER_BASEURL_USER_FRIENDLY is the user-facing URL for the instance
+        if (
+            user_friendly_url := config["WEBDRIVER_BASEURL_USER_FRIENDLY"]
+        ) and not _is_local_url(user_friendly_url):
+            base_url = user_friendly_url.rstrip("/")
+            return f"{base_url}/mcp"
 
-        logging.getLogger(__name__).debug("Config access failed: %s", e)
+    except Exception as e:
+        logger.debug("Config access failed: %s", e)
 
-    # Always fallback to MCP service default port (never use Superset URL)
+    # Development fallback - direct access to MCP service on port 5008
     return "http://localhost:5008"
 
 
diff --git a/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py b/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py
index 56588666db..9e209050ee 100644
--- a/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py
+++ b/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py
@@ -56,6 +56,17 @@ def mock_auth():
         yield mock_get_user
 
 
+@pytest.fixture(autouse=True)
+def mock_webdriver_baseurl(app_context):
+    """Mock WEBDRIVER_BASEURL_USER_FRIENDLY for consistent test URLs."""
+    from flask import current_app
+
+    original_value = current_app.config.get("WEBDRIVER_BASEURL_USER_FRIENDLY")
+    current_app.config["WEBDRIVER_BASEURL_USER_FRIENDLY"] = "http://localhost:9001/"
+    yield
+    current_app.config["WEBDRIVER_BASEURL_USER_FRIENDLY"] = original_value
+
+
 def _mock_dataset(id: int = 1) -> Mock:
     """Create a mock dataset object."""
     dataset = Mock()