Skip to content

transport

Public contracts for message transports on either side of an MCP connection.

DispatcherTransport dataclass

Explicitly opt into a dispatcher-backed connection instead of message streams.

Pass this wrapper to Client or ServerRuntime.connect(). Entering connection acquires the channel and yields an unstarted dispatcher; the SDK owns its receive loop. Exiting releases the channel after the loop stops. Native adapters can use their own framing without implementing MCP negotiation, validation, callbacks, or a separate client-session API.

Custom dispatcher implementations remain experimental until the lifecycle contract has been validated against native network bindings.

Source code in src/mcp/shared/transport.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@dataclass(frozen=True)
class DispatcherTransport:
    """Explicitly opt into a dispatcher-backed connection instead of message streams.

    Pass this wrapper to `Client` or `ServerRuntime.connect()`. Entering
    `connection` acquires the channel and yields an unstarted dispatcher; the
    SDK owns its receive loop. Exiting releases the channel after the loop
    stops. Native adapters can use their own framing without implementing MCP
    negotiation, validation, callbacks, or a separate client-session API.

    Custom dispatcher implementations remain experimental until the lifecycle
    contract has been validated against native network bindings.
    """

    connection: AbstractAsyncContextManager[Dispatcher[TransportContext]]

MessageMetadata module-attribute

MessageMetadata = (
    ClientMessageMetadata | ServerMessageMetadata | None
)

ReadStream

Bases: Protocol[T_co]

Protocol for reading items from a stream.

Consumers that need the sender's context should use getattr(stream, 'last_context', None).

Source code in src/mcp/shared/_stream_protocols.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class ReadStream(Protocol[T_co]):
    """Protocol for reading items from a stream.

    Consumers that need the sender's context should use
    ``getattr(stream, 'last_context', None)``.
    """

    async def receive(self) -> T_co: ...
    async def aclose(self) -> None: ...
    def __aiter__(self) -> ReadStream[T_co]: ...
    async def __anext__(self) -> T_co: ...
    async def __aenter__(self) -> Self: ...
    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None: ...

Transport

Bases: AbstractAsyncContextManager[TransportStreams], Protocol

An async context manager yielding a logical peer's read and write streams.

Entering opens the channel. Exiting closes owned resources and stops its background tasks. Consumers may close the streams before context exit, so stream closure must be idempotent. Borrowed network clients remain owned by their caller.

Each inbound item is a decoded SessionMessage or a recoverable exception. End the read stream when the connection is lost; an exception item alone does not fail pending requests. Writes must support cancellation and apply backpressure instead of buffering indefinitely.

A stream pair belongs to one logical peer, not an entire broker. The adapter owns framing and routing; the SDK owns MCP protocol processing.

Source code in src/mcp/shared/transport.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Transport(AbstractAsyncContextManager[TransportStreams], Protocol):
    """An async context manager yielding a logical peer's read and write streams.

    Entering opens the channel. Exiting closes owned resources and stops its
    background tasks. Consumers may close the streams before context exit, so
    stream closure must be idempotent. Borrowed network clients remain owned by
    their caller.

    Each inbound item is a decoded `SessionMessage` or a recoverable exception.
    End the read stream when the connection is lost; an exception item alone
    does not fail pending requests. Writes must support cancellation and apply
    backpressure instead of buffering indefinitely.

    A stream pair belongs to one logical peer, not an entire broker. The
    adapter owns framing and routing; the SDK owns MCP protocol processing.
    """

TransportContextBuilder module-attribute

TransportContextBuilder: TypeAlias = Callable[
    [MessageMetadata], TransportContext
]

TransportStreams module-attribute

WriteStream

Bases: Protocol[T_contra]

Protocol for writing items to a stream.

Source code in src/mcp/shared/_stream_protocols.py
38
39
40
41
42
43
44
45
46
47
48
49
class WriteStream(Protocol[T_contra]):
    """Protocol for writing items to a stream."""

    async def send(self, item: T_contra, /) -> None: ...
    async def aclose(self) -> None: ...
    async def __aenter__(self) -> Self: ...
    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None: ...

Classes

Attributes