Describe the bug
On Windows 11 when you initialize an mcp client it hangs indefinitely.
To Reproduce
Code to reproduce:
# ruff: noqaimportasynciofrommcpimportClientSession, StdioServerParametersfrommcp.client.sseimportsse_clientfrommcp.client.stdioimportstdio_clientasyncdefrun():
params=StdioServerParameters(
command='bunx', args=['@playwright/mcp@latest']
)
asyncwithstdio_client(params) as (read, write):
print('inside client')
asyncwithClientSession(read, write) asc:
print('inside ClientSession')
awaitc.initialize()
print('exit ClientSession')
print('exit stdio_client')
asyncdefrun_sse():
asyncwithsse_client('http://localhost:8931/sse') as (read, write):
asyncwithClientSession(read, write) asc:
awaitc.initialize()
print('exit ClientSession')
print('exit sse_client')
if__name__=='__main__':
asyncio.run(run_sse()) # worksasyncio.run(run()) # does not workExpected behavior
in both cases it should print both exit statements
Desktop (please complete the following information):
- OS: Windows 11
- Python Version: Tested on both 3.13.1 and 3.12.7
Additional context
counterintuitively commenting out the code meant to support windows fixes this issue:
importosimportsysfromcontextlibimportasynccontextmanagerfrompathlibimportPathfromtypingimportLiteral, TextIOimportanyioimportanyio.lowlevelfromanyio.streams.memoryimportMemoryObjectReceiveStream, MemoryObjectSendStreamfromanyio.streams.textimportTextReceiveStreamfrompydanticimportBaseModel, Fieldimportmcp.typesastypes# from .win32 import (# create_windows_process,# get_windows_executable_command,# terminate_windows_process,# )# Environment variables to inherit by defaultDEFAULT_INHERITED_ENV_VARS= (
[
"APPDATA",
"HOMEDRIVE",
"HOMEPATH",
"LOCALAPPDATA",
"PATH",
"PROCESSOR_ARCHITECTURE",
"SYSTEMDRIVE",
"SYSTEMROOT",
"TEMP",
"USERNAME",
"USERPROFILE",
]
ifsys.platform=="win32"else ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]
)
print( sys.platform)
defget_default_environment() ->dict[str, str]:
""" Returns a default environment object including only environment variables deemed safe to inherit. """env: dict[str, str] = {}
forkeyinDEFAULT_INHERITED_ENV_VARS:
value=os.environ.get(key)
ifvalueisNone:
continueifvalue.startswith("()"):
# Skip functions, which are a security riskcontinueenv[key] =valuereturnenvclassStdioServerParameters(BaseModel):
command: str"""The executable to run to start the server."""args: list[str] =Field(default_factory=list)
"""Command line arguments to pass to the executable."""env: dict[str, str] |None=None""" The environment to use when spawning the process. If not specified, the result of get_default_environment() will be used. """cwd: str|Path|None=None"""The working directory to use when spawning the process."""encoding: str="utf-8"""" The text encoding used when sending/receiving messages to the server defaults to utf-8 """encoding_error_handler: Literal["strict", "ignore", "replace"] ="strict"""" The text encoding error handler. See https://docs.python.org/3/library/codecs.html#codec-base-classes for explanations of possible values """@asynccontextmanagerasyncdefstdio_client(server: StdioServerParameters, errlog: TextIO=sys.stderr):
""" Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout. """read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage|Exception]
read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage|Exception]
write_stream: MemoryObjectSendStream[types.JSONRPCMessage]
write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage]
read_stream_writer, read_stream=anyio.create_memory_object_stream(0)
write_stream, write_stream_reader=anyio.create_memory_object_stream(0)
command=_get_executable_command(server.command)
# Open process with stderr piped for captureprocess=await_create_platform_compatible_process(
command=command,
args=server.args,
env=(
{**get_default_environment(), **server.env}
ifserver.envisnotNoneelseget_default_environment()
),
errlog=errlog,
cwd=server.cwd,
)
asyncdefstdout_reader():
assertprocess.stdout, "Opened process is missing stdout"try:
asyncwithread_stream_writer:
buffer=""asyncforchunkinTextReceiveStream(
process.stdout,
encoding=server.encoding,
errors=server.encoding_error_handler,
):
lines= (buffer+chunk).split("\n")
buffer=lines.pop()
forlineinlines:
try:
message=types.JSONRPCMessage.model_validate_json(line)
exceptExceptionasexc:
awaitread_stream_writer.send(exc)
continueawaitread_stream_writer.send(message)
exceptanyio.ClosedResourceError:
awaitanyio.lowlevel.checkpoint()
asyncdefstdin_writer():
assertprocess.stdin, "Opened process is missing stdin"try:
asyncwithwrite_stream_reader:
asyncformessageinwrite_stream_reader:
json=message.model_dump_json(by_alias=True, exclude_none=True)
awaitprocess.stdin.send(
(json+"\n").encode(
encoding=server.encoding,
errors=server.encoding_error_handler,
)
)
exceptanyio.ClosedResourceError:
awaitanyio.lowlevel.checkpoint()
asyncwith (
anyio.create_task_group() astg,
process,
):
tg.start_soon(stdout_reader)
tg.start_soon(stdin_writer)
try:
yieldread_stream, write_streamfinally:
# Clean up process to prevent any dangling orphaned processes# if sys.platform == "win32":# await terminate_windows_process(process)# else:process.terminate()
def_get_executable_command(command: str) ->str:
""" Get the correct executable command normalized for the current platform. Args: command: Base command (e.g., 'uvx', 'npx') Returns: str: Platform-appropriate command """# if sys.platform == "win32":# return get_windows_executable_command(command)# else:returncommandasyncdef_create_platform_compatible_process(
command: str,
args: list[str],
env: dict[str, str] |None=None,
errlog: TextIO=sys.stderr,
cwd: Path|str|None=None,
):
""" Creates a subprocess in a platform-compatible way. Returns a process handle. """# if sys.platform == "win32":# print('attempting create windows process')# process = await create_windows_process(command, args, env, errlog, cwd)# print('created windows process')# else:process=awaitanyio.open_process(
[command, *args], env=env, stderr=errlog, cwd=cwd
)
returnprocess
Describe the bug
On Windows 11 when you initialize an mcp client it hangs indefinitely.
To Reproduce
Code to reproduce:
Expected behavior
in both cases it should print both exit statements
Desktop (please complete the following information):
Additional context
counterintuitively commenting out the code meant to support windows fixes this issue: