Skip to content

Bug: RuntimeError during cleanup on Windows – no running event loop #575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Dadiya-Harsh opened this issue Apr 23, 2025 · 0 comments
Open

Comments

@Dadiya-Harsh
Copy link


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:

  1. 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`
import asyncio
import json
from contextlib import AsyncExitStack
import os
from typing import Any, Dict, List, Optional

import nest_asyncio
from dotenv import load_dotenv
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from groq import AsyncGroq

nest_asyncio.apply()
load_dotenv()

class MCPGroqClient:
    def __init__(self, model: str = "llama-3.3-70b-versatile"):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.groq_client = AsyncGroq(api_key=os.getenv("GROQ_API_KEY"))
        self.model = model
        self.stdio: Optional[Any] = None
        self.write: Optional[Any] = None

    async def connect_to_server(self, server_script_path: str = "server.py"):
        server_params = StdioServerParameters(command="python", args=[server_script_path])
        stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.stdio, self.write = stdio_transport
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
        await self.session.initialize()
        tools_result = await self.session.list_tools()
        print("\nConnected to server with tools:")
        for tool in tools_result.tools:
            print(f"  - {tool.name}: {tool.description}")

    async def get_mcp_tools(self) -> List[Dict[str, Any]]:
        tools_result = await self.session.list_tools()
        return [
            {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description,
                    "parameters": tool.inputSchema,
                },
            }
            for tool in tools_result.tools
        ]

    async def process_query(self, query: str) -> str:
        tools = await self.get_mcp_tools()
        response = await self.groq_client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": query}],
            tools=tools,
            tool_choice="auto",
        )
        assistant_message = response.choices[0].message
        messages = [{"role": "user", "content": query}, assistant_message]

        if assistant_message.tool_calls:
            for tool_call in assistant_message.tool_calls:
                result = await self.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 = await self.groq_client.chat.completions.create(
                model=self.model,
                messages=messages,
                tools=tools,
                tool_choice="none",
            )
            return final_response.choices[0].message.content

        return assistant_message.content

    async def cleanup(self):
        await self.exit_stack.aclose()

async def main():
    client = MCPGroqClient()
    await client.connect_to_server()
    query = "What is our company's vacation policy?"
    print(f"\nQuery: {query}")
    response = await client.process_query(query)
    print(f"\nResponse: {response}")

if __name__ == "__main__":
    asyncio.run(main())
  1. Set your environment variable GROQ_API_KEY.
  2. Start the server with a valid tool (like knowledge_base).
  3. Run the client using:
    uv run client.py
  4. 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__
    if current_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:
    if asyncio.get_event_loop().is_running():
        with anyio.fail_after(2.0):
            ...
  • Or use try/except RuntimeError to suppress it gracefully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant