This commit is contained in:
@@ -13,6 +13,11 @@ from typing_extensions import NotRequired, TypedDict
|
||||
|
||||
from .. import _debug
|
||||
from ..exceptions import AgentsException, ModelBehaviorError, UserError
|
||||
|
||||
try:
|
||||
from mcp.shared.exceptions import McpError as _McpError
|
||||
except ImportError: # pragma: no cover – mcp is optional on Python < 3.10
|
||||
_McpError = None # type: ignore[assignment, misc]
|
||||
from ..logger import logger
|
||||
from ..run_context import RunContextWrapper
|
||||
from ..strict_schema import ensure_strict_json_schema
|
||||
@@ -360,6 +365,19 @@ class MCPUtil:
|
||||
# Re-raise UserError as-is (it already has a good message)
|
||||
raise
|
||||
except Exception as e:
|
||||
if _McpError is not None and isinstance(e, _McpError):
|
||||
# An MCP-level error (e.g. upstream HTTP 4xx/5xx, tool not found, etc.)
|
||||
# is not a programming error – re-raise so the FunctionTool failure
|
||||
# pipeline (failure_error_function) can handle it. The default handler
|
||||
# will surface the message as a structured error result; callers who set
|
||||
# failure_error_function=None will have the error raised as documented.
|
||||
error_text = e.error.message if hasattr(e, "error") and e.error else str(e)
|
||||
logger.warning(
|
||||
f"MCP tool {tool.name} on server '{server.name}' returned an error: "
|
||||
f"{error_text}"
|
||||
)
|
||||
raise
|
||||
|
||||
logger.error(f"Error invoking MCP tool {tool.name} on server '{server.name}': {e}")
|
||||
raise AgentsException(
|
||||
f"Error invoking MCP tool {tool.name} on server '{server.name}': {e}"
|
||||
|
||||
@@ -192,6 +192,60 @@ async def test_mcp_invocation_crash_causes_error(caplog: pytest.LogCaptureFixtur
|
||||
assert "Error invoking MCP tool test_tool_1" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_invocation_mcp_error_reraises(caplog: pytest.LogCaptureFixture):
|
||||
"""Test that McpError from server.call_tool is re-raised so the FunctionTool failure
|
||||
pipeline (failure_error_function) can handle it.
|
||||
|
||||
When an MCP server raises McpError (e.g. upstream HTTP 4xx/5xx), invoke_mcp_tool
|
||||
re-raises so the configured failure_error_function shapes the model-visible error.
|
||||
With the default failure_error_function the FunctionTool returns a string error
|
||||
result; with failure_error_function=None the error is propagated to the caller.
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.types import ErrorData
|
||||
|
||||
class McpErrorFakeMCPServer(FakeMCPServer):
|
||||
async def call_tool(
|
||||
self,
|
||||
tool_name: str,
|
||||
arguments: dict[str, Any] | None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
):
|
||||
raise McpError(ErrorData(code=-32000, message="upstream 422 Unprocessable Entity"))
|
||||
|
||||
server = McpErrorFakeMCPServer()
|
||||
server.add_tool("search", {})
|
||||
|
||||
ctx = RunContextWrapper(context=None)
|
||||
tool = MCPTool(name="search", inputSchema={})
|
||||
|
||||
# invoke_mcp_tool itself should re-raise McpError
|
||||
with pytest.raises(McpError):
|
||||
await MCPUtil.invoke_mcp_tool(server, tool, ctx, "{}")
|
||||
|
||||
# Warning (not error) should be logged before re-raising
|
||||
assert "returned an error" in caplog.text
|
||||
|
||||
# Via FunctionTool with default failure_error_function: error becomes a string result
|
||||
mcp_tool = MCPTool(name="search", inputSchema={})
|
||||
agent = Agent(name="test-agent")
|
||||
function_tool = MCPUtil.to_function_tool(
|
||||
mcp_tool, server, convert_schemas_to_strict=False, agent=agent
|
||||
)
|
||||
tool_context = ToolContext(
|
||||
context=None,
|
||||
tool_name="search",
|
||||
tool_call_id="test_call_mcp_error",
|
||||
tool_arguments="{}",
|
||||
)
|
||||
result = await function_tool.on_invoke_tool(tool_context, "{}")
|
||||
assert isinstance(result, str)
|
||||
assert "upstream 422 Unprocessable Entity" in result or "error" in result.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_tool_graceful_error_handling(caplog: pytest.LogCaptureFixture):
|
||||
"""Test that MCP tool errors are handled gracefully when invoked via FunctionTool.
|
||||
|
||||
Reference in New Issue
Block a user