Hermes.Server.Base (hermes_mcp v0.9.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 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_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 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)