MCPcopy
hub / github.com/modelcontextprotocol/python-sdk / ClientSession

Class ClientSession

src/mcp/client/session.py:183–867  ·  view source on GitHub ↗

Client half of an MCP connection, running on a `Dispatcher`. Construct it over a transport's stream pair (or pass a pre-built `dispatcher=`), enter as an async context manager, then call `initialize()`. The dispatcher owns the receive loop and request correlation; this class owns th

Source from the content-addressed store, hash-verified

181
182
183class ClientSession:
184 """Client half of an MCP connection, running on a `Dispatcher`.
185
186 Construct it over a transport's stream pair (or pass a pre-built
187 `dispatcher=`), enter as an async context manager, then call
188 `initialize()`. The dispatcher owns the receive loop and request
189 correlation; this class owns the typed MCP layer and the constructor
190 callbacks. Transport `Exception` items reach `message_handler` only when
191 the session builds its own dispatcher from a stream pair.
192 """
193
194 def __init__(
195 self,
196 read_stream: ReadStream[SessionMessage | Exception] | None = None,
197 write_stream: WriteStream[SessionMessage] | None = None,
198 read_timeout_seconds: float | None = None,
199 sampling_callback: SamplingFnT | None = None,
200 elicitation_callback: ElicitationFnT | None = None,
201 list_roots_callback: ListRootsFnT | None = None,
202 logging_callback: LoggingFnT | None = None,
203 message_handler: MessageHandlerFnT | None = None,
204 client_info: types.Implementation | None = None,
205 *,
206 sampling_capabilities: types.SamplingCapability | None = None,
207 dispatcher: Dispatcher[Any] | None = None,
208 ) -> None:
209 self._session_read_timeout_seconds = read_timeout_seconds
210 self._client_info = client_info or DEFAULT_CLIENT_INFO
211 self._sampling_callback = sampling_callback or _default_sampling_callback
212 self._sampling_capabilities = sampling_capabilities
213 self._elicitation_callback = elicitation_callback or _default_elicitation_callback
214 self._list_roots_callback = list_roots_callback or _default_list_roots_callback
215 self._logging_callback = logging_callback or _default_logging_callback
216 self._message_handler = message_handler or _default_message_handler
217 self._tool_output_schemas: dict[str, dict[str, Any] | None] = {}
218 self._initialize_result: types.InitializeResult | None = None
219 self._discover_result: types.DiscoverResult | None = None
220 self._negotiated_version: str | None = None
221 self._stamp: Callable[[dict[str, Any], CallOptions], None] = _preconnect_stamp
222 self._task_group: anyio.abc.TaskGroup | None = None
223 if dispatcher is not None:
224 if read_stream is not None or write_stream is not None:
225 raise ValueError("pass read_stream/write_stream or dispatcher, not both")
226 self._dispatcher: Dispatcher[Any] = dispatcher
227 if isinstance(dispatcher, JSONRPCDispatcher) and dispatcher.on_stream_exception is None:
228 # Route transport-level Exception items into message_handler — only
229 # stream-backed dispatchers carry these; DirectDispatcher has none.
230 # Don't clobber a caller-supplied hook.
231 # TODO(L78): this leaves a bound-method ref on the dispatcher after the
232 # session exits (memory pin) and a second wrap of the same dispatcher would
233 # skip install. The Transport-as-Dispatcher rework (L77) removes this seam.
234 dispatcher.on_stream_exception = self._on_stream_exception
235 else:
236 if read_stream is None or write_stream is None:
237 raise ValueError("read_stream and write_stream are required when no dispatcher is given")
238 # Built eagerly so notifications can be sent before entering the context manager.
239 self._dispatcher = JSONRPCDispatcher(
240 read_stream, write_stream, on_stream_exception=self._on_stream_exception

Calls

no outgoing calls