99from pydantic import AnyUrl
1010
1111import mcp .types as types
12- from mcp .client ._memory import InMemoryTransport
1312from mcp .client .session import (
1413 ClientSession ,
1514 ElicitationFnT ,
1817 MessageHandlerFnT ,
1918 SamplingFnT ,
2019)
20+ from mcp .client .transports import HttpTransport , InMemoryTransport , Transport
2121from mcp .server import Server
2222from mcp .server .fastmcp import FastMCP
2323from mcp .shared .session import ProgressFnT
2424
2525logger = logging .getLogger (__name__ )
2626
27+ # Type alias for all accepted target types
28+ ClientTarget = Server [Any ] | FastMCP | Transport | str
29+
30+
31+ def _infer_transport (
32+ target : ClientTarget ,
33+ * ,
34+ raise_exceptions : bool = False ,
35+ ) -> Transport :
36+ """Infer the appropriate transport from the target type.
37+
38+ Args:
39+ target: The target to connect to. Can be:
40+ - Server or FastMCP instance: Uses InMemoryTransport
41+ - Transport instance: Uses the transport directly
42+ - str (URL): Uses HttpTransport (Streamable HTTP)
43+ raise_exceptions: For InMemoryTransport, whether to raise exceptions
44+ from the server. Ignored for other transport types.
45+
46+ Returns:
47+ A Transport instance ready to connect.
48+
49+ Raises:
50+ TypeError: If the target type is not recognized.
51+ """
52+ # Already a transport - use directly
53+ if isinstance (target , Transport ):
54+ return target
55+
56+ # Server or FastMCP - use in-memory transport for testing
57+ if isinstance (target , Server | FastMCP ):
58+ return InMemoryTransport (target , raise_exceptions = raise_exceptions )
59+
60+ # URL string - use Streamable HTTP transport (modern standard)
61+ # Note: After type narrowing above, target is str here
62+ return HttpTransport (target )
63+
2764
2865class Client :
2966 """A high-level MCP client for connecting to MCP servers.
3067
31- Currently supports in-memory transport for testing. Pass a Server or
32- FastMCP instance directly to the constructor.
68+ Supports multiple transport types:
69+ - In-memory: Pass a Server or FastMCP instance directly (for testing)
70+ - HTTP: Pass a URL string or HttpTransport instance
71+ - SSE: Pass an SSETransport instance (legacy)
3372
34- Example :
73+ Examples :
3574 ```python
75+ # In-memory testing (recommended for unit tests)
3676 from mcp.client import Client
3777 from mcp.server.fastmcp import FastMCP
3878
@@ -44,21 +84,34 @@ def add(a: int, b: int) -> int:
4484
4585 async with Client(server) as client:
4686 result = await client.call_tool("add", {"a": 1, "b": 2})
87+
88+ # HTTP connection via URL string
89+ async with Client("http://localhost:8000/mcp") as client:
90+ result = await client.call_tool("my_tool", {...})
91+
92+ # HTTP connection with custom headers
93+ from mcp.client.transports import HttpTransport
94+
95+ transport = HttpTransport(
96+ "http://localhost:8000/mcp",
97+ headers={"Authorization": "Bearer token"},
98+ )
99+ async with Client(transport) as client:
100+ result = await client.call_tool("my_tool", {...})
101+
102+ # Legacy SSE connection
103+ from mcp.client.transports import SSETransport
104+
105+ async with Client(SSETransport("http://localhost:8000/sse")) as client:
106+ result = await client.call_tool("my_tool", {...})
47107 ```
48108 """
49109
50- # TODO(felixweinberger): Expand to support all transport types (like FastMCP 2):
51- # - Add ClientTransport base class with connect_session() method
52- # - Add StreamableHttpTransport, SSETransport, StdioTransport
53- # - Add infer_transport() to auto-detect transport from input type
54- # - Accept URL strings, Path objects, config dicts in constructor
55- # - Add auth support (OAuth, bearer tokens)
56-
57110 def __init__ (
58111 self ,
59- server : Server [ Any ] | FastMCP ,
112+ target : ClientTarget ,
60113 * ,
61- # TODO(Marcelo): When do `raise_exceptions=True` actually raises ?
114+ # TODO(Marcelo): When does `raise_exceptions=True` actually raise ?
62115 raise_exceptions : bool = False ,
63116 read_timeout_seconds : float | None = None ,
64117 sampling_callback : SamplingFnT | None = None ,
@@ -68,20 +121,24 @@ def __init__(
68121 client_info : types .Implementation | None = None ,
69122 elicitation_callback : ElicitationFnT | None = None ,
70123 ) -> None :
71- """Initialize the client with a server .
124+ """Initialize the client.
72125
73126 Args:
74- server: The MCP server to connect to (Server or FastMCP instance)
75- raise_exceptions: Whether to raise exceptions from the server
76- read_timeout_seconds: Timeout for read operations
77- sampling_callback: Callback for handling sampling requests
78- list_roots_callback: Callback for handling list roots requests
79- logging_callback: Callback for handling logging notifications
80- message_handler: Callback for handling raw messages
81- client_info: Client implementation info to send to server
82- elicitation_callback: Callback for handling elicitation requests
127+ target: The target to connect to. Can be:
128+ - Server or FastMCP instance: Uses in-memory transport (for testing)
129+ - Transport instance: Uses the transport directly
130+ - str (URL): Uses HTTP transport (Streamable HTTP protocol)
131+ raise_exceptions: For in-memory transport, whether to raise exceptions
132+ from the server. Ignored for other transport types.
133+ read_timeout_seconds: Timeout for read operations.
134+ sampling_callback: Callback for handling sampling requests.
135+ list_roots_callback: Callback for handling list roots requests.
136+ logging_callback: Callback for handling logging notifications.
137+ message_handler: Callback for handling raw messages.
138+ client_info: Client implementation info to send to server.
139+ elicitation_callback: Callback for handling elicitation requests.
83140 """
84- self ._server = server
141+ self ._target = target
85142 self ._raise_exceptions = raise_exceptions
86143 self ._read_timeout_seconds = read_timeout_seconds
87144 self ._sampling_callback = sampling_callback
@@ -100,8 +157,8 @@ async def __aenter__(self) -> Client:
100157 raise RuntimeError ("Client is already entered; cannot reenter" )
101158
102159 async with AsyncExitStack () as exit_stack :
103- # Create transport and connect
104- transport = InMemoryTransport (self ._server , raise_exceptions = self ._raise_exceptions )
160+ # Infer and connect transport
161+ transport = _infer_transport (self ._target , raise_exceptions = self ._raise_exceptions )
105162 read_stream , write_stream = await exit_stack .enter_async_context (transport .connect ())
106163
107164 # Create session
0 commit comments