66import subprocess
77import sys
88from pathlib import Path
9- from typing import TextIO
9+ from typing import IO , TextIO
1010
1111import anyio
12+ from anyio import to_thread
1213from anyio .abc import Process
1314from anyio .streams .file import FileReadStream , FileWriteStream
1415
15- from typing import Optional , TextIO , Union
16- from pathlib import Path
17-
18-
1916def get_windows_executable_command (command : str ) -> str :
2017 """
2118 Get the correct executable command normalized for Windows.
@@ -52,18 +49,18 @@ class DummyProcess:
5249 A fallback process wrapper for Windows to handle async I/O
5350 when using subprocess.Popen, which provides sync-only FileIO objects.
5451
55- This wraps stdin and stdout into async-compatible streams (FileReadStream, FileWriteStream),
52+ This wraps stdin and stdout into async-compatible
53+ streams (FileReadStream, FileWriteStream),
5654 so that MCP clients expecting async streams can work properly.
5755 """
58- def __init__ (self , popen_obj : subprocess .Popen ):
59- self .popen = popen_obj
60- self .stdin_raw = popen_obj .stdin
61- self .stdout_raw = popen_obj .stdout
62- self .stderr = popen_obj .stderr
56+ def __init__ (self , popen_obj : subprocess .Popen [ bytes ] ):
57+ self .popen : subprocess . Popen [ bytes ] = popen_obj
58+ self .stdin_raw : IO [ bytes ] | None = popen_obj .stdin
59+ self .stdout_raw : IO [ bytes ] | None = popen_obj .stdout
60+ self .stderr : IO [ bytes ] | None = popen_obj .stderr
6361
64- # Wrap into async-compatible AnyIO streams
65- self .stdin = FileWriteStream (self .stdin_raw )
66- self .stdout = FileReadStream (self .stdout_raw )
62+ self .stdin = FileWriteStream (self .stdin_raw ) if self .stdin_raw else None
63+ self .stdout = FileReadStream (self .stdout_raw ) if self .stdout_raw else None
6764
6865 async def __aenter__ (self ):
6966 """Support async context manager entry."""
@@ -72,11 +69,11 @@ async def __aenter__(self):
7269 async def __aexit__ (self , exc_type , exc_val , exc_tb ):
7370 """Terminate and wait on process exit inside a thread."""
7471 self .popen .terminate ()
75- await anyio . to_thread .run_sync (self .popen .wait )
72+ await to_thread .run_sync (self .popen .wait )
7673
7774 async def wait (self ):
7875 """Async wait for process completion."""
79- return await anyio . to_thread .run_sync (self .popen .wait )
76+ return await to_thread .run_sync (self .popen .wait )
8077
8178 def terminate (self ):
8279 """Terminate the subprocess immediately."""
@@ -89,10 +86,10 @@ def terminate(self):
8986async def create_windows_process (
9087 command : str ,
9188 args : list [str ],
92- env : Optional [ dict [str , str ]] = None ,
93- errlog : Optional [ TextIO ] = sys .stderr ,
94- cwd : Union [ Path , str , None ] = None ,
95- ):
89+ env : dict [str , str ] | None = None ,
90+ errlog : TextIO | None = sys .stderr ,
91+ cwd : Path | str | None = None ,
92+ ) -> DummyProcess :
9693 """
9794 Creates a subprocess in a Windows-compatible way.
9895
@@ -120,7 +117,11 @@ async def create_windows_process(
120117 env = env ,
121118 cwd = cwd ,
122119 bufsize = 0 , # Unbuffered output
123- creationflags = subprocess .CREATE_NO_WINDOW if hasattr (subprocess , "CREATE_NO_WINDOW" ) else 0 ,
120+ creationflags = (
121+ subprocess .CREATE_NO_WINDOW
122+ if hasattr (subprocess , "CREATE_NO_WINDOW" )
123+ else 0
124+ ),
124125 )
125126 return DummyProcess (popen_obj )
126127
0 commit comments