Hermes.Server.Base (hermes_mcp v0.10.1)
Base implementation of an MCP server.
This module provides the core functionality for handling MCP messages, managing the protocol lifecycle, and coordinating with transport layers. It implements the JSON-RPC message handling, session management, and protocol negotiation required by the MCP specification.
Architecture
The Base server acts as the central message processor in the server stack:
- Receives messages from transport layers (STDIO, StreamableHTTP)
- Manages protocol initialization and version negotiation
- Delegates business logic to the implementation module
- Maintains session state via Session agents
- Handles errors and protocol violations
Session Management
For transports that support multiple sessions (like StreamableHTTP), the Base server maintains a registry of Session agents. Each session tracks:
- Protocol version negotiated with that client
- Client information and capabilities
- Initialization state
- Log level preferences
Message Flow
- Transport receives raw message from client
- Transport calls Base with
{:message, data, session_id}
- Base decodes and validates the message
- Base retrieves or creates session state
- Base delegates to implementation module callbacks
- Base encodes response and sends back through transport
Example Implementation
defmodule MyServer do
use Hermes.Server
def server_info do
%{"name" => "My MCP Server", "version" => "1.0.0"}
end
def handle_request(%{"method" => "my_method"} = request, frame) do
result = process_request(request["params"])
{:reply, result, frame}
end
end
Summary
Functions
Returns a specification to start this module under a supervisor.
Sends a log message notification to the client.
Sends a notification to the client.
Sends a progress notification to the client.
Sends a prompts list changed notification to the client.
Sends a resource updated notification to the client.
Sends a resources list changed notification to the client.
Sends a tools list changed notification to the client.
Starts a new MCP server process.
Types
@type option() :: {:module, GenServer.name()} | {:init_arg, keyword()} | {:name, GenServer.name()} | GenServer.option()
MCP server options
:module
- The module implementing the server behavior (required):init_args
- Arguments passed to the module's init/1 callback:name
- Optional name for registering the GenServer
@type t() :: %{ module: module(), server_info: map(), capabilities: map(), frame: Hermes.Server.Frame.t(), supported_versions: [String.t()], transport: [layer: module(), name: GenServer.name()], init_arg: term(), registry: module(), sessions: %{required(String.t()) => {GenServer.name(), reference()}} }
Functions
Returns a specification to start this module under a supervisor.
See Supervisor
.
@spec send_log_message(GenServer.name(), String.t(), String.t(), String.t() | nil) :: :ok | {:error, term()}
Sends a log message notification to the client.
Parameters
server
- The server processlevel
- Log level (debug, info, notice, warning, error, critical, alert, emergency)data
- The log message datalogger
- Optional logger name
@spec send_notification(GenServer.name(), String.t(), map()) :: :ok | {:error, term()}
Sends a notification to the client.
Notifications are fire-and-forget messages that don't expect a response. This function is useful for server-initiated communication like progress updates or status changes.
Parameters
server
- The server process name or PIDmethod
- The notification method (e.g., "notifications/message")params
- Optional parameters for the notification (defaults to%{}
)
Returns
:ok
if notification was sent successfully{:error, reason}
if transport fails
Examples
# Send a log message notification
Hermes.Server.Base.send_notification(
server,
"notifications/message",
%{"level" => "info", "data" => "Processing started"}
)
# Send a custom notification
Hermes.Server.Base.send_notification(
server,
"custom/status_changed",
%{"status" => "active"}
)
@spec send_progress( GenServer.name(), String.t() | integer(), number(), number() | nil, String.t() | nil ) :: :ok | {:error, term()}
Sends a progress notification to the client.
Parameters
server
- The server processprogress_token
- The progress token (string or integer)progress
- The current progress valuetotal
- Optional total value for the operationmessage
- Optional message describing the current progress (2025-03-26 spec)
@spec send_prompts_list_changed(GenServer.name()) :: :ok | {:error, term()}
Sends a prompts list changed notification to the client.
This notification informs the client that the list of available prompts has changed.
The server must have declared the prompts.listChanged
capability.
@spec send_resource_updated(GenServer.name(), String.t(), String.t() | nil) :: :ok | {:error, term()}
Sends a resource updated notification to the client.
This notification informs the client that a specific resource has been updated.
Parameters
server
- The server processuri
- The URI of the updated resourcetitle
- Optional human-readable title for the resource
@spec send_resources_list_changed(GenServer.name()) :: :ok | {:error, term()}
Sends a resources list changed notification to the client.
This notification informs the client that the list of available resources has changed.
The server must have declared the resources.listChanged
capability.
@spec send_tools_list_changed(GenServer.name()) :: :ok | {:error, term()}
Sends a tools list changed notification to the client.
This notification informs the client that the list of available tools has changed.
The server must have declared the tools.listChanged
capability.
@spec start_link(Enumerable.t(option())) :: GenServer.on_start()
Starts a new MCP server process.
Parameters
opts
- Keyword list of options::module
- (required) The module implementing theHermes.Server.Behaviour
:init_arg
- Argument to pass to the module'sinit/2
callback:name
- (required) Name for the GenServer processregistry
- The custom registry module to use to call related processes:transport
- (required) Transport configuration:layer
- The transport module (e.g.,Hermes.Server.Transport.STDIO
):name
- The registered name of the transport process
Examples
# Start with explicit transport configuration
Hermes.Server.Base.start_link(
module: MyServer,
init_arg: [],
name: {:via, Registry, {MyRegistry, :my_server}},
transport: [
layer: Hermes.Server.Transport.STDIO,
name: {:via, Registry, {MyRegistry, :my_transport}}
]
)
# Typical usage through Hermes.Server.Supervisor
Hermes.Server.Supervisor.start_link(MyServer, [], transport: :stdio)