Hermolaos.Transport behaviour (Hermolaos v0.3.0)

View Source

Behaviour defining the transport interface for MCP communication.

A transport is responsible for the low-level communication between an MCP client and server. MCP supports two standard transports:

  • stdio: Communication via stdin/stdout with a subprocess
  • HTTP/SSE: Communication via HTTP POST with optional Server-Sent Events

Implementing a Custom Transport

To create a custom transport, implement this behaviour:

defmodule MyApp.CustomTransport do
  @behaviour Hermolaos.Transport
  use GenServer

  @impl Hermolaos.Transport
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts)
  end

  @impl Hermolaos.Transport
  def send_message(transport, message) do
    GenServer.call(transport, {:send, message})
  end

  @impl Hermolaos.Transport
  def close(transport) do
    GenServer.stop(transport)
  end

  @impl Hermolaos.Transport
  def connected?(transport) do
    GenServer.call(transport, :connected?)
  end

  # GenServer callbacks...
end

Message Flow

Transports communicate with their owner (typically a Connection GenServer) via messages:

  • {:transport_ready, pid} - Transport is ready for messages
  • {:transport_message, pid, message} - Received a message from server
  • {:transport_closed, pid, reason} - Transport connection closed
  • {:transport_error, pid, error} - Transport error occurred

Options

All transports accept these common options:

  • :owner - The PID to send messages to (required)
  • :name - Optional name for the transport process

Summary

Callbacks

Sends a message asynchronously (fire-and-forget).

Closes the transport connection.

Checks if the transport is currently connected.

Returns transport-specific information.

Sends a JSON-RPC message through the transport.

Starts the transport process.

Functions

Sends a message, falling back to sync send if async is not available.

Validates that a module implements the Transport behaviour.

Types

message()

@type message() :: map()

send_result()

@type send_result() :: :ok | {:error, term()}

start_result()

@type start_result() :: {:ok, pid()} | {:error, term()}

t()

@type t() :: pid() | GenServer.name()

Callbacks

cast_message(transport, message)

(optional)
@callback cast_message(transport :: t(), message :: message()) :: :ok

Sends a message asynchronously (fire-and-forget).

This is an optional callback for transports that support non-blocking sends. If not implemented, defaults to calling send_message/2.

close(transport)

@callback close(transport :: t()) :: :ok

Closes the transport connection.

This should gracefully shut down the transport, cleaning up any resources. The transport should send {:transport_closed, self(), :normal} to the owner before terminating.

Returns

  • :ok on success

connected?(transport)

@callback connected?(transport :: t()) :: boolean()

Checks if the transport is currently connected.

Returns

  • true if connected and ready for messages
  • false otherwise

info(transport)

(optional)
@callback info(transport :: t()) :: map()

Returns transport-specific information.

This is an optional callback for transports to expose metadata like session IDs, connection state, or statistics.

send_message(transport, message)

@callback send_message(transport :: t(), message :: message()) :: send_result()

Sends a JSON-RPC message through the transport.

The message should be a map that will be JSON-encoded by the transport.

Parameters

  • transport - The transport process (PID or name)
  • message - The JSON-RPC message map to send

Returns

  • :ok on success
  • {:error, reason} on failure

Notes

This function may block until the message is sent, or it may be asynchronous depending on the transport implementation. For non-blocking sends, consider using cast_message/2 if available.

start_link(opts)

@callback start_link(opts :: keyword()) :: start_result()

Starts the transport process.

The transport should send {:transport_ready, self()} to the owner when it's ready to send and receive messages.

Options

  • :owner - PID to receive transport messages (required)
  • Other options are transport-specific

Returns

  • {:ok, pid} on success
  • {:error, reason} on failure

Functions

send(module, transport, message)

@spec send(module(), t(), message()) :: send_result()

Sends a message, falling back to sync send if async is not available.

Examples

Hermolaos.Transport.send(transport_mod, transport_pid, message)

valid_transport?(module)

@spec valid_transport?(module()) :: boolean()

Validates that a module implements the Transport behaviour.

Examples

iex> Hermolaos.Transport.valid_transport?(Hermolaos.Transport.Stdio)
true

iex> Hermolaos.Transport.valid_transport?(String)
false