diff --git a/src/fastmcp/client/client.py b/src/fastmcp/client/client.py index 7e8c106e8..853f68c79 100644 --- a/src/fastmcp/client/client.py +++ b/src/fastmcp/client/client.py @@ -9,6 +9,7 @@ from exceptiongroup import catch from mcp import ClientSession from pydantic import AnyUrl +import fastmcp from fastmcp.client.logging import ( LogHandler, MessageHandler, @@ -45,8 +46,8 @@ class Client: MCP client that delegates connection management to a Transport instance. The Client class is responsible for MCP protocol logic, while the Transport - handles connection establishment and management. Client provides methods - for working with resources, prompts, tools and other MCP capabilities. + handles connection establishment and management. Client provides methods for + working with resources, prompts, tools and other MCP capabilities. Args: transport: Connection source specification, which can be: @@ -62,20 +63,18 @@ class Client: message_handler: Optional handler for protocol messages progress_handler: Optional handler for progress notifications timeout: Optional timeout for requests (seconds or timedelta) - init_timeout: Optional timeout for initial connection (seconds or - timedelta) + init_timeout: Optional timeout for initial connection (seconds or timedelta). + Set to 0 to disable. If None, uses the value in the FastMCP global settings. Examples: - ```python - # Connect to FastMCP server - client = Client("http://localhost:8080") + ```python # Connect to FastMCP server client = + Client("http://localhost:8080") async with client: - # List available resources - resources = await client.list_resources() + # List available resources resources = await client.list_resources() - # Call a tool - result = await client.call_tool("my_tool", {"param": "value"}) + # Call a tool result = await client.call_tool("my_tool", {"param": + "value"}) ``` """ @@ -95,7 +94,7 @@ class Client: message_handler: MessageHandler | None = None, progress_handler: ProgressHandler | None = None, timeout: datetime.timedelta | float | int | None = None, - init_timeout: datetime.timedelta | float | int = 1, + init_timeout: datetime.timedelta | float | int | None = None, ): self.transport = infer_transport(transport) self._session: ClientSession | None = None @@ -114,14 +113,16 @@ class Client: if isinstance(timeout, int | float): timeout = datetime.timedelta(seconds=timeout) - if isinstance(init_timeout, int): - self._init_timeout = float(init_timeout) - elif isinstance(init_timeout, datetime.timedelta): - self._init_timeout = float(init_timeout.total_seconds()) - elif isinstance(init_timeout, float): - self._init_timeout = init_timeout + # handle init handshake timeout + if init_timeout is None: + init_timeout = fastmcp.settings.settings.client_init_timeout + if isinstance(init_timeout, datetime.timedelta): + init_timeout = init_timeout.total_seconds() + elif not init_timeout: + init_timeout = None else: - raise ValueError("init_timeout must be int, float or datetime.timedelta") + init_timeout = float(init_timeout) + self._init_timeout = init_timeout self._session_kwargs: SessionKwargs = { "sampling_callback": None, diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py index 662b0db6f..c872ecb2a 100644 --- a/src/fastmcp/settings.py +++ b/src/fastmcp/settings.py @@ -87,6 +87,13 @@ class Settings(BaseSettings): ), ] = False + client_init_timeout: Annotated[ + float | None, + Field( + description="The timeout for the client's initialization handshake, in seconds. Set to None or 0 to disable.", + ), + ] = None + @model_validator(mode="after") def setup_logging(self) -> Self: """Finalize the settings."""