Skip to content

Commit a2870a7

Browse files
Implement recursive process tree termination for Windows
The taskkill /T command doesn't reliably kill all child processes when they're created by Python subprocesses. This implements a recursive solution using WMI to: 1. Find all child processes using wmic 2. Recursively kill all children first (depth-first) 3. Finally kill the parent process This ensures that entire process trees are properly terminated, including nested processes like parent->child->grandchild. Reported-by: fweinberger
1 parent d345d0d commit a2870a7

File tree

1 file changed

+46
-9
lines changed

1 file changed

+46
-9
lines changed

src/mcp/client/stdio/__init__.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,49 @@ async def _create_platform_compatible_process(
252252
return process
253253

254254

255+
async def _kill_process_tree_windows(pid: int) -> None:
256+
"""
257+
Recursively kill a process and all its children on Windows.
258+
Uses WMI to find child processes since taskkill /T doesn't always work.
259+
"""
260+
# First, get all child processes
261+
children_pids = set()
262+
263+
# Use wmic to get child processes
264+
result = await anyio.to_thread.run_sync(
265+
subprocess.run,
266+
["wmic", "process", "where", f"ParentProcessId={pid}", "get", "ProcessId", "/FORMAT:VALUE"],
267+
capture_output=True,
268+
shell=False,
269+
check=False,
270+
text=True,
271+
)
272+
273+
if result.returncode == 0 and result.stdout:
274+
# Parse child PIDs
275+
for line in result.stdout.strip().split('\n'):
276+
if line.startswith('ProcessId='):
277+
try:
278+
child_pid = int(line.split('=')[1].strip())
279+
if child_pid > 0:
280+
children_pids.add(child_pid)
281+
except (ValueError, IndexError):
282+
pass
283+
284+
# Recursively kill all children first
285+
for child_pid in children_pids:
286+
await _kill_process_tree_windows(child_pid)
287+
288+
# Finally kill the parent process
289+
await anyio.to_thread.run_sync(
290+
subprocess.run,
291+
["taskkill", "/F", "/PID", str(pid)],
292+
capture_output=True,
293+
shell=False,
294+
check=False,
295+
)
296+
297+
255298
async def _terminate_process_with_children(process: Process | FallbackProcess, timeout: float = 2.0) -> None:
256299
"""
257300
Terminate a process and all its children across platforms.
@@ -306,14 +349,8 @@ async def _terminate_process_with_children(process: Process | FallbackProcess, t
306349
with anyio.fail_after(timeout):
307350
await process.wait()
308351
except TimeoutError:
309-
# Force kill using taskkill for tree termination
310-
# The /T flag kills the process tree when processes share the same console
311-
await anyio.to_thread.run_sync(
312-
subprocess.run,
313-
["taskkill", "/F", "/T", "/PID", str(pid)],
314-
capture_output=True,
315-
shell=False,
316-
check=False,
317-
)
352+
# On Windows, we need to recursively kill all child processes
353+
# because taskkill /T doesn't always work with Python subprocesses
354+
await _kill_process_tree_windows(pid)
318355
except ProcessLookupError:
319356
pass

0 commit comments

Comments
 (0)