Skip to content

Introduce a shared Transport abstraction to reduce stdio/SSE/WebSocket/HTTP duplication #1690

@dgenio

Description

@dgenio

Summary

The SDK currently implements multiple server and client transports (stdio, SSE, WebSocket, Streamable HTTP). Each one has its own message loop, error handling, and resource management logic with significant duplication.

This makes it harder to:

  • fix bugs consistently across transports,
  • add new transports, and
  • ensure feature parity between them.

A small Transport abstraction could centralize most of the shared behavior.

Problems

  • Duplication: Similar read/write loops and error handling are repeated in:
    • src/mcp/server/stdio.py
    • src/mcp/server/sse.py
    • src/mcp/server/websocket.py
    • src/mcp/server/streamable_http.py
  • Inconsistent behavior: Features like cancellation, error mapping, and shutdown can diverge between transports.
  • Maintenance cost: Adding a new transport or changing session semantics requires updating each implementation.

Proposal

  1. Define a Transport protocol/interface

    For example:

    class Transport(Protocol):
        async def connect(self) -> None: ...
        async def send_message(self, message: JSONType) -> None: ...
        async def receive_message(self) -> JSONType | None: ...
        async def close(self) -> None: ...
  • Implementations handle encoding details (JSON lines, SSE, WebSocket frames, HTTP streaming).
  1. Refactor sessions to depend on Transport

    • BaseSession (and server/client sessions) should operate on a Transport instance rather than transport-specific primitives.
    • Transport-agnostic session logic (request routing, error mapping) lives in one place.
  2. Align behavior across transports

    • Standardize behavior for:

      • connection setup/teardown,
      • error handling,
      • cancellation and shutdown semantics.
  3. Incremental migration

    • Start with one or two transports (e.g. stdio + Streamable HTTP) to validate the abstraction.
    • Gradually migrate remaining transports.

Why this matters

  • Consistency: Users can expect the same semantics regardless of transport choice.
  • Easier maintenance: Bug fixes and feature additions happen once, in a shared layer.
  • Future-proofing: Adding new transports becomes easier and less error-prone.

Acceptance criteria

  • A Transport interface (or equivalent) is defined for server and client usage.
  • At least two transports are refactored to use the shared abstraction.
  • Session logic no longer duplicates transport-specific read/write loops.
  • Tests verify identical semantics (errors, cancellation, shutdown) across transports.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions