@@ -905,3 +905,74 @@ async def test_stderr_reader_exception_handling():
905905 # Give it time to process stderr
906906 await anyio .sleep (0.3 )
907907 # Should not crash, just log the error
908+
909+
910+ @pytest .mark .anyio
911+ async def test_stderr_reader_final_buffer_exception ():
912+ """Test stderr reader handles exception in final buffer flush."""
913+ # Write stderr without trailing newline to trigger final buffer path
914+ script_content = textwrap .dedent (
915+ """
916+ import sys
917+ sys.stderr.write("no newline")
918+ sys.stderr.flush()
919+ # Keep running briefly
920+ import time
921+ time.sleep(0.1)
922+ """
923+ )
924+
925+ server_params = StdioServerParameters (
926+ command = sys .executable ,
927+ args = ["-c" , script_content ],
928+ )
929+
930+ # Mock _print_stderr to raise an exception only on final buffer call
931+ call_count = 0
932+
933+ def mock_print_stderr_side_effect (line , errlog ):
934+ nonlocal call_count
935+ call_count += 1
936+ if call_count > 1 : # Raise on subsequent calls (final buffer)
937+ raise Exception ("Print failed on final buffer" )
938+
939+ with patch ("mcp.client.stdio._print_stderr" , side_effect = mock_print_stderr_side_effect ):
940+ async with stdio_client (server_params ) as (_ , _ ):
941+ await anyio .sleep (0.3 )
942+ # Should not crash, just log the error
943+
944+
945+ @pytest .mark .anyio
946+ async def test_stderr_with_empty_lines ():
947+ """Test that empty stderr lines are skipped."""
948+ script_content = textwrap .dedent (
949+ """
950+ import sys
951+ print("line1", file=sys.stderr, flush=True)
952+ print("", file=sys.stderr, flush=True) # Empty line
953+ print(" ", file=sys.stderr, flush=True) # Whitespace only
954+ print("line2", file=sys.stderr, flush=True)
955+ # Keep running
956+ try:
957+ while True:
958+ line = sys.stdin.readline()
959+ if not line:
960+ break
961+ except:
962+ pass
963+ """
964+ )
965+
966+ server_params = StdioServerParameters (
967+ command = sys .executable ,
968+ args = ["-c" , script_content ],
969+ )
970+
971+ stderr_capture = io .StringIO ()
972+ async with stdio_client (server_params , errlog = stderr_capture ) as (_ , _ ):
973+ await anyio .sleep (0.3 )
974+
975+ stderr_output = stderr_capture .getvalue ()
976+ # Should have line1 and line2, but not empty lines
977+ assert "line1" in stderr_output
978+ assert "line2" in stderr_output
0 commit comments