6
6
import subprocess
7
7
import sys
8
8
from pathlib import Path
9
- from typing import TextIO
9
+ from typing import IO , TextIO
10
10
11
11
import anyio
12
+ from anyio import to_thread
12
13
from anyio .abc import Process
13
14
from anyio .streams .file import FileReadStream , FileWriteStream
14
15
15
- from typing import Optional , TextIO , Union
16
- from pathlib import Path
17
-
18
-
19
16
def get_windows_executable_command (command : str ) -> str :
20
17
"""
21
18
Get the correct executable command normalized for Windows.
@@ -52,18 +49,18 @@ class DummyProcess:
52
49
A fallback process wrapper for Windows to handle async I/O
53
50
when using subprocess.Popen, which provides sync-only FileIO objects.
54
51
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),
56
54
so that MCP clients expecting async streams can work properly.
57
55
"""
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
63
61
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
67
64
68
65
async def __aenter__ (self ):
69
66
"""Support async context manager entry."""
@@ -72,11 +69,11 @@ async def __aenter__(self):
72
69
async def __aexit__ (self , exc_type , exc_val , exc_tb ):
73
70
"""Terminate and wait on process exit inside a thread."""
74
71
self .popen .terminate ()
75
- await anyio . to_thread .run_sync (self .popen .wait )
72
+ await to_thread .run_sync (self .popen .wait )
76
73
77
74
async def wait (self ):
78
75
"""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 )
80
77
81
78
def terminate (self ):
82
79
"""Terminate the subprocess immediately."""
@@ -89,10 +86,10 @@ def terminate(self):
89
86
async def create_windows_process (
90
87
command : str ,
91
88
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 :
96
93
"""
97
94
Creates a subprocess in a Windows-compatible way.
98
95
@@ -120,7 +117,11 @@ async def create_windows_process(
120
117
env = env ,
121
118
cwd = cwd ,
122
119
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
+ ),
124
125
)
125
126
return DummyProcess (popen_obj )
126
127
0 commit comments