eudaimos opened a new issue, #42578:
URL: https://github.com/apache/superset/issues/42578

   ### Bug
   
   `GlobalErrorHandlerMiddleware` in `superset/mcp_service/middleware.py` 
imports pydantic's `ValidationError`:
   
   ```python
   from pydantic import ValidationError  # line 29
   ```
   
   and dispatches on it:
   
   ```python
   elif isinstance(error, ValidationError):
       # Pydantic validation errors
       validation_details = []
       for err in error.errors():
           field = " -> ".join(str(loc) for loc in err["loc"])
           validation_details.append(f"{field}: {err['msg']}")
       raise ToolError(
           f"Validation error in {tool_name}: {'; '.join(validation_details)}"
       ) from error
   ```
   
   But FastMCP 3.x raises its **own** `fastmcp.exceptions.ValidationError` when 
tool arguments fail to validate — it is not a subclass of pydantic's, and not a 
subclass of `ToolError`:
   
   ```
   fastmcp.ValidationError MRO: ['ValidationError', 'FastMCPError', 
'Exception', 'BaseException', 'object']
   is subclass of ToolError: False
   is subclass of pydantic.ValidationError: False
   ```
   
   So the `isinstance` check never matches. Every malformed-arguments call 
falls through to the generic branch:
   
   ```python
   else:
       # Generic internal errors
       error_id = f"err_{int(time.time())}"
       logger.error("Unexpected error [%s] in %s: %s", error_id, tool_name, 
error)
       raise ToolError(
           f"Internal error in {tool_name}: An unexpected error occurred. "
           f"Error ID: {error_id}. Please contact support if this persists."
       ) from error
   ```
   
   A client-side 400-class error is reported to the client as an opaque 
internal server error.
   
   ### Reproduction
   
   Confirmed in-process against a running MCP service (Superset 6.1.0, fastmcp 
3.4.3, pydantic 2.11.7). Calling `execute_sql` with arguments that omit the 
required `request` wrapper:
   
   ```python
   t = await mcp.get_tool('execute_sql')
   try:
       await t.run({'database_id': '1', 'sql': 'SHOW TABLES'})
   except Exception as e:
       print(type(e).__module__ + '.' + type(e).__name__)
       print(isinstance(e, PydanticValidationError))
       print(isinstance(e, FastMCPValidationError))
   ```
   
   Output:
   
   ```
   fastmcp.exceptions.ValidationError
   False
   True
   ```
   
   Resulting server log — note `error_type=ValidationError` is logged, then the 
*same* `_handle_error` invocation falls through to the generic branch:
   
   ```
   ERROR:superset.mcp_service.middleware:MCP tool error: tool=execute_sql, 
user_id=17, duration_ms=2, error_type=ValidationError, error=3 validation 
errors for call[execute_sql]
   request
     Missing required argument [type=missing_argument, 
input_value={'database_id': '1', 'sql': 'SHOW TABLES'}, input_type=dict]
   ERROR:superset.mcp_service.middleware:Unexpected error [err_1785349449] in 
execute_sql: 3 validation errors for call[execute_sql]
   ```
   
   What the client receives:
   
   ```
   Internal error in execute_sql: An unexpected error occurred. Error ID: 
err_1785349449. Please contact support if this persists.
   ```
   
   ### Impact
   
   This is actively harmful with LLM clients, which are the primary consumers 
of this service. In our case a user's agent made three malformed calls 
(`execute_sql`, `list_datasets`, `list_dashboards`), received three "Internal 
error… contact support" responses, observed that `health_check` still passed 
(it is the only tool taking no arguments), and concluded:
   
   > Health check passes, but every data endpoint still returns internal server 
errors — it looks like a backend outage on the Superset MCP's data layer, not 
something I can work around from here.
   
   It then stopped retrying and the user escalated it as an outage. The service 
was entirely healthy.
   
   The `ValidationError` branch that already exists would have returned:
   
   ```
   Validation error in execute_sql: request: Missing required argument
   ```
   
   which is self-correcting — an LLM client reading that will retry with the 
wrapper. Correct error classification is the difference between a client 
recovering on its own and a false outage report.
   
   ### Suggested fix
   
   Match both exception types:
   
   ```python
   from fastmcp.exceptions import ToolError, ValidationError as 
FastMCPValidationError
   from pydantic import ValidationError as PydanticValidationError
   
   ...
   
   elif isinstance(error, (PydanticValidationError, FastMCPValidationError)):
   ```
   
   `error.errors()` only exists on the pydantic exception, so the 
detail-formatting loop needs to be guarded — 
`fastmcp.exceptions.ValidationError` carries its message in `str(error)`, which 
already contains the per-field pydantic detail (as visible in the log above).
   
   Since `fastmcp.exceptions.ValidationError` derives from `FastMCPError`, it 
may be worth handling `FastMCPError` generally so future FastMCP exception 
types don't silently land in the internal-error branch too.
   
   ### Environment
   
   - Superset 6.1.0 (`apache/superset:6.1.0`)
   - fastmcp 3.4.3
   - pydantic 2.11.7
   - Python 3.10.20, Linux
   - Transport: streamable-http
   


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