Describe the bug
When running the mcp client using uv run client.py on Windows, the client connects and runs correctly, but after the response is returned, a RuntimeError: no running event loop is thrown during shutdown. The error occurs inside the terminate_windows_process function in win32.py, which uses anyio.fail_after() after the event loop has already closed.
To Reproduce
Steps to reproduce the behavior:
- Use the following Python code to create a minimal MCP client that connects to a server and runs a tool-enabled Groq chat query:
# save this as `client.py`importasyncioimportjsonfromcontextlibimportAsyncExitStackimportosfromtypingimportAny, Dict, List, Optionalimportnest_asynciofromdotenvimportload_dotenvfrommcpimportClientSession, StdioServerParametersfrommcp.client.stdioimportstdio_clientfromgroqimportAsyncGroqnest_asyncio.apply()
load_dotenv()
classMCPGroqClient:
def__init__(self, model: str="llama-3.3-70b-versatile"):
self.session: Optional[ClientSession] =Noneself.exit_stack=AsyncExitStack()
self.groq_client=AsyncGroq(api_key=os.getenv("GROQ_API_KEY"))
self.model=modelself.stdio: Optional[Any] =Noneself.write: Optional[Any] =Noneasyncdefconnect_to_server(self, server_script_path: str="server.py"):
server_params=StdioServerParameters(command="python", args=[server_script_path])
stdio_transport=awaitself.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write=stdio_transportself.session=awaitself.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
awaitself.session.initialize()
tools_result=awaitself.session.list_tools()
print("\nConnected to server with tools:")
fortoolintools_result.tools:
print(f" - {tool.name}: {tool.description}")
asyncdefget_mcp_tools(self) ->List[Dict[str, Any]]:
tools_result=awaitself.session.list_tools()
return [
{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
},
}
fortoolintools_result.tools
]
asyncdefprocess_query(self, query: str) ->str:
tools=awaitself.get_mcp_tools()
response=awaitself.groq_client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": query}],
tools=tools,
tool_choice="auto",
)
assistant_message=response.choices[0].messagemessages= [{"role": "user", "content": query}, assistant_message]
ifassistant_message.tool_calls:
fortool_callinassistant_message.tool_calls:
result=awaitself.session.call_tool(
tool_call.function.name,
arguments=json.loads(tool_call.function.arguments),
)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result.content[0].text,
})
final_response=awaitself.groq_client.chat.completions.create(
model=self.model,
messages=messages,
tools=tools,
tool_choice="none",
)
returnfinal_response.choices[0].message.contentreturnassistant_message.contentasyncdefcleanup(self):
awaitself.exit_stack.aclose()
asyncdefmain():
client=MCPGroqClient()
awaitclient.connect_to_server()
query="What is our company's vacation policy?"print(f"\nQuery: {query}")
response=awaitclient.process_query(query)
print(f"\nResponse: {response}")
if__name__=="__main__":
asyncio.run(main())- Set your environment variable
GROQ_API_KEY. - Start the server with a valid tool (like
knowledge_base). - Run the client using:
- Observe the traceback at shutdown related to
RuntimeError: no running event loop.
Expected behavior
The client should shut down gracefully without throwing exceptions after processing a response.
Screenshots / Traceback
Exception ignored in: <async_generator object stdio_client at 0x000002025AE0EFC0>
Traceback (most recent call last):
File "C:\Program Files\Python312\Lib\asyncio\tasks.py", line 314, in __step_run_and_handle_result
result = coro.send(None)
^^^^^^^^^^^^^^^
RuntimeError: async generator ignored GeneratorExit
Exception ignored in: <coroutine object terminate_windows_process at 0x000002025AF5F3E0>
Traceback (most recent call last):
File "...\Lib\site-packages\mcp\client\stdio\win32.py", line 105, in terminate_windows_process
with anyio.fail_after(2.0):
File "C:\Program Files\Python312\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "...\Lib\site-packages\anyio\_core\_tasks.py", line 112, in fail_after
with get_async_backend().create_cancel_scope(
File "...\Lib\site-packages\anyio\_backends\_asyncio.py", line 456, in __exit__
ifcurrent_task() is not self._host_task:
^^^^^^^^^^^^^^
RuntimeError: no running event loop
Desktop (please complete the following information):
- OS: Windows 11
- Python Version: 3.12
- MCP Version: [insert version]
- AnyIO Version: [insert version]
- Terminal: Windows PowerShell
- Command Used:
uv run client.py
Additional context
- This issue seems to be a Windows-specific async shutdown bug.
- Happens when
anyio.fail_after() is used after the event loop is already closed. - Suggest adding a check before using async context tools during shutdown, such as:
ifasyncio.get_event_loop().is_running():
withanyio.fail_after(2.0):
...
- Or use
try/except RuntimeError to suppress it gracefully.
Describe the bug
When running the
mcpclient usinguv run client.pyon Windows, the client connects and runs correctly, but after the response is returned, aRuntimeError: no running event loopis thrown during shutdown. The error occurs inside theterminate_windows_processfunction inwin32.py, which usesanyio.fail_after()after the event loop has already closed.To Reproduce
Steps to reproduce the behavior:
GROQ_API_KEY.knowledge_base).RuntimeError: no running event loop.Expected behavior
The client should shut down gracefully without throwing exceptions after processing a response.
Screenshots / Traceback
Desktop (please complete the following information):
uv run client.pyAdditional context
anyio.fail_after()is used after the event loop is already closed.try/except RuntimeErrorto suppress it gracefully.