44
55import logging
66from collections .abc import AsyncGenerator
7- from contextlib import asynccontextmanager
7+ from contextlib import AsyncExitStack , asynccontextmanager
88from typing import Any
99
1010from anyio .streams .memory import MemoryObjectReceiveStream , MemoryObjectSendStream
1919 MessageHandlerFnT ,
2020 SamplingFnT ,
2121)
22+ from mcp .client .transports .memory import create_in_memory_transport
2223from mcp .server import Server
2324from mcp .server .fastmcp import FastMCP
2425from mcp .shared .message import SessionMessage
@@ -35,14 +36,14 @@ class Client:
3536 using different transports (in-memory, stdio, HTTP, etc.) and exposes
3637 all ClientSession functionality with simpler lifecycle management.
3738
38- Example with in-memory transport:
39+ Example with in-memory transport (for testing) :
3940 server = FastMCP("test")
4041
4142 async with Client.from_server(server) as client:
4243 tools = await client.list_tools()
4344 result = await client.call_tool("my_tool", {"arg": "value"})
4445
45- Example with custom transport :
46+ Example with custom streams :
4647 async with Client(read_stream, write_stream) as client:
4748 await client.initialize()
4849 # Use client...
@@ -74,7 +75,18 @@ def __init__(
7475 client_info: Client implementation info to send to server
7576 elicitation_callback: Callback for handling elicitation requests
7677 """
77- raise NotImplementedError ("Client.__init__ is not yet implemented" )
78+ self ._read_stream = read_stream
79+ self ._write_stream = write_stream
80+ self ._read_timeout_seconds = read_timeout_seconds
81+ self ._sampling_callback = sampling_callback
82+ self ._list_roots_callback = list_roots_callback
83+ self ._logging_callback = logging_callback
84+ self ._message_handler = message_handler
85+ self ._client_info = client_info
86+ self ._elicitation_callback = elicitation_callback
87+
88+ self ._session : ClientSession | None = None
89+ self ._exit_stack : AsyncExitStack | None = None
7890
7991 @classmethod
8092 @asynccontextmanager
@@ -120,16 +132,43 @@ def my_tool(arg: str) -> str:
120132 async with Client.from_server(server) as client:
121133 result = await client.call_tool("my_tool", {"arg": "value"})
122134 """
123- # Silence unused parameter warnings in stub
124- _ = (server , read_timeout_seconds , sampling_callback , list_roots_callback ,
125- logging_callback , message_handler , client_info , raise_exceptions , elicitation_callback )
126- # Stub: yield fake value, actual implementation will provide real client
127- yield None # type: ignore[misc]
128- raise NotImplementedError ("Client.from_server is not yet implemented" )
135+ async with create_in_memory_transport (
136+ server , raise_exceptions = raise_exceptions
137+ ) as (read_stream , write_stream ):
138+ client = cls (
139+ read_stream = read_stream ,
140+ write_stream = write_stream ,
141+ read_timeout_seconds = read_timeout_seconds ,
142+ sampling_callback = sampling_callback ,
143+ list_roots_callback = list_roots_callback ,
144+ logging_callback = logging_callback ,
145+ message_handler = message_handler ,
146+ client_info = client_info ,
147+ elicitation_callback = elicitation_callback ,
148+ )
149+ async with client :
150+ await client .initialize ()
151+ yield client
129152
130153 async def __aenter__ (self ) -> Client :
131154 """Enter the async context manager."""
132- raise NotImplementedError ("Client.__aenter__ is not yet implemented" )
155+ self ._exit_stack = AsyncExitStack ()
156+ await self ._exit_stack .__aenter__ ()
157+
158+ self ._session = await self ._exit_stack .enter_async_context (
159+ ClientSession (
160+ read_stream = self ._read_stream ,
161+ write_stream = self ._write_stream ,
162+ read_timeout_seconds = self ._read_timeout_seconds ,
163+ sampling_callback = self ._sampling_callback ,
164+ list_roots_callback = self ._list_roots_callback ,
165+ logging_callback = self ._logging_callback ,
166+ message_handler = self ._message_handler ,
167+ client_info = self ._client_info ,
168+ elicitation_callback = self ._elicitation_callback ,
169+ )
170+ )
171+ return self
133172
134173 async def __aexit__ (
135174 self ,
@@ -138,7 +177,23 @@ async def __aexit__(
138177 exc_tb : Any ,
139178 ) -> None :
140179 """Exit the async context manager."""
141- raise NotImplementedError ("Client.__aexit__ is not yet implemented" )
180+ if self ._exit_stack :
181+ await self ._exit_stack .__aexit__ (exc_type , exc_val , exc_tb )
182+ self ._session = None
183+
184+ @property
185+ def session (self ) -> ClientSession :
186+ """
187+ Get the underlying ClientSession.
188+
189+ This provides access to the full ClientSession API for advanced use cases.
190+
191+ Raises:
192+ RuntimeError: If accessed before entering the context manager.
193+ """
194+ if self ._session is None :
195+ raise RuntimeError ("Client must be used within an async context manager" )
196+ return self ._session
142197
143198 async def initialize (self ) -> types .InitializeResult :
144199 """
@@ -149,7 +204,7 @@ async def initialize(self) -> types.InitializeResult:
149204 Returns:
150205 The initialization result from the server
151206 """
152- raise NotImplementedError ( "Client. initialize is not yet implemented" )
207+ return await self . session . initialize ( )
153208
154209 def get_server_capabilities (self ) -> types .ServerCapabilities | None :
155210 """
@@ -158,11 +213,11 @@ def get_server_capabilities(self) -> types.ServerCapabilities | None:
158213 Returns:
159214 The server capabilities, or None if not yet initialized
160215 """
161- raise NotImplementedError ( "Client. get_server_capabilities is not yet implemented" )
216+ return self . session . get_server_capabilities ( )
162217
163218 async def send_ping (self ) -> types .EmptyResult :
164219 """Send a ping request to the server."""
165- raise NotImplementedError ( "Client. send_ping is not yet implemented" )
220+ return await self . session . send_ping ( )
166221
167222 async def send_progress_notification (
168223 self ,
@@ -172,11 +227,16 @@ async def send_progress_notification(
172227 message : str | None = None ,
173228 ) -> None :
174229 """Send a progress notification to the server."""
175- raise NotImplementedError ("Client.send_progress_notification is not yet implemented" )
230+ await self .session .send_progress_notification (
231+ progress_token = progress_token ,
232+ progress = progress ,
233+ total = total ,
234+ message = message ,
235+ )
176236
177237 async def set_logging_level (self , level : types .LoggingLevel ) -> types .EmptyResult :
178238 """Set the logging level on the server."""
179- raise NotImplementedError ( "Client. set_logging_level is not yet implemented" )
239+ return await self . session . set_logging_level ( level )
180240
181241 async def list_resources (
182242 self ,
@@ -194,7 +254,12 @@ async def list_resources(
194254 Returns:
195255 List of available resources
196256 """
197- raise NotImplementedError ("Client.list_resources is not yet implemented" )
257+ if params is not None :
258+ return await self .session .list_resources (params = params )
259+ elif cursor is not None :
260+ return await self .session .list_resources (cursor )
261+ else :
262+ return await self .session .list_resources ()
198263
199264 async def list_resource_templates (
200265 self ,
@@ -212,7 +277,12 @@ async def list_resource_templates(
212277 Returns:
213278 List of available resource templates
214279 """
215- raise NotImplementedError ("Client.list_resource_templates is not yet implemented" )
280+ if params is not None :
281+ return await self .session .list_resource_templates (params = params )
282+ elif cursor is not None :
283+ return await self .session .list_resource_templates (cursor )
284+ else :
285+ return await self .session .list_resource_templates ()
216286
217287 async def read_resource (self , uri : AnyUrl ) -> types .ReadResourceResult :
218288 """
@@ -224,15 +294,15 @@ async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult:
224294 Returns:
225295 The resource content
226296 """
227- raise NotImplementedError ( "Client. read_resource is not yet implemented" )
297+ return await self . session . read_resource ( uri )
228298
229299 async def subscribe_resource (self , uri : AnyUrl ) -> types .EmptyResult :
230300 """Subscribe to resource updates."""
231- raise NotImplementedError ( "Client. subscribe_resource is not yet implemented" )
301+ return await self . session . subscribe_resource ( uri )
232302
233303 async def unsubscribe_resource (self , uri : AnyUrl ) -> types .EmptyResult :
234304 """Unsubscribe from resource updates."""
235- raise NotImplementedError ( "Client. unsubscribe_resource is not yet implemented" )
305+ return await self . session . unsubscribe_resource ( uri )
236306
237307 async def call_tool (
238308 self ,
@@ -256,7 +326,13 @@ async def call_tool(
256326 Returns:
257327 The tool result
258328 """
259- raise NotImplementedError ("Client.call_tool is not yet implemented" )
329+ return await self .session .call_tool (
330+ name = name ,
331+ arguments = arguments ,
332+ read_timeout_seconds = read_timeout_seconds ,
333+ progress_callback = progress_callback ,
334+ meta = meta ,
335+ )
260336
261337 async def list_prompts (
262338 self ,
@@ -274,7 +350,12 @@ async def list_prompts(
274350 Returns:
275351 List of available prompts
276352 """
277- raise NotImplementedError ("Client.list_prompts is not yet implemented" )
353+ if params is not None :
354+ return await self .session .list_prompts (params = params )
355+ elif cursor is not None :
356+ return await self .session .list_prompts (cursor )
357+ else :
358+ return await self .session .list_prompts ()
278359
279360 async def get_prompt (
280361 self ,
@@ -291,7 +372,7 @@ async def get_prompt(
291372 Returns:
292373 The prompt content
293374 """
294- raise NotImplementedError ( "Client. get_prompt is not yet implemented" )
375+ return await self . session . get_prompt ( name = name , arguments = arguments )
295376
296377 async def complete (
297378 self ,
@@ -310,7 +391,11 @@ async def complete(
310391 Returns:
311392 Completion suggestions
312393 """
313- raise NotImplementedError ("Client.complete is not yet implemented" )
394+ return await self .session .complete (
395+ ref = ref ,
396+ argument = argument ,
397+ context_arguments = context_arguments ,
398+ )
314399
315400 async def list_tools (
316401 self ,
@@ -328,17 +413,13 @@ async def list_tools(
328413 Returns:
329414 List of available tools
330415 """
331- raise NotImplementedError ("Client.list_tools is not yet implemented" )
416+ if params is not None :
417+ return await self .session .list_tools (params = params )
418+ elif cursor is not None :
419+ return await self .session .list_tools (cursor )
420+ else :
421+ return await self .session .list_tools ()
332422
333423 async def send_roots_list_changed (self ) -> None :
334424 """Send a notification that the roots list has changed."""
335- raise NotImplementedError ("Client.send_roots_list_changed is not yet implemented" )
336-
337- @property
338- def session (self ) -> ClientSession :
339- """
340- Get the underlying ClientSession.
341-
342- This provides access to the full ClientSession API for advanced use cases.
343- """
344- raise NotImplementedError ("Client.session is not yet implemented" )
425+ await self .session .send_roots_list_changed ()
0 commit comments