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
Current section
Files
lib/backplane/mcp_protocol/server/registry.ex
defmodule Backplane.McpProtocol.Server.Registry do
@moduledoc """
Behaviour for pluggable session registries and deterministic naming utilities.
The registry is responsible for mapping session IDs to PIDs. Different transports
have different needs:
- STDIO: single session, no registry needed (`Registry.None`)
- HTTP: multiple sessions, need lookup by session ID (`Registry.Local`)
## Naming Utilities
The module also provides deterministic atom naming for internal processes.
These are safe because server modules are compile-time bounded.
"""
@type session_id :: String.t()
@callback child_spec(keyword()) :: Supervisor.child_spec() | :ignore
@callback register_session(name :: term(), session_id(), pid()) :: :ok | {:error, term()}
@callback lookup_session(name :: term(), session_id()) :: {:ok, pid()} | {:error, :not_found}
@callback unregister_session(name :: term(), session_id()) :: :ok
@doc """
Returns the GenServer name for a session. Override this to return a `:via` tuple
(e.g. `{:via, Horde.Registry, {name, session_id}}`) when using a distributed registry
that auto-registers processes on `start_link`. The default returns a plain atom.
When a `:via` tuple is returned, `register_session/3` should be a no-op since
registration happens automatically on process start.
"""
@callback session_name(registry_name :: term(), session_id()) :: GenServer.name()
@optional_callbacks session_name: 2
@doc """
Resolves the session GenServer name via the registry adapter.
Falls back to the default atom-based naming if the adapter does not implement
the optional `session_name/2` callback.
"""
@spec resolve_session_name(module(), term(), session_id()) :: GenServer.name()
def resolve_session_name(registry_mod, registry_name, session_id) do
if function_exported?(registry_mod, :session_name, 2) do
registry_mod.session_name(registry_name, session_id)
else
session_name_from_registry_name(registry_name, session_id)
end
end
defp session_name_from_registry_name(registry_name, session_id) when is_atom(registry_name) do
:"#{registry_name}.session.#{session_id}"
end
defp session_name_from_registry_name(_registry_name, session_id) do
:"Backplane.McpProtocol.session.#{session_id}"
end
# Deterministic atom naming for internal processes
@spec transport_name(module(), atom()) :: atom()
def transport_name(server, type), do: :"Backplane.McpProtocol.#{server}.transport.#{type}"
@spec task_supervisor_name(module()) :: atom()
def task_supervisor_name(server), do: :"Backplane.McpProtocol.#{server}.task_supervisor"
@doc """
Default atom name for a server's `Backplane.McpProtocol.Server.TaskStore` process. Adapters
may override via the optional `resolve_name/2` callback to return a `:via`
tuple for distributed deployments.
"""
@spec task_store_name(module()) :: atom()
def task_store_name(server), do: :"Backplane.McpProtocol.#{server}.task_store"
@spec session_supervisor_name(module()) :: atom()
def session_supervisor_name(server), do: :"Backplane.McpProtocol.#{server}.session_supervisor"
@spec supervisor_name(module()) :: atom()
def supervisor_name(server), do: :"Backplane.McpProtocol.#{server}.supervisor"
@spec session_name(module(), String.t()) :: atom()
def session_name(server, session_id), do: :"Backplane.McpProtocol.#{server}.session.#{session_id}"
@spec stdio_session_name(module()) :: atom()
def stdio_session_name(server), do: :"Backplane.McpProtocol.#{server}.session.stdio"
@spec registry_name(module()) :: atom()
def registry_name(server), do: :"Backplane.McpProtocol.#{server}.registry"
end