Skip to content

Commit 7dff9c1

Browse files
Restore anyio.open_process as primary Windows process creation method
This restores the pre-#596 behavior where anyio.open_process is tried first, falling back to subprocess.Popen only when necessary (e.g., when using SelectorEventLoop on Windows). The approach: 1. Try anyio.open_process with CREATE_NO_WINDOW flag 2. If NotImplementedError, fall back to subprocess.Popen wrapper 3. If other exception, retry anyio.open_process without flags Also refactored to extract the fallback logic into a separate _create_windows_fallback_process function for better code organization.
1 parent 7af6896 commit 7dff9c1

File tree

1 file changed

+38
-5
lines changed

1 file changed

+38
-5
lines changed

src/mcp/client/stdio/win32.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import BinaryIO, TextIO, cast
1010

1111
import anyio
12+
from anyio.abc import Process
1213
from anyio.streams.file import FileReadStream, FileWriteStream
1314

1415

@@ -127,13 +128,13 @@ async def create_windows_process(
127128
env: dict[str, str] | None = None,
128129
errlog: TextIO | None = sys.stderr,
129130
cwd: Path | str | None = None,
130-
) -> FallbackProcess:
131+
) -> Process | FallbackProcess:
131132
"""
132133
Creates a subprocess in a Windows-compatible way.
133134
134-
On Windows, asyncio.create_subprocess_exec has incomplete support
135-
(NotImplementedError when trying to open subprocesses).
136-
Therefore, we fallback to subprocess.Popen and wrap it for async usage.
135+
First attempts to use anyio.open_process. If that fails
136+
(e.g., with SelectorEventLoop on Windows), falls back
137+
to subprocess.Popen wrapped for async usage.
137138
138139
Args:
139140
command (str): The executable to run
@@ -143,7 +144,39 @@ async def create_windows_process(
143144
cwd (Path | str | None): Working directory for the subprocess
144145
145146
Returns:
146-
FallbackProcess: Async-compatible subprocess with stdin and stdout streams
147+
Process | FallbackProcess: Async-compatible subprocess with stdin and stdout streams
148+
"""
149+
try:
150+
# Try with Windows-specific flags to hide console window
151+
process = await anyio.open_process(
152+
[command, *args],
153+
env=env,
154+
# Ensure we don't create console windows for each process
155+
creationflags=subprocess.CREATE_NO_WINDOW # type: ignore
156+
if hasattr(subprocess, "CREATE_NO_WINDOW")
157+
else 0,
158+
stderr=errlog,
159+
cwd=cwd,
160+
)
161+
return process
162+
except NotImplementedError:
163+
# If anyio fails (e.g., with SelectorEventLoop), fall back to subprocess.Popen
164+
return await _create_windows_fallback_process(command, args, env=env, errlog=errlog, cwd=cwd)
165+
except Exception:
166+
# Don't raise, let's try to create the process without creation flags
167+
process = await anyio.open_process([command, *args], env=env, stderr=errlog, cwd=cwd)
168+
return process
169+
170+
171+
async def _create_windows_fallback_process(
172+
command: str,
173+
args: list[str],
174+
env: dict[str, str] | None = None,
175+
errlog: TextIO | None = sys.stderr,
176+
cwd: Path | str | None = None,
177+
) -> FallbackProcess:
178+
"""
179+
Create a FallbackProcess for Windows using subprocess.Popen.
147180
"""
148181
try:
149182
# Try launching with creationflags to avoid opening a new console window

0 commit comments

Comments
 (0)