55import tempfile
66import textwrap
77import time
8+ from unittest .mock import MagicMock , patch
89
910import anyio
1011import pytest
@@ -648,13 +649,13 @@ async def test_stderr_captured_in_process(self):
648649 """Test that stderr output from a subprocess can be captured."""
649650 # Create a script that writes to stderr
650651 script = textwrap .dedent (
651- '''
652+ """
652653 import sys
653654 sys.stderr.write("test error message\\ n")
654655 sys.stderr.flush()
655656 # Exit immediately
656657 sys.exit(0)
657- '''
658+ """
658659 )
659660
660661 server_params = StdioServerParameters (
@@ -674,7 +675,7 @@ async def test_stderr_with_continuous_output(self):
674675 """Test that continuous stderr output doesn't block the client."""
675676 # Create a script that writes to stderr continuously then exits
676677 script = textwrap .dedent (
677- '''
678+ """
678679 import sys
679680 import time
680681
@@ -685,7 +686,7 @@ async def test_stderr_with_continuous_output(self):
685686
686687 # Exit after writing
687688 sys.exit(0)
688- '''
689+ """
689690 )
690691
691692 server_params = StdioServerParameters (
@@ -699,3 +700,96 @@ async def test_stderr_with_continuous_output(self):
699700 await anyio .sleep (1.0 ) # Wait for stderr output
700701
701702 assert not cancel_scope .cancelled_caught , "stdio_client should handle continuous stderr output"
703+
704+ def test_jupyter_detection_ipkernel_app (self ):
705+ """Test that _is_jupyter_environment returns True for IPKernelApp config."""
706+ mock_ipython_instance = MagicMock ()
707+ mock_ipython_instance .config = {"IPKernelApp" : {}}
708+ mock_ipython_instance .__class__ = type ("TerminalInteractiveShell" , (), {})
709+
710+ mock_ipython_module = MagicMock ()
711+ mock_ipython_module .get_ipython = MagicMock (return_value = mock_ipython_instance )
712+
713+ with patch .dict ("sys.modules" , {"IPython" : mock_ipython_module }):
714+ result = _is_jupyter_environment ()
715+ assert result is True
716+
717+ def test_jupyter_detection_zmq_shell (self ):
718+ """Test that _is_jupyter_environment returns True for ZMQInteractiveShell."""
719+ mock_ipython_instance = MagicMock ()
720+ mock_ipython_instance .config = {}
721+ mock_ipython_instance .__class__ = type ("ZMQInteractiveShell" , (), {})
722+
723+ mock_ipython_module = MagicMock ()
724+ mock_ipython_module .get_ipython = MagicMock (return_value = mock_ipython_instance )
725+
726+ with patch .dict ("sys.modules" , {"IPython" : mock_ipython_module }):
727+ result = _is_jupyter_environment ()
728+ assert result is True
729+
730+ def test_jupyter_detection_non_notebook_ipython (self ):
731+ """Test that _is_jupyter_environment returns False for plain IPython terminal."""
732+ mock_ipython_instance = MagicMock ()
733+ mock_ipython_instance .config = {}
734+ mock_ipython_instance .__class__ = type ("TerminalInteractiveShell" , (), {})
735+
736+ mock_ipython_module = MagicMock ()
737+ mock_ipython_module .get_ipython = MagicMock (return_value = mock_ipython_instance )
738+
739+ with patch .dict ("sys.modules" , {"IPython" : mock_ipython_module }):
740+ result = _is_jupyter_environment ()
741+ assert result is False
742+
743+ @pytest .mark .anyio
744+ async def test_stderr_reader_jupyter_mode (self ):
745+ """Test that stderr is captured and printed in Jupyter mode."""
746+ script = textwrap .dedent (
747+ """
748+ import sys
749+ sys.stderr.write("jupyter error output\\ n")
750+ sys.stderr.flush()
751+ sys.exit(0)
752+ """
753+ )
754+
755+ server_params = StdioServerParameters (
756+ command = sys .executable ,
757+ args = ["-c" , script ],
758+ )
759+
760+ # Mock _is_jupyter_environment to return True to exercise stderr_reader
761+ with patch ("mcp.client.stdio._is_jupyter_environment" , return_value = True ):
762+ with anyio .move_on_after (5.0 ) as cancel_scope :
763+ async with stdio_client (server_params ) as (read_stream , write_stream ):
764+ await anyio .sleep (1.0 )
765+
766+ assert not cancel_scope .cancelled_caught , "stdio_client should not hang in Jupyter mode"
767+
768+ @pytest .mark .anyio
769+ async def test_stderr_reader_jupyter_mode_continuous (self ):
770+ """Test that continuous stderr output is handled in Jupyter mode."""
771+ script = textwrap .dedent (
772+ """
773+ import sys
774+ import time
775+
776+ for i in range(3):
777+ sys.stderr.write(f"jupyter stderr line {i}\\ n")
778+ sys.stderr.flush()
779+ time.sleep(0.05)
780+
781+ sys.exit(0)
782+ """
783+ )
784+
785+ server_params = StdioServerParameters (
786+ command = sys .executable ,
787+ args = ["-c" , script ],
788+ )
789+
790+ with patch ("mcp.client.stdio._is_jupyter_environment" , return_value = True ):
791+ with anyio .move_on_after (5.0 ) as cancel_scope :
792+ async with stdio_client (server_params ) as (read_stream , write_stream ):
793+ await anyio .sleep (1.0 )
794+
795+ assert not cancel_scope .cancelled_caught , "stdio_client should handle continuous Jupyter stderr"
0 commit comments