# `ExMCP.Transport`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/transport.ex#L1)

Behaviour definition for MCP transport implementations.

A transport is responsible for sending and receiving MCP protocol messages
over a specific communication channel. ExMCP includes implementations for
the standard MCP transports and provides this behaviour for custom implementations.

## Built-in Transports

ExMCP provides these standard transports:

- **`:stdio`** - Standard I/O communication (MCP specification)
- **`:http`** - HTTP with optional SSE streaming (MCP specification)
- **`:test`** - In-memory transport for testing
- **`:beam`** - BEAM-local transport carrying MCP-shaped messages as Elixir terms.

## Using Transports

Transports are specified when starting clients or servers:

    # stdio transport
    {:ok, client} = ExMCP.Client.start_link(
      transport: :stdio,
      command: ["python", "mcp-server.py"]
    )

    # HTTP transport
    {:ok, client} = ExMCP.Client.start_link(
      transport: :http,
      url: "https://api.example.com"
    )

## Push vs Pull Model

Transports support two message delivery models:

- **Pull (legacy):** Client calls `receive_message/1` in a loop via a receiver task.
  This is the default and works for all transports.

- **Push (event-driven):** Client calls `subscribe/2` to register a handler pid.
  The transport pushes `{:transport_event, message}` messages directly to the handler.
  This eliminates the receiver task and is more efficient.

Transports that implement `subscribe/2` should still implement `receive_message/1`
for backwards compatibility.

## Custom Transport Implementation

To implement a custom transport, create a module that implements
all the callbacks defined in this behaviour:

    defmodule MyTransport do
      @behaviour ExMCP.Transport

      @impl true
      def connect(opts) do
        # Establish connection
        {:ok, state}
      end

      @impl true
      def send_message(message, state) do
        # Send the message
        {:ok, state}
      end

      @impl true
      def receive_message(state) do
        # Receive a message (blocking)
        {:ok, message, state}
      end

      @impl true
      def close(state) do
        # Clean up
        :ok
      end

      # Optional: enable push model
      @impl true
      def subscribe(pid, state) do
        # Start pushing {:transport_event, msg} to pid
        {:ok, %{state | subscriber: pid}}
      end
    end

# `message`

```elixir
@type message() :: String.t() | map() | list()
```

# `opts`

```elixir
@type opts() :: keyword()
```

# `state`

```elixir
@type state() :: any()
```

# `capabilities`
*optional* 

```elixir
@callback capabilities(state()) :: [atom()]
```

Optional callback to declare transport capabilities.

Returns a list of capability atoms that indicate special features
supported by this transport. Clients can use this information to
optimize their communication strategy.

## Capabilities

- `:push` - Transport supports `subscribe/2` for event-driven message delivery
- `:compression` - Transport supports message compression (future)
- `:encryption` - Transport supports message encryption (future)

## Examples

    # Transport with push delivery
    def capabilities(_state), do: [:push]

    # Transport with no special capabilities (default)
    def capabilities(_state), do: []

Default implementation returns an empty list (no special capabilities).

# `close`

```elixir
@callback close(state()) :: :ok
```

Closes the transport connection.

Should clean up any resources and return `:ok`.

# `connect`

```elixir
@callback connect(opts()) :: {:ok, state()} | {:error, any()}
```

Establishes a connection for the transport.

Options are transport-specific. Should return `{:ok, state}`
where state contains any necessary connection information.

# `connected?`
*optional* 

```elixir
@callback connected?(state()) :: boolean()
```

Optional callback to check if the transport is still connected.

Default implementation always returns true.

# `receive_message`

```elixir
@callback receive_message(state()) :: {:ok, message(), state()} | {:error, any()}
```

Receives a message from the transport.

This should block until a message is available. Returns
`{:ok, message, new_state}` where message is a JSON string or MCP-shaped term.

Note: When `subscribe/2` is used, `receive_message/1` may not be called.
Transports should still implement it for backwards compatibility.

# `send_message`

```elixir
@callback send_message(message(), state()) ::
  {:ok, state()}
  | {:ok, state(), response :: binary() | map()}
  | {:error, any()}
```

Sends a message through the transport.

The message will be a JSON-encoded string for wire transports, or an
MCP-shaped map/list for local BEAM transports. Should return
`{:ok, new_state}` on success.

Synchronous request/response transports (e.g. HTTP POST without SSE
streaming) may instead return `{:ok, new_state, response}` where
`response` is the response body delivered inline — either a raw JSON
string or an already-decoded map. Callers must be prepared to handle
both the 2-tuple and 3-tuple success shapes; transports that deliver
responses asynchronously (via `receive_message/1` or `subscribe/2`)
should return the 2-tuple.

# `subscribe`
*optional* 

```elixir
@callback subscribe(pid(), state()) :: {:ok, state()} | {:error, any()}
```

Optional: Subscribe a process to receive transport events.

When implemented, the transport pushes messages to the subscriber pid as:
- `{:transport_event, message}` — a received message (JSON string or map)
- `{:transport_closed, reason}` — transport connection closed
- `{:transport_error, reason}` — transport error occurred

This enables the push (event-driven) model, eliminating the need for a
receiver task that polls `receive_message/1`.

Returns `{:ok, new_state}` on success.

# `get_transport`

```elixir
@spec get_transport(:stdio | :http | :test | :beam | module()) :: module()
```

Helper to get the appropriate transport module for an atom identifier.

## Transport identifiers:
- `:stdio` - Standard I/O transport (official MCP transport)
- `:http` - Streamable HTTP transport with SSE (official MCP transport)
- `:test` - In-memory transport for testing (non-standard)
- `:beam` - BEAM-local transport carrying MCP-shaped messages as Elixir terms.

# `supports_push?`

```elixir
@spec supports_push?(module()) :: boolean()
```

Check if a transport module supports the push (subscribe) model.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
