Current section
Files
Jump to
Current section
Files
lib/hermes/server/session.ex
defmodule Hermes.Server.Session do
@moduledoc """
Manages state for the Hermes MCP server base implementation.
This module provides a structured representation of server state during the MCP lifecycle,
including initialization status, protocol negotiation, and server capabilities.
## State Structure
Each server state includes:
- `protocol_version`: Negotiated MCP protocol version
- `frame`: Server frame (similar to LiveView socket)
- `initialized`: Whether the server has completed initialization
- `client_info`: Client information received during initialization
- `client_capabilities`: Client capabilities received during initialization
"""
use Agent, restart: :transient
@type t :: %__MODULE__{
protocol_version: String.t() | nil,
initialized: boolean(),
name: GenServer.name() | nil,
client_info: map() | nil,
client_capabilities: map() | nil,
log_level: String.t(),
id: String.t() | nil
}
defstruct [
:id,
:protocol_version,
:log_level,
:name,
initialized: false,
client_info: nil,
client_capabilities: nil
]
@doc """
Starts a new session agent with initial state.
"""
@spec start_link(keyword()) :: Agent.on_start()
def start_link(opts \\ []) do
session_id = Keyword.fetch!(opts, :session_id)
name = Keyword.fetch!(opts, :name)
Agent.start_link(fn -> new(id: session_id, name: name) end, name: name)
end
@doc """
Creates a new server state with the given options.
## Parameters
* `opts` - Map containing the initialization options
"""
@spec new(Enumerable.t()) :: t()
def new(opts), do: struct(__MODULE__, opts)
@doc """
Guard to check if a session has been initialized.
## Examples
iex> session = %Session{initialized: true}
iex> is_initialized(session)
true
"""
defguard is_initialized(session) when session.initialized
@doc """
Retrieves the current state of a session.
## Parameters
* `session` - The session agent name or PID
## Returns
The current session state as a `%Session{}` struct.
## Examples
iex> session_state = Session.get(session_name)
%Session{initialized: true, protocol_version: "2025-03-26"}
"""
@spec get(GenServer.name()) :: t
def get(session) do
Agent.get(session, & &1)
end
@doc """
Updates state after successful initialization handshake.
This function:
1. Sets the negotiated protocol version
2. Stores client information and capabilities
3. Marks the server as initialized
## Parameters
* `state` - The current server state
* `protocol_version` - The negotiated protocol version
* `client_info` - Client information from the initialize request
* `client_capabilities` - Client capabilities from the initialize request
## Examples
iex> client_info = %{"name" => "hello", "version" => "1.0.0"}
iex> capabilities = %{"sampling" => %{}}
iex> Hermes.Server.Session.update_from_initialization(session, "2025-03-26", client_info, capabilities)
:ok
"""
@spec update_from_initialization(GenServer.name(), String.t(), map, map) :: :ok
def update_from_initialization(session, negotiated_version, client_info, capabilities) do
Agent.update(session, fn state ->
%{state | protocol_version: negotiated_version, client_info: client_info, client_capabilities: capabilities}
end)
end
@doc """
Marks the session as initialized.
"""
@spec mark_initialized(GenServer.name()) :: :ok
def mark_initialized(session) do
Agent.update(session, fn state -> %{state | initialized: true} end)
end
@doc """
Updates the log level.
"""
@spec set_log_level(GenServer.name(), String.t()) :: :ok
def set_log_level(session, level) do
Agent.update(session, fn state -> %{state | log_level: level} end)
end
end