fix(mcp): prevent encoding error on tools/list when middleware raises (#40446)

This commit is contained in:
Amin Ghadersohi
2026-05-26 09:50:31 -07:00
committed by GitHub
parent 8b551d3f74
commit 952a6f3a23
4 changed files with 38 additions and 2 deletions

View File

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

View File

@@ -31,6 +31,7 @@ PATTERNS = {
r"^superset/",
r"^scripts/",
r"^setup\.py",
r"^pyproject\.toml$",
r"^requirements/.+\.txt",
r"^.pylintrc",
],

View File

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

View File

@@ -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."
)