Skip to content

Commit bee827e

Browse files
Restore anyio.open_process as primary Windows process creation method
Changes create_windows_process to try anyio.open_process first before falling back to FallbackProcess. This ensures ProactorEventLoop users get proper async subprocess support while maintaining compatibility for SelectorEventLoop users (Python 3.13+, Streamlit, etc). The function now returns Process | FallbackProcess to reflect both possible return types. This makes the fallback truly a fallback rather than the default, improving performance and compatibility for users with properly configured event loops.
1 parent 9475dee commit bee827e

File tree

1 file changed

+43
-21
lines changed

1 file changed

+43
-21
lines changed

src/mcp/client/stdio/win32.py

Lines changed: 43 additions & 21 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 (works with ProactorEventLoop).
136+
Falls back to subprocess.Popen wrapped in FallbackProcess if that fails
137+
(typically with SelectorEventLoop in Python 3.13+ or frameworks like Streamlit).
137138
138139
Args:
139140
command (str): The executable to run
@@ -143,31 +144,52 @@ 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: Either anyio Process or FallbackProcess wrapper
147148
"""
148149
try:
149-
# Try launching with creationflags to avoid opening a new console window
150-
popen_obj = subprocess.Popen(
150+
# Try with Windows-specific flags to hide console window
151+
process = await anyio.open_process(
151152
[command, *args],
152-
stdin=subprocess.PIPE,
153-
stdout=subprocess.PIPE,
154-
stderr=errlog,
155153
env=env,
154+
stderr=errlog,
156155
cwd=cwd,
157-
bufsize=0, # Unbuffered output
158-
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
156+
creationflags=subprocess.CREATE_NO_WINDOW # type: ignore
157+
if hasattr(subprocess, "CREATE_NO_WINDOW")
158+
else 0,
159159
)
160-
return FallbackProcess(popen_obj)
161-
160+
return process
161+
except NotImplementedError:
162+
# Fall back to subprocess.Popen for SelectorEventLoop compatibility
163+
try:
164+
popen_obj = subprocess.Popen(
165+
[command, *args],
166+
stdin=subprocess.PIPE,
167+
stdout=subprocess.PIPE,
168+
stderr=errlog,
169+
env=env,
170+
cwd=cwd,
171+
bufsize=0, # Unbuffered output
172+
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
173+
)
174+
return FallbackProcess(popen_obj)
175+
except Exception:
176+
# If creationflags failed, try without them
177+
popen_obj = subprocess.Popen(
178+
[command, *args],
179+
stdin=subprocess.PIPE,
180+
stdout=subprocess.PIPE,
181+
stderr=errlog,
182+
env=env,
183+
cwd=cwd,
184+
bufsize=0,
185+
)
186+
return FallbackProcess(popen_obj)
162187
except Exception:
163-
# If creationflags failed, fallback without them
164-
popen_obj = subprocess.Popen(
188+
# If anyio failed for other reasons, try without creation flags
189+
process = await anyio.open_process(
165190
[command, *args],
166-
stdin=subprocess.PIPE,
167-
stdout=subprocess.PIPE,
168-
stderr=errlog,
169191
env=env,
192+
stderr=errlog,
170193
cwd=cwd,
171-
bufsize=0,
172194
)
173-
return FallbackProcess(popen_obj)
195+
return process

0 commit comments

Comments
 (0)