fastmcp/tests/client/test_stdio.py
2025-06-04 15:37:55 -04:00

117 lines
3.9 KiB
Python

import inspect
import pytest
from fastmcp import Client
from fastmcp.client.transports import PythonStdioTransport, StdioTransport
class TestKeepAlive:
# https://github.com/jlowin/fastmcp/issues/581
@pytest.fixture
def stdio_script(self, tmp_path):
script = inspect.cleandoc('''
import os
from fastmcp import FastMCP
mcp = FastMCP()
@mcp.tool
def pid() -> int:
"""Gets PID of server"""
return os.getpid()
if __name__ == "__main__":
mcp.run()
''')
script_file = tmp_path / "stdio.py"
script_file.write_text(script)
return script_file
async def test_keep_alive_default_true(self):
client = Client(transport=StdioTransport(command="python", args=[""]))
assert client.transport.keep_alive is True
async def test_keep_alive_set_false(self):
client = Client(
transport=StdioTransport(command="python", args=[""], keep_alive=False)
)
assert client.transport.keep_alive is False
async def test_keep_alive_maintains_session_across_multiple_calls(
self, stdio_script
):
client = Client(transport=PythonStdioTransport(script_path=stdio_script))
assert client.transport.keep_alive is True
async with client:
result1 = await client.call_tool("pid")
pid1 = int(result1[0].text) # type: ignore[attr-defined]
async with client:
result2 = await client.call_tool("pid")
pid2 = int(result2[0].text) # type: ignore[attr-defined]
assert pid1 == pid2
async def test_keep_alive_false_starts_new_session_across_multiple_calls(
self, stdio_script
):
client = Client(
transport=PythonStdioTransport(script_path=stdio_script, keep_alive=False)
)
assert client.transport.keep_alive is False
async with client:
result1 = await client.call_tool("pid")
pid1 = int(result1[0].text) # type: ignore[attr-defined]
async with client:
result2 = await client.call_tool("pid")
pid2 = int(result2[0].text) # type: ignore[attr-defined]
assert pid1 != pid2
async def test_keep_alive_starts_new_session_if_manually_closed(self, stdio_script):
client = Client(transport=PythonStdioTransport(script_path=stdio_script))
assert client.transport.keep_alive is True
async with client:
result1 = await client.call_tool("pid")
pid1 = int(result1[0].text) # type: ignore[attr-defined]
await client.close()
async with client:
result2 = await client.call_tool("pid")
pid2 = int(result2[0].text) # type: ignore[attr-defined]
assert pid1 != pid2
async def test_keep_alive_maintains_session_if_reentered(self, stdio_script):
client = Client(transport=PythonStdioTransport(script_path=stdio_script))
assert client.transport.keep_alive is True
async with client:
result1 = await client.call_tool("pid")
pid1 = int(result1[0].text) # type: ignore[attr-defined]
async with client:
result2 = await client.call_tool("pid")
pid2 = int(result2[0].text) # type: ignore[attr-defined]
result3 = await client.call_tool("pid")
pid3 = int(result3[0].text) # type: ignore[attr-defined]
assert pid1 == pid2 == pid3
async def test_close_session_and_try_to_use_client_raises_error(self, stdio_script):
client = Client(transport=PythonStdioTransport(script_path=stdio_script))
assert client.transport.keep_alive is True
async with client:
await client.close()
with pytest.raises(RuntimeError, match="Client is not connected"):
await client.call_tool("pid")