Skip to content

Commit 7b87964

Browse files
authored
Merge pull request #623 from jlowin/infer-typing
Improve type inference from client transport
2 parents f61d1cc + a3450b4 commit 7b87964

File tree

3 files changed

+99
-9
lines changed

3 files changed

+99
-9
lines changed

src/fastmcp/client/client.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import datetime
22
from contextlib import AsyncExitStack, asynccontextmanager
33
from pathlib import Path
4-
from typing import Any, cast
4+
from typing import Any, Generic, cast, overload
55

66
import anyio
77
import mcp.types
@@ -28,7 +28,18 @@
2828
from fastmcp.utilities.exceptions import get_catch_handlers
2929
from fastmcp.utilities.mcp_config import MCPConfig
3030

31-
from .transports import ClientTransport, SessionKwargs, infer_transport
31+
from .transports import (
32+
ClientTransportT,
33+
FastMCP1Server,
34+
FastMCPTransport,
35+
MCPConfigTransport,
36+
NodeStdioTransport,
37+
PythonStdioTransport,
38+
SessionKwargs,
39+
SSETransport,
40+
StreamableHttpTransport,
41+
infer_transport,
42+
)
3243

3344
__all__ = [
3445
"Client",
@@ -41,7 +52,7 @@
4152
]
4253

4354

44-
class Client:
55+
class Client(Generic[ClientTransportT]):
4556
"""
4657
MCP client that delegates connection management to a Transport instance.
4758
@@ -78,9 +89,45 @@ class Client:
7889
```
7990
"""
8091

92+
@overload
93+
def __new__(
94+
cls,
95+
transport: ClientTransportT,
96+
**kwargs: Any,
97+
) -> "Client[ClientTransportT]": ...
98+
99+
@overload
100+
def __new__(
101+
cls, transport: AnyUrl, **kwargs
102+
) -> "Client[SSETransport|StreamableHttpTransport]": ...
103+
104+
@overload
105+
def __new__(
106+
cls, transport: FastMCP | FastMCP1Server, **kwargs
107+
) -> "Client[FastMCPTransport]": ...
108+
109+
@overload
110+
def __new__(
111+
cls, transport: Path, **kwargs
112+
) -> "Client[PythonStdioTransport|NodeStdioTransport]": ...
113+
114+
@overload
115+
def __new__(
116+
cls, transport: MCPConfig | dict[str, Any], **kwargs
117+
) -> "Client[MCPConfigTransport]": ...
118+
119+
@overload
120+
def __new__(
121+
cls, transport: str, **kwargs
122+
) -> "Client[PythonStdioTransport|NodeStdioTransport|SSETransport|StreamableHttpTransport]": ...
123+
124+
def __new__(cls, transport, **kwargs) -> "Client":
125+
instance = super().__new__(cls)
126+
return instance
127+
81128
def __init__(
82129
self,
83-
transport: ClientTransport
130+
transport: ClientTransportT
84131
| FastMCP
85132
| AnyUrl
86133
| Path
@@ -96,7 +143,7 @@ def __init__(
96143
timeout: datetime.timedelta | float | int | None = None,
97144
init_timeout: datetime.timedelta | float | int | None = None,
98145
):
99-
self.transport = infer_transport(transport)
146+
self.transport = cast(ClientTransportT, infer_transport(transport))
100147
self._session: ClientSession | None = None
101148
self._exit_stack: AsyncExitStack | None = None
102149
self._nesting_counter: int = 0

src/fastmcp/client/transports.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sys
77
from collections.abc import AsyncIterator
88
from pathlib import Path
9-
from typing import TYPE_CHECKING, Any, TypedDict, cast
9+
from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, cast, overload
1010

1111
from mcp import ClientSession, StdioServerParameters
1212
from mcp.client.session import (
@@ -35,6 +35,9 @@
3535

3636
logger = get_logger(__name__)
3737

38+
# TypeVar for preserving specific ClientTransport subclass types
39+
ClientTransportT = TypeVar("ClientTransportT", bound="ClientTransport")
40+
3841

3942
class SessionKwargs(TypedDict, total=False):
4043
"""Keyword arguments for the MCP ClientSession constructor."""
@@ -575,6 +578,44 @@ def __repr__(self) -> str:
575578
return f"<MCPConfig(config='{self.config}')>"
576579

577580

581+
@overload
582+
def infer_transport(transport: ClientTransportT) -> ClientTransportT: ...
583+
584+
585+
@overload
586+
def infer_transport(transport: FastMCPServer) -> FastMCPTransport: ...
587+
588+
589+
@overload
590+
def infer_transport(transport: FastMCP1Server) -> FastMCPTransport: ...
591+
592+
593+
@overload
594+
def infer_transport(transport: MCPConfig) -> MCPConfigTransport: ...
595+
596+
597+
@overload
598+
def infer_transport(transport: dict[str, Any]) -> MCPConfigTransport: ...
599+
600+
601+
@overload
602+
def infer_transport(
603+
transport: AnyUrl,
604+
) -> SSETransport | StreamableHttpTransport: ...
605+
606+
607+
@overload
608+
def infer_transport(
609+
transport: str,
610+
) -> (
611+
PythonStdioTransport | NodeStdioTransport | SSETransport | StreamableHttpTransport
612+
): ...
613+
614+
615+
@overload
616+
def infer_transport(transport: Path) -> PythonStdioTransport | NodeStdioTransport: ...
617+
618+
578619
def infer_transport(
579620
transport: ClientTransport
580621
| FastMCPServer

src/fastmcp/server/server.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262

6363
if TYPE_CHECKING:
6464
from fastmcp.client import Client
65-
from fastmcp.client.transports import ClientTransport
65+
from fastmcp.client.transports import ClientTransport, ClientTransportT
6666
from fastmcp.server.openapi import ComponentFn as OpenAPIComponentFn
6767
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap
6868
from fastmcp.server.openapi import RouteMapFn as OpenAPIRouteMapFn
@@ -1288,7 +1288,7 @@ def from_fastapi(
12881288
@classmethod
12891289
def as_proxy(
12901290
cls,
1291-
backend: Client
1291+
backend: Client[ClientTransportT]
12921292
| ClientTransport
12931293
| FastMCP[Any]
12941294
| AnyUrl
@@ -1316,7 +1316,9 @@ def as_proxy(
13161316
return FastMCPProxy(client=client, **settings)
13171317

13181318
@classmethod
1319-
def from_client(cls, client: Client, **settings: Any) -> FastMCPProxy:
1319+
def from_client(
1320+
cls, client: Client[ClientTransportT], **settings: Any
1321+
) -> FastMCPProxy:
13201322
"""
13211323
Create a FastMCP proxy server from a FastMCP client.
13221324
"""

0 commit comments

Comments
 (0)