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

  1. Transport receives raw message from client
  2. Transport calls Base with {:message, data, session_id}
  3. Base decodes and validates the message
  4. Base retrieves or creates session state
  5. Base delegates to implementation module callbacks
  6. 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

Types

MCP server options

t()

Functions

Returns a specification to start this module under a supervisor.

Sends a notification to the client.

Starts a new MCP server process.

Types

option()

@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

t()

@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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

get_schema(atom)

parse_options(data)

parse_options!(data)

send_notification(server, method, params \\ %{})

@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 PID
  • method - 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"}
)

start_link(opts)

@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 the Hermes.Server.Behaviour
    • :init_arg - Argument to pass to the module's init/2 callback
    • :name - (required) Name for the GenServer process
    • registry - The custom registry module to use to call related processes
    • :transport - (required) Transport configuration

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)