Transports

MCP transports are the communication layer between your Ruby client and MCP servers. This guide covers the built-in transport types and how to create custom transport implementations for specialized use cases.

Table of contents

  1. Overview
  2. Built-in Transport Types
    1. STDIO Transport
    2. SSE Transport (Server-Sent Events)
    3. Streamable HTTP Transport
      1. OAuth Authentication
  3. Transport Interface
  4. Creating Custom Transports
    1. Basic Custom Transport
  5. Registering Custom Transports
  6. Next Steps

Overview

Transports handle the actual communication protocol between the MCP client and server. They are responsible for:

  • Establishing connections
  • Sending requests and receiving responses
  • Managing the connection lifecycle
  • Handling protocol-specific details

Built-in Transport Types

STDIO Transport

Best for local MCP servers that communicate via standard input/output:

client = RubyLLM::MCP.client(
  name: "local-server",
  transport_type: :stdio,
  config: {
    command: "python",
    args: ["-m", "my_mcp_server"],
    env: { "DEBUG" => "1" }
  }
)

Use cases:

  • Local development
  • Command-line MCP servers
  • Subprocess-based servers

SSE Transport (Server-Sent Events)

Best for web-based MCP servers using HTTP with server-sent events:

client = RubyLLM::MCP.client(
  name: "web-server",
  transport_type: :sse,
  config: {
    url: "https://api.example.com/mcp/sse",
    version: :http2, # You can force HTTP/1.1 by setting this to :http1, default with try to setup HTTP/2 connection
    headers: { "Authorization" => "Bearer token" }
  }
)

Use cases:

  • Web-based MCP services
  • Real-time communication needs
  • HTTP-based infrastructure

Streamable HTTP Transport

Best for HTTP-based MCP servers that support streaming responses:

client = RubyLLM::MCP.client(
  name: "streaming-server",
  transport_type: :streamable,
  config: {
    url: "https://api.example.com/mcp",
    version: :http2, # You can force HTTP/1.1 by setting this to :http1, default with try to setup HTTP/2 connection
    headers: { "Content-Type" => "application/json" }
  }
)

OAuth Authentication

OAuth authentication is available in MCP Protocol 2025-06-18 for Streamable HTTP transport.

For servers requiring OAuth authentication:

client = RubyLLM::MCP.client(
  name: "oauth-server",
  transport_type: :streamable,
  config: {
    url: "https://api.example.com/mcp",
    oauth: {
      issuer: "https://oauth.provider.com",
      client_id: "your-client-id",
      client_secret: "your-client-secret",
      scope: "mcp:read mcp:write"  # Optional
    }
  }
)

OAuth Configuration:

Option Description Required
issuer OAuth provider’s issuer URL Yes
client_id OAuth client identifier Yes
client_secret OAuth client secret Yes
scope Requested OAuth scopes No

The client automatically handles token acquisition, refresh, and authorization headers.

Use cases:

  • REST API-based MCP servers
  • HTTP-first architectures
  • Cloud-based MCP services
  • Enterprise servers requiring OAuth

Transport Interface

All transport implementations must implement the following interface:

class CustomTransport
  # Initialize the transport
  def initialize(coordinator:, **config)
    @coordinator = coordinator
    @config = config
  end

  # Send a request and optionally wait for response
  def request(body, add_id: true, wait_for_response: true)
    # Implementation specific
  end

  # Check if transport is alive/connected
  def alive?
    # Implementation specific
  end

  # Start the transport connection
  def start
    # Implementation specific
  end

  # Close the transport connection
  def close
    # Implementation specific
  end

  # Set the MCP protocol version
  def set_protocol_version(version)
    @protocol_version = version
  end
end

Creating Custom Transports

Basic Custom Transport

Here’s a template for creating a custom transport:

class MyCustomTransport
  def initialize(coordinator:, **config)
    @coordinator = coordinator
    @config = config
    @connection = nil
    @protocol_version = nil
  end

  def request(body, add_id: true, wait_for_response: true)
    # Add request ID if needed
    if add_id
      body = body.merge("id" => generate_request_id)
    end

    # Send the request
    response_data = send_request(body)

    # Create result object
    result = RubyLLM::MCP::Result.new(response_data)

    # Let coordinator process the result
    @coordinator.process_result(result)

    # Return result if it's not a notification
    return nil if result.notification?

    result
  end

  def alive?
    @connection && @connection.connected?
  end

  def start
    @connection = establish_connection
    perform_handshake if @connection
  end

  def close
    @connection&.close
    @connection = nil
  end

  def set_protocol_version(version)
    @protocol_version = version
  end

  private

  def establish_connection
    # Implementation specific
  end

  def send_request(body)
    # Implementation specific
  end

  def generate_request_id
    SecureRandom.uuid
  end

  def perform_handshake
    # Optional: perform any connection setup
  end
end

Registering Custom Transports

Once you’ve created a custom transport, register it with the transport factory:

# Register your custom transport
RubyLLM::MCP::Transport.register_transport(:websocket, WebSocketTransport)
RubyLLM::MCP::Transport.register_transport(:redis_pubsub, RedisPubSubTransport)

# Now you can use it
client = RubyLLM::MCP.client(
  name: "websocket-server",
  transport_type: :websocket,
  config: {
    url: "ws://localhost:8080/mcp",
    headers: { "Authorization" => "Bearer token" }
  }
)

Next Steps