Hermes.Client.State (hermes_mcp v0.9.1)

Manages state for the Hermes MCP client.

This module provides a structured representation of client state, including capabilities, server info, and request tracking.

State Structure

Each client state includes:

  • client_info: Information about the client
  • capabilities: Client capabilities
  • server_capabilities: Server capabilities received during initialization
  • server_info: Server information received during initialization
  • protocol_version: MCP protocol version being used
  • request_timeout: Default timeout for requests
  • transport: Transport module or transport info
  • pending_requests: Map of pending requests with details and timers
  • progress_callbacks: Map of callbacks for progress tracking
  • log_callback: Callback for handling log messages

Examples

# Create a new client state
state = Hermes.Client.State.new(%{
  client_info: %{"name" => "MyClient", "version" => "1.0.0"},
  capabilities: %{"resources" => %{}},
  protocol_version: "2024-11-05",
  request_timeout: 30000,
  transport: %{layer: Hermes.Transport.SSE, name: MyTransport}
})

# Add a request to the state
{request_id, updated_state} = Hermes.Client.State.add_request(state, "ping", %{}, from)

# Get server capabilities
server_capabilities = Hermes.Client.State.get_server_capabilities(state)

Summary

Functions

Helper function to add progress token to params if provided.

Adds a new request to the state from an Operation and returns the request ID and updated state.

Adds a root directory to the state.

Clears the log callback.

Clears all root directories.

Gets the log callback.

Gets a progress callback for a token.

Gets a request by ID.

Gets a root directory by its URI.

Gets the server capabilities.

Gets the server info.

Handles a request timeout, cancelling the timer and returning the updated state.

Returns a list of all pending requests.

Lists all root directories.

Merges additional capabilities into the client's capabilities.

Creates a new client state with the given options.

Registers a progress callback for a token.

Helper function to register progress callback from options.

Removes a request and returns its info along with the updated state.

Removes a root directory from the state.

Sets the log callback.

Unregisters a progress callback for a token.

Updates server info and capabilities after initialization.

Validates if a method is supported by the server's capabilities.

Types

log_callback()

@type log_callback() :: (String.t(), term(), String.t() | nil -> any())

progress_callback()

@type progress_callback() :: (String.t() | integer(), number(), number() | nil ->
                          any())

root()

@type root() :: %{uri: String.t(), name: String.t() | nil}

t()

@type t() :: %Hermes.Client.State{
  capabilities: map(),
  client_info: map(),
  log_callback: log_callback() | nil,
  pending_requests: %{required(String.t()) => Hermes.Client.Request.t()},
  progress_callbacks: %{required(String.t()) => progress_callback()},
  protocol_version: String.t(),
  roots: %{required(String.t()) => root()},
  server_capabilities: map() | nil,
  server_info: map() | nil,
  transport: map()
}

Functions

add_progress_token_to_params(params, progress_opts)

@spec add_progress_token_to_params(map(), keyword() | nil) :: map()

Helper function to add progress token to params if provided.

add_request_from_operation(state, operation, from)

@spec add_request_from_operation(t(), Hermes.Client.Operation.t(), GenServer.from()) ::
  {String.t(), t()}

Adds a new request to the state from an Operation and returns the request ID and updated state.

This function:

  1. Processes the operation details
  2. Creates a new request with a unique ID
  3. Sets up the timeout timer
  4. Registers any progress callbacks
  5. Updates the state with the new request

Parameters

  • state - The current client state
  • operation - The operation to perform
  • from - The GenServer.from for the caller

Examples

iex> operation = Operation.new(%{method: "ping", params: %{}})
iex> {req_id, updated_state} = Hermes.Client.State.add_request_from_operation(state, operation, {pid, ref})
iex> is_binary(req_id)
true
iex> map_size(updated_state.pending_requests) > map_size(state.pending_requests)
true

add_root(state, uri, name \\ nil)

@spec add_root(t(), String.t(), String.t() | nil) :: t()

Adds a root directory to the state.

Parameters

  • state - The current client state
  • uri - The URI of the root directory (must start with "file://")
  • name - Optional human-readable name for display purposes

Examples

iex> updated_state = Hermes.Client.State.add_root(state, "file:///home/user/project", "My Project")
iex> updated_state.roots
[%{uri: "file:///home/user/project", name: "My Project"}]

clear_log_callback(state)

@spec clear_log_callback(t()) :: t()

Clears the log callback.

Parameters

  • state - The current client state

Examples

iex> updated_state = Hermes.Client.State.clear_log_callback(state)
iex> is_nil(updated_state.log_callback)
true

clear_roots(state)

@spec clear_roots(t()) :: t()

Clears all root directories.

Parameters

  • state - The current client state

Examples

iex> updated_state = Hermes.Client.State.clear_roots(state)
iex> updated_state.roots
[]

get_log_callback(state)

@spec get_log_callback(t()) :: log_callback() | nil

Gets the log callback.

Parameters

  • state - The current client state

Examples

iex> callback = Hermes.Client.State.get_log_callback(state)
iex> is_function(callback, 3) or is_nil(callback)
true

get_progress_callback(state, token)

@spec get_progress_callback(t(), String.t()) :: progress_callback() | nil

Gets a progress callback for a token.

Parameters

  • state - The current client state
  • token - The progress token to get the callback for

Examples

iex> callback = Hermes.Client.State.get_progress_callback(state, "token123")
iex> is_function(callback, 3)
true

get_request(state, id)

@spec get_request(t(), String.t()) :: Hermes.Client.Request.t() | nil

Gets a request by ID.

Parameters

  • state - The current client state
  • id - The request ID to retrieve

Examples

iex> Hermes.Client.State.get_request(state, "req_123")
{{pid, ref}, "ping", timer_ref, start_time} # or nil if not found

get_root_by_uri(state, uri)

@spec get_root_by_uri(t(), String.t()) :: root() | nil

Gets a root directory by its URI.

Parameters

  • state - The current client state
  • uri - The URI of the root directory to get

Examples

iex> Hermes.Client.State.get_root_by_uri(state, "file:///home/user/project")
%{uri: "file:///home/user/project", name: "My Project"}

get_server_capabilities(state)

@spec get_server_capabilities(t()) :: map() | nil

Gets the server capabilities.

Parameters

  • state - The current client state

Examples

iex> Hermes.Client.State.get_server_capabilities(state)
%{"resources" => %{}, "tools" => %{}}

get_server_info(state)

@spec get_server_info(t()) :: map() | nil

Gets the server info.

Parameters

  • state - The current client state

Examples

iex> Hermes.Client.State.get_server_info(state)
%{"name" => "TestServer", "version" => "1.0.0"}

handle_request_timeout(state, id)

@spec handle_request_timeout(t(), String.t()) ::
  {Hermes.Client.Request.t() | nil, t()}

Handles a request timeout, cancelling the timer and returning the updated state.

Parameters

  • state - The current client state
  • id - The request ID that timed out

Examples

iex> Hermes.Client.State.handle_request_timeout(state, "req_123")
{%{from: from, method: "ping", elapsed_ms: 30000}, updated_state}

list_pending_requests(state)

@spec list_pending_requests(t()) :: [Hermes.Client.Request.t()]

Returns a list of all pending requests.

Parameters

  • state - The current client state

Examples

iex> requests = Hermes.Client.State.list_pending_requests(state)
iex> length(requests) > 0
true
iex> hd(requests).method
"ping"

list_roots(state)

@spec list_roots(t()) :: [root()]

Lists all root directories.

Parameters

  • state - The current client state

Examples

iex> Hermes.Client.State.list_roots(state)
[%{uri: "file:///home/user/project", name: "My Project"}]

merge_capabilities(state, additional_capabilities)

@spec merge_capabilities(t(), map()) :: t()

Merges additional capabilities into the client's capabilities.

Parameters

  • state - The current client state
  • additional_capabilities - The capabilities to merge

Examples

iex> updated_state = Hermes.Client.State.merge_capabilities(state, %{"tools" => %{"execute" => true}})
iex> updated_state.capabilities["tools"]["execute"]
true

new(opts)

@spec new(map()) :: t()

Creates a new client state with the given options.

Parameters

  • opts - Map containing the initialization options

Options

  • :client_info - Information about the client (required)
  • :capabilities - Client capabilities to advertise
  • :protocol_version - Protocol version to use
  • :request_timeout - Default timeout for requests in milliseconds
  • :transport - Transport configuration

Examples

iex> Hermes.Client.State.new(%{
...>   client_info: %{"name" => "MyClient", "version" => "1.0.0"},
...>   capabilities: %{"resources" => %{}},
...>   protocol_version: "2024-11-05",
...>   transport: %{layer: Hermes.Transport.SSE, name: MyTransport}
...> })
%Hermes.Client.State{
  client_info: %{"name" => "MyClient", "version" => "1.0.0"},
  capabilities: %{"resources" => %{}},
  protocol_version: "2024-11-05",
  transport: %{layer: Hermes.Transport.SSE, name: MyTransport}
}

register_progress_callback(state, token, callback)

@spec register_progress_callback(t(), String.t(), progress_callback()) :: t()

Registers a progress callback for a token.

Parameters

  • state - The current client state
  • token - The progress token to register a callback for
  • callback - The callback function to call when progress updates are received

Examples

iex> updated_state = Hermes.Client.State.register_progress_callback(state, "token123", fn token, progress, total -> IO.inspect({token, progress, total}) end)
iex> Map.has_key?(updated_state.progress_callbacks, "token123")
true

register_progress_callback_from_opts(state, progress_opts)

@spec register_progress_callback_from_opts(t(), keyword() | nil) :: t()

Helper function to register progress callback from options.

remove_request(state, id)

@spec remove_request(t(), String.t()) :: {Hermes.Client.Request.t() | nil, t()}

Removes a request and returns its info along with the updated state.

Parameters

  • state - The current client state
  • id - The request ID to remove

Examples

iex> {request_info, updated_state} = Hermes.Client.State.remove_request(state, "req_123")
iex> request_info.method
"ping"
iex> request_info.elapsed_ms > 0
true

remove_root(state, uri)

@spec remove_root(t(), String.t()) :: t()

Removes a root directory from the state.

Parameters

  • state - The current client state
  • uri - The URI of the root directory to remove

Examples

iex> updated_state = Hermes.Client.State.remove_root(state, "file:///home/user/project")
iex> updated_state.roots
[]

set_log_callback(state, callback)

@spec set_log_callback(t(), log_callback()) :: t()

Sets the log callback.

Parameters

  • state - The current client state
  • callback - The callback function to call when log messages are received

Examples

iex> updated_state = Hermes.Client.State.set_log_callback(state, fn level, data, logger -> IO.inspect({level, data, logger}) end)
iex> is_function(updated_state.log_callback, 3)
true

unregister_progress_callback(state, token)

@spec unregister_progress_callback(t(), String.t()) :: t()

Unregisters a progress callback for a token.

Parameters

  • state - The current client state
  • token - The progress token to unregister the callback for

Examples

iex> updated_state = Hermes.Client.State.unregister_progress_callback(state, "token123")
iex> Map.has_key?(updated_state.progress_callbacks, "token123")
false

update_server_info(state, server_capabilities, server_info)

@spec update_server_info(t(), map(), map()) :: t()

Updates server info and capabilities after initialization.

Parameters

  • state - The current client state
  • server_capabilities - The server capabilities received from initialization
  • server_info - The server information received from initialization

Examples

iex> updated_state = Hermes.Client.State.update_server_info(state, %{"resources" => %{}}, %{"name" => "TestServer"})
iex> updated_state.server_capabilities
%{"resources" => %{}}
iex> updated_state.server_info
%{"name" => "TestServer"}

validate_capability(map, method)

@spec validate_capability(t(), String.t()) :: :ok | {:error, Hermes.MCP.Error.t()}

Validates if a method is supported by the server's capabilities.

Parameters

  • state - The current client state
  • method - The method to validate

Returns

  • :ok if the method is supported
  • {:error, %Hermes.MCP.Error{}} if the method is not supported

Examples

iex> Hermes.Client.State.validate_capability(state_with_resources, "resources/list")
:ok

iex> {:error, error} = Hermes.Client.State.validate_capability(state_without_tools, "tools/list")
iex> error.reason
:method_not_found