From 22e2b3a9cf312f129ae1db16c2516bfde12a5017 Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Tue, 22 Apr 2025 14:46:12 +0800 Subject: [PATCH 1/9] Fix the TaskGroup hanging on stdin_writer when stdio_client exiting Triggers a TaskGroup cancel after reaching the finally code block. --- src/mcp/client/stdio/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mcp/client/stdio/__init__.py b/src/mcp/client/stdio/__init__.py index 83de57a2b..17ea94cb3 100644 --- a/src/mcp/client/stdio/__init__.py +++ b/src/mcp/client/stdio/__init__.py @@ -177,6 +177,7 @@ async def stdin_writer(): await terminate_windows_process(process) else: process.terminate() + tg.cancel_scope.cancel() def _get_executable_command(command: str) -> str: From 277449646f92e39812b72428618f9cabf0826398 Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Tue, 22 Apr 2025 15:45:07 +0800 Subject: [PATCH 2/9] Add test_stdio_cm for stdin_writer hanging --- tests/client/test_stdio_cm.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/client/test_stdio_cm.py diff --git a/tests/client/test_stdio_cm.py b/tests/client/test_stdio_cm.py new file mode 100644 index 000000000..f8fb9f7d7 --- /dev/null +++ b/tests/client/test_stdio_cm.py @@ -0,0 +1,18 @@ +import pytest + +from mcp import StdioServerParameters +from mcp.client.stdio import stdio_client + +MCP_SERVER = { + "command": "uvx", + "args": ["mcp-server-fetch"], +} + + +@pytest.mark.anyio +async def test_context_manager_exiting(): + async with stdio_client(StdioServerParameters(**MCP_SERVER)) as ( + read_stream, + write_stream, + ): + pass From 2591f806b1b6c49f0bf94c2b9f53c20865ed0ba0 Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Tue, 22 Apr 2025 16:03:57 +0800 Subject: [PATCH 3/9] Test using simple tee instead of mcp-server-fetch --- tests/client/test_stdio_cm.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/client/test_stdio_cm.py b/tests/client/test_stdio_cm.py index f8fb9f7d7..d5ceef9db 100644 --- a/tests/client/test_stdio_cm.py +++ b/tests/client/test_stdio_cm.py @@ -4,8 +4,7 @@ from mcp.client.stdio import stdio_client MCP_SERVER = { - "command": "uvx", - "args": ["mcp-server-fetch"], + "command": "tee", } From 474f0721422430c18f1074f27bdcbffc94e7f3b7 Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Tue, 22 Apr 2025 16:05:36 +0800 Subject: [PATCH 4/9] Close write_stream_reader in finally block --- src/mcp/client/stdio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/client/stdio/__init__.py b/src/mcp/client/stdio/__init__.py index 17ea94cb3..eb61aee83 100644 --- a/src/mcp/client/stdio/__init__.py +++ b/src/mcp/client/stdio/__init__.py @@ -177,7 +177,7 @@ async def stdin_writer(): await terminate_windows_process(process) else: process.terminate() - tg.cancel_scope.cancel() + write_stream_reader.close() def _get_executable_command(command: str) -> str: From d2f1146d496d1183ac0d386b53cc69510561fc7a Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Tue, 22 Apr 2025 16:08:41 +0800 Subject: [PATCH 5/9] Fix pyright warnings in test_stdio_cm.py --- tests/client/test_stdio_cm.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/client/test_stdio_cm.py b/tests/client/test_stdio_cm.py index d5ceef9db..524d75573 100644 --- a/tests/client/test_stdio_cm.py +++ b/tests/client/test_stdio_cm.py @@ -3,14 +3,10 @@ from mcp import StdioServerParameters from mcp.client.stdio import stdio_client -MCP_SERVER = { - "command": "tee", -} - @pytest.mark.anyio async def test_context_manager_exiting(): - async with stdio_client(StdioServerParameters(**MCP_SERVER)) as ( + async with stdio_client(StdioServerParameters(command="tee")) as ( read_stream, write_stream, ): From 12d4740adf523c672354f786e29f698d7b0dd1a5 Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Tue, 22 Apr 2025 16:25:35 +0800 Subject: [PATCH 6/9] Fix read_stream stream leakage --- src/mcp/client/stdio/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mcp/client/stdio/__init__.py b/src/mcp/client/stdio/__init__.py index eb61aee83..fcc9037f2 100644 --- a/src/mcp/client/stdio/__init__.py +++ b/src/mcp/client/stdio/__init__.py @@ -177,7 +177,8 @@ async def stdin_writer(): await terminate_windows_process(process) else: process.terminate() - write_stream_reader.close() + read_stream.close() + write_stream.close() def _get_executable_command(command: str) -> str: From 15b24ebc4fa171245d91275e964f57f00f101ce3 Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Thu, 8 May 2025 14:26:20 +0800 Subject: [PATCH 7/9] Update test_stdio.py --- tests/client/test_stdio.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/client/test_stdio.py b/tests/client/test_stdio.py index 95747ffd1..daf0f432d 100644 --- a/tests/client/test_stdio.py +++ b/tests/client/test_stdio.py @@ -8,6 +8,13 @@ tee: str = shutil.which("tee") # type: ignore +@pytest.mark.anyio +@pytest.mark.skipif(tee is None, reason="could not find tee command") +async def test_stdio_context_manager_exiting(): + async with stdio_client(StdioServerParameters(command=tee)) as (_, _): + pass + + @pytest.mark.anyio @pytest.mark.skipif(tee is None, reason="could not find tee command") async def test_stdio_client(): From a2e332899683f6fadcc830250ee90c32b04a47a0 Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Thu, 8 May 2025 14:26:59 +0800 Subject: [PATCH 8/9] Delete tests/client/test_stdio_cm.py Merged to test_stdio.py --- tests/client/test_stdio_cm.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 tests/client/test_stdio_cm.py diff --git a/tests/client/test_stdio_cm.py b/tests/client/test_stdio_cm.py deleted file mode 100644 index 524d75573..000000000 --- a/tests/client/test_stdio_cm.py +++ /dev/null @@ -1,13 +0,0 @@ -import pytest - -from mcp import StdioServerParameters -from mcp.client.stdio import stdio_client - - -@pytest.mark.anyio -async def test_context_manager_exiting(): - async with stdio_client(StdioServerParameters(command="tee")) as ( - read_stream, - write_stream, - ): - pass From a59995eabfe66eb224aee32f4edf28f16d276beb Mon Sep 17 00:00:00 2001 From: Lion Yang Date: Fri, 9 May 2025 20:58:09 +0800 Subject: [PATCH 9/9] Update src/mcp/client/stdio/__init__.py Use async versions of .close() Co-authored-by: ihrpr --- src/mcp/client/stdio/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mcp/client/stdio/__init__.py b/src/mcp/client/stdio/__init__.py index fcc9037f2..db7443e3b 100644 --- a/src/mcp/client/stdio/__init__.py +++ b/src/mcp/client/stdio/__init__.py @@ -177,8 +177,8 @@ async def stdin_writer(): await terminate_windows_process(process) else: process.terminate() - read_stream.close() - write_stream.close() + await read_stream.aclose() + await write_stream.aclose() def _get_executable_command(command: str) -> str: