-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Open
Labels
bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.Something isn't working. Reports of errors, unexpected behavior, or broken functionality.clientRelated to the FastMCP client SDK or client-side functionality.Related to the FastMCP client SDK or client-side functionality.tests
Description
Description
When using Client with StdioMCPServer in pytest, we get warnings during test teardown:
PytestUnraisableExceptionWarning: Exception ignored in: <function BaseSubprocessTransport.__del__ at ...>
Traceback (most recent call last):
File ".../asyncio/base_subprocess.py", line 126, in __del__
self.close()
...
File ".../asyncio/base_events.py", line 515, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Reproduction
import pytest
from fastmcp import Client
from fastmcp.mcp_config import StdioMCPServer
@pytest.mark.asyncio
async def test_stdio_client():
server = StdioMCPServer(command="python", args=["-m", "some_mcp_server"])
async with Client(server) as client:
# do something
pass
# Warning appears after test completesEnvironment
- Python 3.10 on Linux (works fine on macOS)
- fastmcp 3.0.x
- pytest-asyncio
Analysis
The issue is that asyncio's BaseSubprocessTransport has a __del__ method that calls self._loop.call_soon(), which fails if the event loop is already closed. During pytest teardown, the loop closes before all subprocess transport references are garbage collected.
Things we tried that didn't help:
- Setting
keep_alive=FalseonStdioTransport - Explicit
del clientafter context exit await asyncio.sleep(0)to flush callbacksgc.collect()while loop is still open
Possible solutions
- In
StdioTransport.close(), explicitly wait for the subprocess to terminate and clean up the transport before returning - Use
subprocess.Popendirectly instead of asyncio subprocess (avoids theBaseSubprocessTransport.__del__issue) - Store a reference to the transport and call
transport.close()synchronously in a way that doesn't require the event loop
Related
StdioMCPServer.to_transport() doesn't pass through the keep_alive parameter - it always defaults to True on the created StdioTransport. (Noted this is fixed in a future version.)
Metadata
Metadata
Assignees
Labels
bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.Something isn't working. Reports of errors, unexpected behavior, or broken functionality.clientRelated to the FastMCP client SDK or client-side functionality.Related to the FastMCP client SDK or client-side functionality.tests