fix(mcp): include exception type in error messages when str(exc) is empty

Some exception classes (e.g. anyio.ClosedResourceError) are raised without
a message argument, so str(exc) returns an empty string. The existing error
format f'{type(exc).__name__}: {exc}' would produce messages like
'MCP call failed: ClosedResourceError: ' with nothing after the colon.

Add _exc_str() helper that falls back to repr(exc) when str(exc) is empty,
and apply it to all 6 MCP error formatting sites (5 tool/prompt/resource
handlers + 1 sampling handler).

Fixes #19417
This commit is contained in:
liuhao1024 2026-05-04 07:56:05 +08:00 committed by Teknium
parent f481395d4c
commit f9b4b8af34
2 changed files with 107 additions and 6 deletions

View file

@ -312,6 +312,18 @@ def _sanitize_error(text: str) -> str:
return _CREDENTIAL_PATTERN.sub("[REDACTED]", text)
def _exc_str(exc: BaseException) -> str:
"""Return a non-empty human-readable string for *exc*.
Some exception classes (e.g. ``anyio.ClosedResourceError``) are raised
without a message argument, so ``str(exc)`` is ``""``. This helper
falls back to ``repr(exc)`` so that error messages shown to the user
and logged to disk always carry *some* diagnostic information.
"""
text = str(exc).strip()
return text if text else repr(exc)
# ---------------------------------------------------------------------------
# MCP tool description content scanning
# ---------------------------------------------------------------------------
@ -831,7 +843,7 @@ class SamplingHandler:
except Exception as exc:
self.metrics["errors"] += 1
return self._error(
f"Sampling LLM call failed: {_sanitize_error(str(exc))}"
f"Sampling LLM call failed: {_sanitize_error(_exc_str(exc))}"
)
# Guard against empty choices (content filtering, provider errors)
@ -2174,7 +2186,7 @@ def _make_tool_handler(server_name: str, tool_name: str, tool_timeout: float):
)
return json.dumps({
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
f"MCP call failed: {type(exc).__name__}: {_exc_str(exc)}"
)
}, ensure_ascii=False)
@ -2232,7 +2244,7 @@ def _make_list_resources_handler(server_name: str, tool_timeout: float):
)
return json.dumps({
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
f"MCP call failed: {type(exc).__name__}: {_exc_str(exc)}"
)
}, ensure_ascii=False)
@ -2292,7 +2304,7 @@ def _make_read_resource_handler(server_name: str, tool_timeout: float):
)
return json.dumps({
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
f"MCP call failed: {type(exc).__name__}: {_exc_str(exc)}"
)
}, ensure_ascii=False)
@ -2355,7 +2367,7 @@ def _make_list_prompts_handler(server_name: str, tool_timeout: float):
)
return json.dumps({
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
f"MCP call failed: {type(exc).__name__}: {_exc_str(exc)}"
)
}, ensure_ascii=False)
@ -2426,7 +2438,7 @@ def _make_get_prompt_handler(server_name: str, tool_timeout: float):
)
return json.dumps({
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
f"MCP call failed: {type(exc).__name__}: {_exc_str(exc)}"
)
}, ensure_ascii=False)