defmodule Backplane.McpProtocol.Client.Supervisor do @moduledoc false use Supervisor use Backplane.McpProtocol.Logging alias Backplane.McpProtocol.Client alias Backplane.McpProtocol.Transport.SSE alias Backplane.McpProtocol.Transport.STDIO alias Backplane.McpProtocol.Transport.StreamableHTTP alias Backplane.McpProtocol.Transport.Websocket @type transport_config :: {:stdio, keyword()} | {:sse, keyword()} | {:websocket, keyword()} | {:streamable_http, keyword()} @doc """ Starts the client supervisor. ## Arguments * `opts` - Supervisor options including: * `:name` - Optional custom name for the client process (defaults to `Backplane.McpProtocol.Client`) * `:transport` - Transport configuration (required) * `:transport_name` - Optional custom name for the transport process * `:client_info` - Client identification info * `:capabilities` - Client capabilities map * `:protocol_version` - MCP protocol version ## Examples # Simple usage with atom names Backplane.McpProtocol.Client.Supervisor.start_link( name: MyApp.MCPClient, transport: {:stdio, command: "mcp", args: ["server"]}, client_info: %{"name" => "MyApp", "version" => "1.0.0"}, capabilities: %{"roots" => %{}}, protocol_version: "2024-11-05" ) # With custom names (e.g., for distributed systems) Backplane.McpProtocol.Client.Supervisor.start_link( name: {:via, Horde.Registry, {MyCluster, "client_1"}}, transport_name: {:via, Horde.Registry, {MyCluster, "transport_1"}}, transport: {:stdio, command: "mcp", args: ["server"]}, client_info: %{"name" => "MyApp", "version" => "1.0.0"}, capabilities: %{"roots" => %{}}, protocol_version: "2024-11-05" ) """ @spec start_link(keyword()) :: Supervisor.on_start() def start_link(opts) do client_name = opts[:name] || Client if sup_name = derive_supervisor_name(client_name) do Supervisor.start_link(__MODULE__, opts, name: sup_name) else Supervisor.start_link(__MODULE__, opts) end end @impl true def init(opts) do transport = Keyword.fetch!(opts, :transport) client_info = Keyword.get(opts, :client_info) || raise ArgumentError, """ :client_info is required when starting Backplane.McpProtocol.Client. Example: {Backplane.McpProtocol.Client, name: MyApp.MCPClient, client_info: %{"name" => "MyApp", "version" => "1.0.0"}, transport: {:streamable_http, base_url: "http://localhost:9999"}} """ capabilities = Keyword.get(opts, :capabilities, %{}) protocol_version = Keyword.get(opts, :protocol_version, Backplane.McpProtocol.Protocol.latest_version()) client_name = opts[:name] || Client transport_name = derive_transport_name(opts[:transport_name], client_name) {layer, transport_opts} = parse_transport_config(transport) client_transport = [layer: layer, name: transport_name] client_opts = [ transport: client_transport, client_info: client_info, capabilities: capabilities, protocol_version: protocol_version, name: client_name ] children = [ %{id: Client, start: {Client, :start_link_server, [client_opts]}}, {layer, transport_opts ++ [name: transport_name, client: client_name]} ] Supervisor.init(children, strategy: :one_for_all) end defp derive_supervisor_name(name) when is_atom(name), do: Module.concat(name, "Supervisor") defp derive_supervisor_name(_name), do: nil defp derive_transport_name(transport, _client) when not is_nil(transport), do: transport defp derive_transport_name(nil, client) when is_atom(client) do Module.concat(client, "Transport") end defp derive_transport_name(_transport, _client) do raise ArgumentError, """ When using a non-atom client name (e.g., via tuple), you must provide an explicit :transport_name option. Example: name: {:via, Registry, {MyRegistry, "client"}}, transport_name: {:via, Registry, {MyRegistry, "transport"}} """ end defp parse_transport_config({:stdio, opts}), do: {STDIO, opts} defp parse_transport_config({:streamable_http, opts}), do: {StreamableHTTP, opts} defp parse_transport_config({:sse, opts}) do Logging.log( :warning, "The :sse transport option is deprecated as of MCP specification 2025-03-26. " <> "Please use {:streamable_http, opts} instead. " <> "The SSE transport is maintained only for backward compatibility with MCP protocol version 2024-11-05.", [] ) {SSE, opts} end defp parse_transport_config({:websocket, opts}), do: {Websocket, opts} end