Skip to content

Commit 8b04353

Browse files
committed
update migration.md
1 parent 9f0fd66 commit 8b04353

File tree

1 file changed

+96
-108
lines changed

1 file changed

+96
-108
lines changed

docs/migration.md

Lines changed: 96 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,89 @@ server.experimental.enable_tasks(on_get_task=custom_get_task)
770770

771771
The session hierarchy has been refactored to support pluggable transport implementations. This introduces several breaking changes:
772772

773+
#### New `AbstractBaseSession` Protocol
774+
775+
A new runtime-checkable Protocol, AbstractBaseSession, establishes a transport-agnostic contract for all MCP sessions. It ensures that client and server sessions share a consistent communication interface regardless of the transport used.
776+
777+
**Key characteristics of `AbstractBaseSession`:**
778+
779+
To maintain a clean architectural boundary, AbstractBaseSession is a pure interface—it defines what methods must exist but does not manage how they work.
780+
781+
* **No State Management**: The protocol does not handle internal machinery like task groups, response streams, or buffers.
782+
783+
* **Implementation Ownership**: The concrete class is now fully responsible for managing its own state and lifecycle for how it sends and receives data.
784+
785+
* **No Inheritance Needed**: As a structural protocol, you no longer need to call super().**init**() or inherit from a base class to satisfy the contract.
786+
787+
**Motivation: Interface vs. Implementation**
788+
Previously, all custom sessions were required to inherit from BaseSession, which locked them into a specific architecture involving memory streams and JSON-RPC message routing, but now users have the flexibility to implement their own transport logic.
789+
790+
BaseSession (Implementation): Remains available for transports that follow the standard pattern of reading and writing JSON-RPC messages over streams. You can continue to inherit from this if you want the SDK to handle message linking and routing for you.
791+
792+
AbstractBaseSession (Interface): A new stateless protocol for transports that do not use standard streams or JSON-RPC. It defines the "what" (method signatures) without enforcing the "how" (internal machinery).
793+
794+
**Before:**
795+
796+
```python
797+
from mcp.shared.session import BaseSession
798+
799+
# Locked into the BaseSession implementation details
800+
class MyCustomSession(BaseSession[...]):
801+
def __init__(self, read_stream, write_stream):
802+
# Mandatory: must use streams and JSON-RPC machinery
803+
super().__init__(read_stream, write_stream)
804+
```
805+
806+
**After:**
807+
808+
```python
809+
# OPTION A: Continue using BaseSession (for stream-based JSON-RPC)
810+
class MyStreamSession(BaseSession):
811+
...
812+
813+
# OPTION B: Use AbstractBaseSession (for non-stream/custom transports)
814+
class MyDirectApiSession: # Satisfies AbstractBaseSession protocol
815+
def __init__(self):
816+
# No streams or super().__init__() required.
817+
# Manage your own custom transport logic here.
818+
self._client = CustomTransportClient()
819+
820+
async def send_request(self, ...):
821+
return await self._client.execute(...)
822+
```
823+
824+
#### Introduction of the `BaseClientSession` Protocol
825+
826+
A new runtime-checkable Protocol, BaseClientSession, has been introduced to establish a common interface for all MCP client sessions. This protocol defines the essential methods—such as `send_request`, `send_notification`, and `initialize`, etc. — that a session must implement to be compatible with the SDK's client utilities.
827+
828+
The primary goal of this protocol is to ensure that the high-level session logic remains consistent irrespective of the underlying transport. Custom session implementations can now satisfy the SDK's requirements simply by implementing the defined methods. No explicit inheritance is required.
829+
830+
```python
831+
from mcp.client.base_client_session import BaseClientSession
832+
833+
class MyCustomTransportSession:
834+
"""
835+
A custom session implementation. It doesn't need to inherit from
836+
BaseClientSession to be compatible.
837+
"""
838+
async def initialize(self) -> InitializeResult:
839+
...
840+
841+
async def send_request(self, ...) -> Any:
842+
...
843+
844+
# Implementing these methods makes this class a 'BaseClientSession'
845+
```
846+
847+
Because the protocol is `@runtime_checkable`, you can verify that any session object adheres to the required structure using standard Python checks:
848+
849+
```python
850+
def start_client(session: BaseClientSession):
851+
# This works for any object implementing the protocol
852+
if not isinstance(session, BaseClientSession):
853+
raise TypeError("Session must implement the BaseClientSession protocol")
854+
```
855+
773856
#### `ClientRequestContext` type changed
774857

775858
`ClientRequestContext` is now `RequestContext[BaseClientSession]` instead of `RequestContext[ClientSession]`. This means callbacks receive the more general `BaseClientSession` type, which may not have all methods available on `ClientSession`.
@@ -829,130 +912,35 @@ my_sampling_typed: SamplingFnT[ClientSession] = my_sampling
829912
session = ClientSession(..., sampling_callback=my_sampling_typed)
830913
```
831914

832-
#### `SessionT` renamed to `SessionT_co`
833-
834-
In `mcp.shared._context` and `mcp.shared.progress`, the `SessionT` TypeVar has been renamed to `SessionT_co` to follow naming conventions for covariant type variables.
835-
836-
**Before:**
837-
838-
```python
839-
from mcp.shared._context import SessionT
840-
```
841-
842-
**After:**
843-
844-
```python
845-
from mcp.shared._context import SessionT_co
846-
```
847-
848-
#### New `AbstractBaseSession` structural interface
849-
850-
The session hierarchy now uses a new **runtime-checkable Protocol** called `AbstractBaseSession` to define the shared contract for all MCP sessions. This protocol enables structural subtyping, allowing different transport implementations to be used interchangeably without requiring rigid inheritance.
851-
852-
Key characteristics of `AbstractBaseSession`:
853-
854-
1. **Pure Interface**: It is a structural protocol with no implementation state or `__init__` method.
855-
2. **Simplified Type Parameters**: It takes two parameters: `AbstractBaseSession[SendRequestT_contra, SendNotificationT_contra]`. Contravariant variance is used for these parameters to ensure that sessions can be used safely in generic contexts (like `RequestContext`).
856-
3. **BaseSession Implementation**: The concrete implementation logic (state management, response routing) is provided by the `BaseSession` class, which satisfies the protocol.
857-
858-
**Before:**
859-
860-
```python
861-
from mcp.shared.session import AbstractBaseSession
862-
863-
class MySession(AbstractBaseSession[MyMessage, ...]):
864-
def __init__(self):
865-
super().__init__() # Would set up _response_streams, _task_group
866-
```
867-
868-
**After:**
915+
#### Internal TypeVars Renamed and Variance Updated
869916

870-
```python
871-
from mcp.shared.session import AbstractBaseSession
917+
To support structural subtyping and ensure type safety across the new Protocol hierarchy, several internal TypeVariables have been updated with explicit variance (covariant or contravariant). These have been renamed to follow PEP 484 naming conventions.
872918

873-
class MySession(AbstractBaseSession[...]):
874-
def __init__(self):
875-
# Manage your own state - no super().__init__() to call
876-
self._my_state = {}
877-
```
878-
879-
#### `SendRequestT` renamed to `SendRequestT_contra`
880-
881-
The `SendRequestT` TypeVar is now defined as **contravariant** to support its use in the `AbstractBaseSession` Protocol and renamed accordingly to follow naming conventions for contravariant type variables.
919+
| Original Name | Updated Name | Variance | Purpose |
920+
| --- | --- | --- | --- |
921+
| SessionT | SessionT_co | Covariant | Allows specialized sessions to be used where a base session is expected. |
922+
| SendRequestT | SendRequestT_contra | Contravariant | Ensures request types can be safely handled by generic handlers. |
923+
| SendNotificationT | SendNotificationT_contra | Contravariant | Ensures notification types are handled safely across the hierarchy. |
924+
| ReceiveResultT | ReceiveResultT_co | Covariant | Handles successful response models safely. |
882925

883926
**Before:**
884927

885928
```python
929+
SessionT_co = TypeVar("SessionT_co", bound="AbstractBaseSession[Any, Any]", covariant=True)
886930
SendRequestT = TypeVar("SendRequestT", ClientRequest, ServerRequest)
887-
```
888-
889-
**After:**
890-
891-
```python
892-
SendRequestT_contra = TypeVar("SendRequestT_contra", ClientRequest, ServerRequest, contravariant=True)
893-
```
894-
895-
#### `SendNotificationT` renamed to `SendNotificationT_contra`
896-
897-
The `SendNotificationT` TypeVar is now defined as **contravariant** to support its use in the `AbstractBaseSession` Protocol and renamed accordingly to follow naming conventions for contravariant type variables.
898-
899-
**Before:**
900-
901-
```python
902931
SendNotificationT = TypeVar("SendNotificationT", ClientNotification, ServerNotification)
932+
ReceiveResultT = TypeVar("ReceiveResultT", bound=BaseModel)
903933
```
904934

905935
**After:**
906936

907937
```python
938+
SessionT_co = TypeVar("SessionT_co", bound="AbstractBaseSession[Any, Any]", covariant=True)
939+
SendRequestT_contra = TypeVar("SendRequestT_contra", ClientRequest, ServerRequest, contravariant=True)
908940
SendNotificationT_contra = TypeVar(
909941
"SendNotificationT_contra", ClientNotification, ServerNotification, contravariant=True
910942
)
911-
```
912-
913-
#### `ReceiveResultT` renamed to `ReceiveResultT_co`
914-
915-
The `ReceiveResultT` TypeVar is now defined as **covariant** to support its use in the `AbstractBaseSession` Protocol and renamed accordingly to follow naming conventions for covariant type variables.
916-
917-
**Before:**
918-
919-
```python
920-
ReceiveResultT = TypeVar("ReceiveResultT", bound=BaseModel)
921-
```
922-
923-
**After:**
924-
925-
```python
926-
ReceiveResultT_co =
927-
TypeVar("ReceiveResultT_co", bound=BaseModel, covariant=True)
928-
```
929-
930-
#### `BaseClientSession` is now a Protocol
931-
932-
`BaseClientSession` is now a `typing.Protocol` (structural subtyping) instead of an abstract base class. It no longer inherits from `AbstractBaseSession` and requires no inheritance to satisfy.
933-
934-
**Before:**
935-
936-
```python
937-
from mcp.client.base_client_session import BaseClientSession
938-
939-
class MyClientSession(BaseClientSession):
940-
async def initialize(self) -> InitializeResult:
941-
...
942-
```
943-
944-
**After:**
945-
946-
```python
947-
from mcp.client.base_client_session import BaseClientSession
948-
949-
class MyClientSession:
950-
# Just implement the methods - no inheritance needed
951-
async def initialize(self) -> InitializeResult:
952-
...
953-
954-
# Verify protocol satisfaction at runtime
955-
assert isinstance(MyClientSession(), BaseClientSession)
943+
ReceiveResultT_co = TypeVar("ReceiveResultT_co", bound=BaseModel, covariant=True)
956944
```
957945

958946
## Deprecations

0 commit comments

Comments
 (0)