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 clientcapabilities
: Client capabilitiesserver_capabilities
: Server capabilities received during initializationserver_info
: Server information received during initializationprotocol_version
: MCP protocol version being usedrequest_timeout
: Default timeout for requeststransport
: Transport module or transport infopending_requests
: Map of pending requests with details and timersprogress_callbacks
: Map of callbacks for progress trackinglog_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
@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
Helper function to add progress token to params if provided.
@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:
- Processes the operation details
- Creates a new request with a unique ID
- Sets up the timeout timer
- Registers any progress callbacks
- Updates the state with the new request
Parameters
state
- The current client stateoperation
- The operation to performfrom
- 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
Adds a root directory to the state.
Parameters
state
- The current client stateuri
- 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"}]
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
Clears all root directories.
Parameters
state
- The current client state
Examples
iex> updated_state = Hermes.Client.State.clear_roots(state)
iex> updated_state.roots
[]
@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
@spec get_progress_callback(t(), String.t()) :: progress_callback() | nil
Gets a progress callback for a token.
Parameters
state
- The current client statetoken
- 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
@spec get_request(t(), String.t()) :: Hermes.Client.Request.t() | nil
Gets a request by ID.
Parameters
state
- The current client stateid
- 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
Gets a root directory by its URI.
Parameters
state
- The current client stateuri
- 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"}
Gets the server capabilities.
Parameters
state
- The current client state
Examples
iex> Hermes.Client.State.get_server_capabilities(state)
%{"resources" => %{}, "tools" => %{}}
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"}
@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 stateid
- 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}
@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"
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"}]
Merges additional capabilities into the client's capabilities.
Parameters
state
- The current client stateadditional_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
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}
}
@spec register_progress_callback(t(), String.t(), progress_callback()) :: t()
Registers a progress callback for a token.
Parameters
state
- The current client statetoken
- The progress token to register a callback forcallback
- 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
Helper function to register progress callback from options.
@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 stateid
- 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
Removes a root directory from the state.
Parameters
state
- The current client stateuri
- 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
[]
@spec set_log_callback(t(), log_callback()) :: t()
Sets the log callback.
Parameters
state
- The current client statecallback
- 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
Unregisters a progress callback for a token.
Parameters
state
- The current client statetoken
- 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
Updates server info and capabilities after initialization.
Parameters
state
- The current client stateserver_capabilities
- The server capabilities received from initializationserver_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"}
@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 statemethod
- 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