From 952a6f3a239acd4b9f8fd066131dda2d0887c0fb Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Tue, 26 May 2026 09:50:31 -0700 Subject: [PATCH] fix(mcp): prevent encoding error on tools/list when middleware raises (#40446) --- requirements/development.txt | 2 +- scripts/change_detector.py | 1 + superset/mcp_service/middleware.py | 9 +++++- .../mcp_service/test_middleware_logging.py | 28 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/requirements/development.txt b/requirements/development.txt index 8bb622c6b88..d2ae61417a6 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -219,7 +219,7 @@ docstring-parser==0.17.0 # via cyclopts docutils==0.22.2 # via rich-rst -duckdb==1.4.2 +duckdb==1.5.3 # via # apache-superset # duckdb-engine diff --git a/scripts/change_detector.py b/scripts/change_detector.py index 9a0e1fc8501..8fda027f855 100755 --- a/scripts/change_detector.py +++ b/scripts/change_detector.py @@ -31,6 +31,7 @@ PATTERNS = { r"^superset/", r"^scripts/", r"^setup\.py", + r"^pyproject\.toml$", r"^requirements/.+\.txt", r"^.pylintrc", ], diff --git a/superset/mcp_service/middleware.py b/superset/mcp_service/middleware.py index 632eed5b813..844b5ff59d0 100644 --- a/superset/mcp_service/middleware.py +++ b/superset/mcp_service/middleware.py @@ -388,7 +388,14 @@ class StructuredContentStripperMiddleware(Middleware): context: MiddlewareContext[mt.ListToolsRequest], call_next: CallNext[mt.ListToolsRequest, Sequence[Tool]], ) -> Sequence[Tool]: - tools = await call_next(context) + try: + tools = await call_next(context) + except Exception: + # ToolError raised by inner middleware (e.g. GlobalErrorHandlerMiddleware) + # cannot be encoded by the MCP SDK in a tools/list response — it expects a + # list, not an error object — causing "encoding without a string argument". + # Return an empty list; GlobalErrorHandlerMiddleware already logged it. + return [] return [ t.model_copy(update={"output_schema": None}) if t.output_schema is not None diff --git a/tests/unit_tests/mcp_service/test_middleware_logging.py b/tests/unit_tests/mcp_service/test_middleware_logging.py index 19487810bbf..3f81dc3ae9b 100644 --- a/tests/unit_tests/mcp_service/test_middleware_logging.py +++ b/tests/unit_tests/mcp_service/test_middleware_logging.py @@ -435,3 +435,31 @@ class TestMiddlewareChainOrder: assert result.meta is not None assert "mcp_call_id" in result.meta assert len(result.meta["mcp_call_id"]) == 32 + + @pytest.mark.asyncio + async def test_list_tools_exception_returns_empty_list(self): + """Exception during tools/list returns [] instead of causing encoding error. + + ToolError raised by GlobalErrorHandlerMiddleware cannot be encoded + by the MCP SDK in a tools/list response, producing "encoding without + a string argument". StructuredContentStripperMiddleware.on_list_tools + must catch it and return an empty list. + """ + from superset.mcp_service.server import build_middleware_list + + middleware_list = build_middleware_list() + + async def failing_list_tools(context: Any) -> Any: + raise ValueError("auth failed") + + chain = failing_list_tools + for mw in reversed(middleware_list): + chain = partial(mw, call_next=chain) + + ctx = _make_context(method="tools/list", name="") + result = await chain(ctx) + + assert result == [], ( + "on_list_tools must return [] on exception — " + "ToolError cannot be encoded in a tools/list response." + )