diff --git a/src/strands/tools/mcp/mcp_client.py b/src/strands/tools/mcp/mcp_client.py index e81dc7130..aaa6d4615 100644 --- a/src/strands/tools/mcp/mcp_client.py +++ b/src/strands/tools/mcp/mcp_client.py @@ -744,6 +744,8 @@ def _handle_tool_result(self, tool_use_id: str, call_tool_result: MCPCallToolRes result["structuredContent"] = call_tool_result.structuredContent if call_tool_result.meta: result["metadata"] = call_tool_result.meta + if call_tool_result.isError is not None: + result["isError"] = call_tool_result.isError return result diff --git a/src/strands/tools/mcp/mcp_types.py b/src/strands/tools/mcp/mcp_types.py index 8fbf573be..09feb624f 100644 --- a/src/strands/tools/mcp/mcp_types.py +++ b/src/strands/tools/mcp/mcp_types.py @@ -61,7 +61,13 @@ class MCPToolResult(ToolResult): metadata: Optional arbitrary metadata returned by the MCP tool. This field allows MCP servers to attach custom metadata to tool results (e.g., token usage, performance metrics, or business-specific tracking information). + isError: Whether the MCP tool reported an application-level error via + ``CallToolResult.isError``. ``True`` means the tool executed but its logic + returned a failure. Absent when the tool succeeded or when the error was a + protocol/client exception rather than a tool-reported failure, letting + callers distinguish application errors from transport/protocol errors. """ structuredContent: NotRequired[dict[str, Any]] metadata: NotRequired[dict[str, Any]] + isError: NotRequired[bool] diff --git a/tests/strands/tools/mcp/test_mcp_client.py b/tests/strands/tools/mcp/test_mcp_client.py index bf0e7ce8e..d40972219 100644 --- a/tests/strands/tools/mcp/test_mcp_client.py +++ b/tests/strands/tools/mcp/test_mcp_client.py @@ -132,6 +132,8 @@ def test_call_tool_sync_status(mock_transport, mock_session, is_error, expected_ assert result["content"][0]["text"] == "Test message" # No structured content should be present when not provided by MCP assert result.get("structuredContent") is None + # isError mirrors the MCP server's explicit value; absent only for protocol/client exceptions + assert result.get("isError") is is_error def test_call_tool_sync_session_not_active(): @@ -261,6 +263,8 @@ async def mock_awaitable(): assert result["toolUseId"] == "test-123" assert len(result["content"]) == 1 assert result["content"][0]["text"] == "Test message" + # isError mirrors the MCP server's explicit value; absent only for protocol/client exceptions + assert result.get("isError") is is_error @pytest.mark.asyncio @@ -408,6 +412,15 @@ def test_mcp_tool_result_type(): assert result_with_structured["structuredContent"] == {"key": "value"} + # isError is optional — absent by default + assert "isError" not in result + + # isError can be set to flag tool-reported application errors + result_with_is_error = MCPToolResult( + status="error", toolUseId="test-789", content=[{"text": "Tool failed"}], isError=True + ) + assert result_with_is_error["isError"] is True + def test_call_tool_sync_without_structured_content(mock_transport, mock_session): """Test that call_tool_sync works correctly when no structured content is provided."""