codeant-ai-for-open-source[bot] commented on code in PR #37023:
URL: https://github.com/apache/superset/pull/37023#discussion_r2677790864


##########
superset/mcp_service/__main__.py:
##########
@@ -123,7 +123,7 @@ def main() -> None:
         if transport == "streamable-http":
             host = os.environ.get("FASTMCP_HOST", "127.0.0.1")
             port = int(os.environ.get("FASTMCP_PORT", "5008"))

Review Comment:
   **Suggestion:** Parsing `FASTMCP_PORT` with `int(os.environ.get(...))` will 
raise a ValueError if the environment variable is set to a non-integer string; 
validate or fallback safely when conversion fails. [type error]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
               port_env = os.environ.get("FASTMCP_PORT", "5008")
               try:
                   port = int(port_env)
               except (TypeError, ValueError):
                   # Fallback to default port when env var is invalid
                   port = 5008
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   Calling int() directly on an environment value can raise ValueError for 
malformed input; the suggested try/except fallback makes startup more robust 
and avoids crashing the process due to a bad env var.
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/__main__.py
   **Line:** 125:125
   **Comment:**
        *Type Error: Parsing `FASTMCP_PORT` with `int(os.environ.get(...))` 
will raise a ValueError if the environment variable is set to a non-integer 
string; validate or fallback safely when conversion fails.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
tests/unit_tests/mcp_service/test_mcp_tool_registration.py:
##########
@@ -49,3 +49,120 @@ def test_tool_import_works():
     # Should return a decorator function
     decorator = tool(name="test", description="test")
     assert callable(decorator)
+
+
+def test_prompt_import_works():
+    """Test that prompt can be imported from superset_core.mcp after
+    initialization."""
+    from superset_core.mcp import prompt
+
+    # Should be callable
+    assert callable(prompt)
+
+    # Should return a decorator function
+    decorator = prompt(name="test", description="test")
+    assert callable(decorator)
+
+
+def _find_mcp_creation_line(tree):
+    """Find line number of mcp = create_mcp_app() assignment."""
+    import ast
+
+    for node in tree.body:
+        if not isinstance(node, ast.Assign):
+            continue
+        for target in node.targets:
+            if not isinstance(target, ast.Name) or target.id != "mcp":
+                continue
+            if not isinstance(node.value, ast.Call):
+                continue
+            if (
+                hasattr(node.value.func, "id")
+                and node.value.func.id == "create_mcp_app"
+            ):
+                return node.lineno
+    return None
+
+
+def _find_injection_call_line(tree):
+    """Find line number of initialize_core_mcp_dependencies() call."""
+    import ast
+
+    for node in tree.body:
+        if not isinstance(node, ast.Expr):
+            continue
+        if not isinstance(node.value, ast.Call):
+            continue
+        func = node.value.func
+        if hasattr(func, "id") and func.id == 
"initialize_core_mcp_dependencies":

Review Comment:
   **Suggestion:** The helper `_find_injection_call_line` only checks for 
`ast.Call.func` having an `.id` (i.e., a bare name), so it will fail to detect 
calls like `module.initialize_core_mcp_dependencies()` where `func` is an 
`ast.Attribute`; update to handle both `ast.Name` and `ast.Attribute` so the 
initialize call is reliably detected. [logic error]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
           func_name = None
           if isinstance(func, ast.Name):
               func_name = func.id
           elif isinstance(func, ast.Attribute):
               func_name = func.attr
           if func_name == "initialize_core_mcp_dependencies":
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   Same problem as the previous helper: it only checks for ast.Name via .id and 
will miss module.initialize_core_mcp_dependencies() calls. The improved code 
correctly normalizes ast.Name and ast.Attribute to a func_name before 
comparison, resolving a real false-negative detection scenario for the test.
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/mcp_service/test_mcp_tool_registration.py
   **Line:** 97:97
   **Comment:**
        *Logic Error: The helper `_find_injection_call_line` only checks for 
`ast.Call.func` having an `.id` (i.e., a bare name), so it will fail to detect 
calls like `module.initialize_core_mcp_dependencies()` where `func` is an 
`ast.Attribute`; update to handle both `ast.Name` and `ast.Attribute` so the 
initialize call is reliably detected.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
tests/unit_tests/mcp_service/system/tool/test_get_schema.py:
##########
@@ -328,6 +328,71 @@ async def test_get_schema_default_sort_values(self, 
mock_filters, mcp_server):
             assert info["default_sort_direction"] == "desc"
 
 
+class TestGetSchemaLazyImports:
+    """Test lazy imports for DAOs to avoid circular dependencies."""
+
+    def test_no_module_level_dao_imports(self):
+        """Verify get_schema.py has no module-level DAO imports.
+
+        This is critical because module-level DAO imports trigger SQLAlchemy 
model
+        loading, which requires Flask app to be initialized (for encrypted 
fields).
+        DAOs must be imported inside functions (lazy imports) to avoid this 
issue.
+        """
+        import ast
+
+        with open("superset/mcp_service/system/tool/get_schema.py", "r") as f:
+            source = f.read()
+
+        tree = ast.parse(source)
+
+        # Check only top-level imports (direct children of Module)
+        dao_imports = []
+        for node in tree.body:
+            if isinstance(node, ast.ImportFrom):
+                module_name = node.module or ""
+                if "dao" in module_name.lower():
+                    dao_imports.append(module_name)

Review Comment:
   **Suggestion:** Logic bug: the module-level import check only inspects 
ImportFrom nodes and misses top-level "import ..." statements (ast.Import). 
Module-level imports like "import superset.daos.chart" will not be detected, 
producing false negatives; update the check to also inspect ast.Import nodes 
and alias names. [logic error]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
               # Handle "from x import y" forms and check both module and alias 
names
               if isinstance(node, ast.ImportFrom):
                   module_name = node.module or ""
                   if "dao" in module_name.lower() or any("dao" in 
alias.name.lower() for alias in node.names):
                       dao_imports.append(module_name or ", ".join(alias.name 
for alias in node.names))
               # Handle plain "import x.y" forms
               elif isinstance(node, ast.Import):
                   for alias in node.names:
                       if "dao" in alias.name.lower():
                           dao_imports.append(alias.name)
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This suggestion correctly identifies a real gap: the test only inspects 
ast.ImportFrom nodes and will miss top-level "import x.y" statements that could 
reference DAOs. The proposed improved_code handles ast.Import and checks alias 
names, fixing false negatives. It's a targeted test improvement that resolves 
an actual detection bug.
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/mcp_service/system/tool/test_get_schema.py
   **Line:** 351:354
   **Comment:**
        *Logic Error: Logic bug: the module-level import check only inspects 
ImportFrom nodes and misses top-level "import ..." statements (ast.Import). 
Module-level imports like "import superset.daos.chart" will not be detected, 
producing false negatives; update the check to also inspect ast.Import nodes 
and alias names.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
tests/unit_tests/mcp_service/test_mcp_tool_registration.py:
##########
@@ -49,3 +49,120 @@ def test_tool_import_works():
     # Should return a decorator function
     decorator = tool(name="test", description="test")
     assert callable(decorator)
+
+
+def test_prompt_import_works():
+    """Test that prompt can be imported from superset_core.mcp after
+    initialization."""
+    from superset_core.mcp import prompt
+
+    # Should be callable
+    assert callable(prompt)
+
+    # Should return a decorator function
+    decorator = prompt(name="test", description="test")
+    assert callable(decorator)
+
+
+def _find_mcp_creation_line(tree):
+    """Find line number of mcp = create_mcp_app() assignment."""
+    import ast
+
+    for node in tree.body:
+        if not isinstance(node, ast.Assign):
+            continue
+        for target in node.targets:
+            if not isinstance(target, ast.Name) or target.id != "mcp":
+                continue
+            if not isinstance(node.value, ast.Call):
+                continue
+            if (
+                hasattr(node.value.func, "id")
+                and node.value.func.id == "create_mcp_app"
+            ):

Review Comment:
   **Suggestion:** The helper `_find_mcp_creation_line` only checks for a 
function name when the call's `func` is an `ast.Name` (has `.id`), but it 
ignores `ast.Attribute` (e.g., `module.create_mcp_app()`), so it will miss 
valid calls and return None incorrectly; update the function to handle both 
`ast.Name` and `ast.Attribute` by extracting the callable name in both cases. 
[logic error]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
               func = node.value.func
               func_name = None
               if isinstance(func, ast.Name):
                   func_name = func.id
               elif isinstance(func, ast.Attribute):
                   func_name = func.attr
               if func_name == "create_mcp_app":
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The existing helper only detects plain Name calls (create_mcp_app(...)) and 
will miss attribute calls like module.create_mcp_app(...). The proposed change 
extracts the callable name for both ast.Name and ast.Attribute, which fixes a 
real detection gap in the AST traversal and is syntactically correct and 
appropriate for this test helper.
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/mcp_service/test_mcp_tool_registration.py
   **Line:** 79:82
   **Comment:**
        *Logic Error: The helper `_find_mcp_creation_line` only checks for a 
function name when the call's `func` is an `ast.Name` (has `.id`), but it 
ignores `ast.Attribute` (e.g., `module.create_mcp_app()`), so it will miss 
valid calls and return None incorrectly; update the function to handle both 
`ast.Name` and `ast.Attribute` by extracting the callable name in both cases.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
superset/mcp_service/app.py:
##########
@@ -275,7 +275,17 @@ def create_mcp_app(
 
 
 # Create default MCP instance for backward compatibility
-mcp = create_mcp_app(stateless_http=True)
+# Note: stateless_http should be provided when calling run(), not at creation 
time
+mcp = create_mcp_app()
+
+# Initialize MCP dependency injection BEFORE importing tools/prompts
+# This replaces the abstract @tool and @prompt decorators in superset_core.mcp
+# with concrete implementations that can register with the mcp instance
+from superset.core.mcp.core_mcp_injection import (  # noqa: E402
+    initialize_core_mcp_dependencies,
+)
+
+initialize_core_mcp_dependencies()

Review Comment:
   **Suggestion:** Importing and executing `initialize_core_mcp_dependencies()` 
at module import time can raise exceptions (it re-raises non-ImportError 
exceptions) and break imports; guard the import and call, handle ImportError 
separately, and log other failures rather than letting them crash the process. 
[possible bug]
   
   **Severity Level:** Critical 🚨
   ```suggestion
   try:
       from superset.core.mcp.core_mcp_injection import (  # noqa: E402
           initialize_core_mcp_dependencies,
       )
       initialize_core_mcp_dependencies()
   except ImportError as e:
       logger.warning("superset_core not available, skipping MCP injection: 
%s", e)
   except Exception as e:
       logger.error("Failed to initialize MCP dependency injection (non-import 
error): %s", e)
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The imported initializer will re-raise non-ImportError exceptions (see
   core_mcp_injection). Allowing those exceptions to propagate at module
   import time can crash the process. Wrapping the import and call with
   explicit handling for ImportError and logging other failures makes the
   module import more robust and gives clearer diagnostics. This is a
   targeted, sensible hardening change that doesn't silently hide failures
   (it still logs errors).
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/app.py
   **Line:** 284:288
   **Comment:**
        *Possible Bug: Importing and executing 
`initialize_core_mcp_dependencies()` at module import time can raise exceptions 
(it re-raises non-ImportError exceptions) and break imports; guard the import 
and call, handle ImportError separately, and log other failures rather than 
letting them crash the process.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
tests/unit_tests/mcp_service/system/tool/test_get_schema.py:
##########
@@ -328,6 +328,71 @@ async def test_get_schema_default_sort_values(self, 
mock_filters, mcp_server):
             assert info["default_sort_direction"] == "desc"
 
 
+class TestGetSchemaLazyImports:
+    """Test lazy imports for DAOs to avoid circular dependencies."""
+
+    def test_no_module_level_dao_imports(self):
+        """Verify get_schema.py has no module-level DAO imports.
+
+        This is critical because module-level DAO imports trigger SQLAlchemy 
model
+        loading, which requires Flask app to be initialized (for encrypted 
fields).
+        DAOs must be imported inside functions (lazy imports) to avoid this 
issue.
+        """
+        import ast
+
+        with open("superset/mcp_service/system/tool/get_schema.py", "r") as f:
+            source = f.read()
+
+        tree = ast.parse(source)
+
+        # Check only top-level imports (direct children of Module)
+        dao_imports = []
+        for node in tree.body:
+            if isinstance(node, ast.ImportFrom):
+                module_name = node.module or ""
+                if "dao" in module_name.lower():
+                    dao_imports.append(module_name)
+
+        # Should be empty - no module-level DAO imports
+        assert dao_imports == [], (
+            f"Found module-level DAO imports: {dao_imports}. "
+            "DAOs must be imported inside functions to avoid circular 
dependencies."
+        )
+
+    def test_dao_imports_inside_factory_functions(self):
+        """Verify DAOs are imported inside factory functions."""
+        import ast
+
+        with open("superset/mcp_service/system/tool/get_schema.py", "r") as f:
+            source = f.read()
+
+        tree = ast.parse(source)
+
+        # Find DAO imports inside functions
+        function_dao_imports = []
+        for node in ast.walk(tree):
+            if isinstance(node, ast.FunctionDef):

Review Comment:
   **Suggestion:** Logic bug: the code only checks ast.FunctionDef nodes when 
searching for imports inside functions and will miss async function definitions 
(ast.AsyncFunctionDef), causing false negatives if any factory function is 
async; change the type check to accept both FunctionDef and AsyncFunctionDef. 
[logic error]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
               if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The current AST walk only matches ast.FunctionDef and will miss async defs 
(ast.AsyncFunctionDef). Accepting both types is a harmless, correct improvement 
to avoid false negatives if any factory functions are async. The suggested 
change is minimal and fixes a legitimate edge case in the test.
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/mcp_service/system/tool/test_get_schema.py
   **Line:** 374:374
   **Comment:**
        *Logic Error: Logic bug: the code only checks ast.FunctionDef nodes 
when searching for imports inside functions and will miss async function 
definitions (ast.AsyncFunctionDef), causing false negatives if any factory 
function is async; change the type check to accept both FunctionDef and 
AsyncFunctionDef.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
tests/unit_tests/mcp_service/test_mcp_tool_registration.py:
##########
@@ -49,3 +49,120 @@ def test_tool_import_works():
     # Should return a decorator function
     decorator = tool(name="test", description="test")
     assert callable(decorator)
+
+
+def test_prompt_import_works():
+    """Test that prompt can be imported from superset_core.mcp after
+    initialization."""
+    from superset_core.mcp import prompt
+
+    # Should be callable
+    assert callable(prompt)
+
+    # Should return a decorator function
+    decorator = prompt(name="test", description="test")
+    assert callable(decorator)
+
+
+def _find_mcp_creation_line(tree):
+    """Find line number of mcp = create_mcp_app() assignment."""
+    import ast
+
+    for node in tree.body:
+        if not isinstance(node, ast.Assign):
+            continue
+        for target in node.targets:
+            if not isinstance(target, ast.Name) or target.id != "mcp":
+                continue
+            if not isinstance(node.value, ast.Call):
+                continue
+            if (
+                hasattr(node.value.func, "id")
+                and node.value.func.id == "create_mcp_app"
+            ):
+                return node.lineno
+    return None
+
+
+def _find_injection_call_line(tree):
+    """Find line number of initialize_core_mcp_dependencies() call."""
+    import ast
+
+    for node in tree.body:
+        if not isinstance(node, ast.Expr):
+            continue
+        if not isinstance(node.value, ast.Call):
+            continue
+        func = node.value.func
+        if hasattr(func, "id") and func.id == 
"initialize_core_mcp_dependencies":
+            return node.lineno
+    return None
+
+
+def _find_first_tool_import_line(tree):
+    """Find line number of first tool/prompt import."""
+    import ast
+
+    for node in tree.body:
+        if not isinstance(node, ast.ImportFrom):
+            continue
+        if node.module and "superset.mcp_service.chart" in node.module:
+            return node.lineno
+    return None
+
+
+def test_mcp_app_initialization_order():
+    """Test that app.py initializes dependencies before importing tools."""
+    import ast
+
+    with open("superset/mcp_service/app.py", "r") as f:

Review Comment:
   **Suggestion:** The test opens "superset/mcp_service/app.py" by a hardcoded 
relative path which can fail if the test runner's current working directory is 
different; resolve the module path via importlib (finding the module spec 
origin) and open the located file to robustly read the source. [possible bug]
   
   **Severity Level:** Critical 🚨
   ```suggestion
       import importlib.util
       spec = importlib.util.find_spec("superset.mcp_service.app")
       if spec is None or spec.origin is None:
           raise FileNotFoundError("Could not locate superset.mcp_service.app 
source file")
       with open(spec.origin, "r") as f:
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   Relying on a hardcoded relative path can break when pytest's CWD isn't the 
repository root. Using importlib.util.find_spec to locate the module and 
opening spec.origin is more robust and makes the test resilient to different 
invocation contexts. The suggested code is valid and addresses a plausible test 
flakiness scenario.
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/mcp_service/test_mcp_tool_registration.py
   **Line:** 118:118
   **Comment:**
        *Possible Bug: The test opens "superset/mcp_service/app.py" by a 
hardcoded relative path which can fail if the test runner's current working 
directory is different; resolve the module path via importlib (finding the 
module spec origin) and open the located file to robustly read the source.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
superset/mcp_service/server.py:
##########
@@ -118,7 +118,9 @@ def run_server(
         os.environ[env_key] = "1"
         try:
             logging.info("Starting FastMCP on %s:%s", host, port)
-            mcp_instance.run(transport="streamable-http", host=host, port=port)
+            mcp_instance.run(
+                transport="streamable-http", host=host, port=port, 
stateless_http=True
+            )

Review Comment:
   **Suggestion:** The environment flag `FASTMCP_RUNNING_{port}` is only 
cleared in the `except Exception` block, so if `mcp_instance.run` raises a 
BaseException (e.g. KeyboardInterrupt or SystemExit) or otherwise exits 
normally, the env var can remain set and incorrectly block future starts in the 
same process; ensure the env var is always cleared by using a finally block 
that runs for all exit paths. [resource leak]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
               try:
                   mcp_instance.run(
                       transport="streamable-http", host=host, port=port, 
stateless_http=True
                   )
               finally:
                   # Ensure the environment flag is always cleared, even for 
BaseException
                   os.environ.pop(env_key, None)
   ```
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The current except Exception block doesn't catch BaseException 
(KeyboardInterrupt/SystemExit),
   so the env flag can remain set if the process exits via those. Wrapping the 
run call with a
   try/finally and popping the env key in finally ensures the flag is always 
cleared. This fixes a
   real resource/state leak in the current code.
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/server.py
   **Line:** 121:123
   **Comment:**
        *Resource Leak: The environment flag `FASTMCP_RUNNING_{port}` is only 
cleared in the `except Exception` block, so if `mcp_instance.run` raises a 
BaseException (e.g. KeyboardInterrupt or SystemExit) or otherwise exits 
normally, the env var can remain set and incorrectly block future starts in the 
same process; ensure the env var is always cleared by using a finally block 
that runs for all exit paths.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



-- 
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