Packages

Model Context Protocol (MCP) implementation in Elixir with Phoenix integration

Retired package: Release invalid - Accidental version. Use 0.3.0 instead.

Current section

Files

Jump to
backplane_mcp_protocol lib backplane mcp_protocol client supervisor.ex
Raw

lib/backplane/mcp_protocol/client/supervisor.ex

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