RFC: Hermes MCP Server Implementation
Introduction
This RFC proposes a server-side implementation for the Model Context Protocol (MCP) within the Hermes MCP library. Currently, Hermes MCP provides client-side functionality, and this proposal aims to extend the library with a robust, OTP-compliant server implementation that follows Elixir best practices.
Background
The Model Context Protocol (MCP) enables seamless integration between Language Model (LLM) applications and external data sources and tools. A complete MCP implementation requires both client and server components. The Hermes MCP library currently implements the client side, supporting various transports (STDIO, HTTP/SSE, Streamable HTTP) and providing robust message handling, logging, and telemetry.
Goals and Requirements
The server implementation should:
- Fully comply with the MCP specification (version 2025-03-26 AND 2024-11-05)
- Maintain backward compatibility with older protocol versions
- Follow OTP principles for robust, fault-tolerant operation and distribution
- Integrate with existing Hermes components (telemetry, logging, transport, JSON-RPC encoding/decoding)
- Support multiple transports (STDIO, HTTP/SSE, Stremable HTTP)
- Provide both low-level and high-level APIs for server implementation
- Support session management and distribution via Erlang clustering
- Handle authentication (particularly OAuth 2.1 for HTTP transports)
- Include proper error handling and logging
Non-Goals
The implementation will not:
- Define a complex DSL for server definition (relying instead on decorators and behaviors)
- Aim for 100% feature parity with non-Elixir implementations
- Support non-standard transports or protocol extensions
Detailed Design
Core Architecture
The server implementation will follow a layered architecture:
- Transport Layer: Handles communication protocols (STDIO, HTTP/SSE)
- Protocol Layer: Processes JSON-RPC messages according to MCP specification
- Server Layer: Implements the core server behavior and state management
- Component Layer: Implements MCP primitives (tools, resources, prompts)
Module Structure
lib/hermes/server/
├── supervisor.ex # Application and supervision
├── base.ex # Base server implementation
├── behaviour.ex # Core behavior definitions
├── server.ex # High-level server implementation
├── registry.ex # Session and server registry
├── decorator.ex # Component decorators
├── component/ # Component behaviors
│ ├── tool.ex # Tool behavior and implementation
│ ├── resource.ex # Resource behavior and implementation
│ └── prompt.ex # Prompt behavior and implementation
├── session/ # Session management
│ ├── registry.ex # Session tracking
│ └── state.ex # Session state management
└── transport/ # Server-specific transport handlers
├── stdio.ex # STDIO server transport
└── sse.ex # SSE server transport
Behavior Definitions
Core Server Behavior
defmodule Hermes.Server.Behaviour do
@moduledoc """
Defines the core behavior that all MCP servers must implement.
"""
@type state :: term()
@type request :: map()
@type response :: map()
@type notification :: map()
@type mcp_error :: Hermes.MCP.Error.t()
@type server_info :: %{name: String.t(), version: String.t()}
@doc """
Initializes the server state.
"""
@callback init(init_arg :: term()) ::
{:ok, state()} | {:error, reason :: term()}
@doc """
Handles incoming requests from clients.
"""
@callback handle_request(request :: request(), state :: state()) ::
{:reply, response :: response(), new_state :: state()}
| {:noreply, new_state :: state()}
| {:error, error :: mcp_error(), new_state :: state()}
@doc """
Handles incoming notifications from clients.
"""
@callback handle_notification(notification :: notification(), state :: state()) ::
{:noreply, new_state :: state()}
| {:error, error :: mcp_error(), new_state :: state()}
@doc """
Returns server information for initialization response.
"""
@callback server_info() :: server_info()
@doc """
Returns server capabilities for initialization response.
Optional callback with default implementation.
"""
@callback server_capabilities() :: map()
@optional_callbacks [server_capabilities: 0]
end
Component Behaviors
defmodule Hermes.Server.Component.Tool do
@moduledoc """
Defines the behavior for MCP tools.
"""
@type params :: map()
@type context :: map()
@type result :: any()
@type parameter_schema :: map()
@callback name() :: String.t()
@callback description() :: String.t()
@callback parameters() :: [parameter_schema()]
@callback handle(params :: params(), context :: context()) ::
{:ok, result :: result()} | {:error, reason :: String.t()}
end
defmodule Hermes.Server.Component.Resource do
@moduledoc """
Defines the behavior for MCP resources.
"""
@callback uri() :: String.t()
@callback name() :: String.t()
@callback description() :: String.t()
@callback mime_type() :: String.t()
@callback read(params :: map(), context :: map()) ::
{:ok, content :: binary() | String.t()} | {:error, reason :: String.t()}
end
defmodule Hermes.Server.Component.Prompt do
@moduledoc """
Defines the behavior for MCP prompts.
"""
@callback name() :: String.t()
@callback description() :: String.t()
@callback arguments() :: [map()]
@callback get(args :: map(), context :: map()) ::
{:ok, messages :: [map()]} | {:error, reason :: String.t()}
end
Base Server Implementation
defmodule Hermes.Server.Base do
@moduledoc """
Base implementation of an MCP server.
This module provides the core functionality for handling MCP messages,
without any higher-level abstractions.
"""
alias Hermes.MCP.{Message, Error, ID}
alias Hermes.Telemetry
@behaviour GenServer
# Default protocol version
@protocol_version "2025-03-26"
# Server state structure
defmodule State do
defstruct [
:mod, # Server module implementing the behavior
:transport, # Transport module/pid
:init_args, # Initial arguments
:server_info, # Server information
:capabilities, # Server capabilities
:custom_state, # Custom state from the implementing module
:initialized # Whether the server has been initialized
]
end
def start_link(module, init_args, opts) do
GenServer.start_link(__MODULE__, {module, init_args}, opts)
end
@impl GenServer
def init({module, init_args}) do
# Validate that module implements the required behavior
unless implements_behaviour?(module, Hermes.Server.Behaviour) do
raise ArgumentError, "Module #{inspect(module)} does not implement Hermes.Server.Behaviour"
end
# Get server info and capabilities
server_info = module.server_info()
capabilities = if function_exported?(module, :server_capabilities, 0) do
module.server_capabilities()
else
%{}
end
state = %State{
mod: module,
init_args: init_args,
server_info: server_info,
capabilities: capabilities,
initialized: false
}
case module.init(init_args) do
{:ok, custom_state} ->
{:ok, %{state | custom_state: custom_state}}
{:error, reason} ->
{:stop, reason}
end
end
@impl GenServer
def handle_call({:message, message}, _from, state) do
# Handle incoming messages based on type
with {:ok, decoded} <- decode_message(message) do
handle_decoded_message(decoded, state)
else
{:error, reason} ->
# Handle invalid message
{:reply, {:error, %Error{code: -32700, message: "Parse error", data: reason}}, state}
end
end
@impl GenServer
def handle_cast({:notification, notification}, state) do
# Handle incoming notifications
with {:ok, decoded} <- decode_message(notification) do
handle_decoded_notification(decoded, state)
else
{:error, _reason} ->
# Just ignore invalid notifications
{:noreply, state}
end
end
# Implementation details for message handling, decoding, etc.
# ...
defp handle_decoded_message(%{"method" => "initialize"} = request, %{initialized: false} = state) do
# Handle initialization request
params = request["params"] || %{}
protocol_version = params["protocolVersion"] || @protocol_version
# Build initialization response
response = %{
"protocolVersion" => protocol_version,
"serverInfo" => state.server_info,
"capabilities" => state.capabilities
}
# Update state to indicate initialization
new_state = %{state | initialized: true}
{:reply, {:ok, response}, new_state}
end
defp handle_decoded_message(request, %{initialized: true, mod: module} = state) do
# Dispatch request to the implementing module
case module.handle_request(request, state.custom_state) do
{:reply, response, new_custom_state} ->
{:reply, {:ok, response}, %{state | custom_state: new_custom_state}}
{:noreply, new_custom_state} ->
{:reply, :ok, %{state | custom_state: new_custom_state}}
{:error, error, new_custom_state} ->
{:reply, {:error, error}, %{state | custom_state: new_custom_state}}
end
end
defp handle_decoded_message(_request, %{initialized: false} = state) do
# Reject non-initialization requests when not initialized
{:reply, {:error, %Error{code: -32600, message: "Server not initialized"}}, state}
end
defp handle_decoded_notification(%{"method" => "notifications/initialized"}, state) do
# Handle initialized notification
{:noreply, %{state | initialized: true}}
end
defp handle_decoded_notification(notification, %{initialized: true, mod: module} = state) do
# Dispatch notification to the implementing module
case module.handle_notification(notification, state.custom_state) do
{:noreply, new_custom_state} ->
{:noreply, %{state | custom_state: new_custom_state}}
{:error, _error, new_custom_state} ->
# We ignore errors in notifications
{:noreply, %{state | custom_state: new_custom_state}}
end
end
defp handle_decoded_notification(_notification, state) do
# Ignore notifications when not initialized
{:noreply, state}
end
defp decode_message(message) when is_binary(message) do
Message.decode(message)
end
defp decode_message(message) when is_map(message) do
{:ok, message}
end
defp implements_behaviour?(module, behaviour) do
behaviours = Keyword.take(module.__info__(:attributes), [:behaviour])
|> Keyword.values()
|> List.flatten()
Enum.member?(behaviours, behaviour)
end
end
Decorator System
defmodule Hermes.Server.Decorator do
@moduledoc """
Decorators for MCP server components.
"""
use Decorator.Define, [tool: 0, resource: 0, prompt: 0]
@doc """
Marks a function as an MCP tool.
The function will be registered as a tool when the server is initialized.
The function must have a @spec defining its parameter and return types,
which will be used to generate the tool's schema.
## Example
@decorate tool()
@spec calculate(operation :: String.t(), x :: number(), y :: number()) :: number()
def calculate(operation, x, y) do
case operation do
"add" -> x + y
"subtract" -> x - y
"multiply" -> x * y
"divide" when y != 0 -> x / y
"divide" -> raise "Cannot divide by zero"
end
end
"""
def tool(body, context) do
Module.put_attribute(context.module, :mcp_tools, {context.name, context.arity})
body
end
@doc """
Marks a function as an MCP resource.
The function will be registered as a resource when the server is initialized.
## Example
@decorate resource()
@spec readme() :: {:ok, String.t()} | {:error, String.t()}
def readme do
case File.read("README.md") do
{:ok, content} -> {:ok, content}
{:error, reason} -> {:error, "Failed to read README: #{reason}"}
end
end
"""
def resource(body, context) do
Module.put_attribute(context.module, :mcp_resources, {context.name, context.arity})
body
end
@doc """
Marks a function as an MCP prompt.
The function will be registered as a prompt when the server is initialized.
## Example
@decorate prompt()
@spec greeting(name :: String.t()) :: [map()]
def greeting(name \\ nil) do
greeting = if name, do: "Hello, #{name}!", else: "Hello!"
[
%{
role: "assistant",
content: %{
type: "text",
text: greeting
}
}
]
end
"""
def prompt(body, context) do
Module.put_attribute(context.module, :mcp_prompts, {context.name, context.arity})
body
end
end
High-Level Server Implementation
defmodule Hermes.Server do
@moduledoc """
High-level MCP server implementation with decorator support.
This module extends the base server with support for decorators
and automatic handling of common requests.
"""
@doc """
Creates a new MCP server module.
## Options
- `:name` - Server name (default: "Hermes MCP Server")
- `:version` - Server version (default: "1.0.0")
"""
defmacro __using__(opts) do
server_name = Keyword.get(opts, :name, "Hermes MCP Server")
server_version = Keyword.get(opts, :version, "1.0.0")
quote do
@behaviour Hermes.Server.Behaviour
use Decorator, decorators: [Hermes.Server.Decorator]
Module.register_attribute(__MODULE__, :mcp_tools, accumulate: true)
Module.register_attribute(__MODULE__, :mcp_resources, accumulate: true)
Module.register_attribute(__MODULE__, :mcp_prompts, accumulate: true)
@before_compile Hermes.Server
@impl Hermes.Server.Behaviour
def server_info do
%{
name: unquote(server_name),
version: unquote(server_version)
}
end
@impl Hermes.Server.Behaviour
def server_capabilities do
%{
"tools" => %{},
"resources" => %{},
"prompts" => %{}
}
end
@impl Hermes.Server.Behaviour
def handle_request(%{"method" => "tools/list"} = request, state) do
tools = fetch_mcp_tools(__MODULE__)
response = %{
"tools" => tools
}
{:reply, response, state}
end
@impl Hermes.Server.Behaviour
def handle_request(%{"method" => "resources/list"} = request, state) do
resources = fetch_mcp_resources(__MODULE__)
response = %{
"resources" => resources
}
{:reply, response, state}
end
@impl Hermes.Server.Behaviour
def handle_request(%{"method" => "prompts/list"} = request, state) do
prompts = fetch_mcp_prompts(__MODULE__)
response = %{
"prompts" => prompts
}
{:reply, response, state}
end
@impl Hermes.Server.Behaviour
def handle_request(%{"method" => "tools/call", "params" => params} = request, state) do
tool_name = params["name"]
arguments = params["arguments"]
case call_tool(__MODULE__, tool_name, arguments, state) do
{:ok, result, new_state} ->
response = %{
"content" => [
%{
"type" => "text",
"text" => "#{inspect(result)}"
}
],
"isError" => false
}
{:reply, response, new_state}
{:error, reason, new_state} ->
response = %{
"content" => [
%{
"type" => "text",
"text" => "Error: #{reason}"
}
],
"isError" => true
}
{:reply, response, new_state}
end
end
@impl Hermes.Server.Behaviour
def handle_request(request, state) do
# Default implementation - return error
{:error, %Hermes.MCP.Error{
code: -32601,
message: "Method not found: #{request["method"]}"
}, state}
end
@impl Hermes.Server.Behaviour
def handle_notification(_notification, state) do
# Default implementation - ignore notification
{:noreply, state}
end
defoverridable [server_capabilities: 0, handle_request: 2, handle_notification: 2]
end
end
defmacro __before_compile__(_env) do
quote do
# Add helper functions for introspection
def __mcp_tools__ do
@mcp_tools
end
def __mcp_resources__ do
@mcp_resources
end
def __mcp_prompts__ do
@mcp_prompts
end
end
end
# Helper functions for fetching and calling MCP components
# ...
end
Session Management
defmodule Hermes.Server.Session do
@moduledoc """
Manages MCP server sessions.
"""
use GenServer
alias Hermes.MCP.ID
defmodule State do
defstruct [
:id, # Session ID
:server, # Server module or pid
:transport, # Transport module or pid
:created_at, # Creation timestamp
:last_activity_at, # Last activity timestamp
:client_info, # Client information
:capabilities # Client capabilities
]
end
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
@impl GenServer
def init(opts) do
server = Keyword.fetch!(opts, :server)
transport = Keyword.fetch!(opts, :transport)
state = %State{
id: ID.generate(),
server: server,
transport: transport,
created_at: DateTime.utc_now(),
last_activity_at: DateTime.utc_now()
}
{:ok, state}
end
# Implementation details for session management
# ...
end
Transport Integration
The existing transport modules will need to be adapted for server use. There are two main approaches:
- Bidirectional Adaptation: Modify existing transports to support both client and server roles
- Server-Specific Transports: Create new transport modules specifically for server use
We'll implement server-specific transports to avoid modifying existing client functionality, focusing on the STDIO transport example:
defmodule Hermes.Server.Transport.STDIO do
@moduledoc """
STDIO transport implementation for MCP servers.
"""
use GenServer
alias Hermes.MCP.{Message, Error}
alias Hermes.Telemetry
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
@impl GenServer
def init(opts) do
server = Keyword.fetch!(opts, :server)
# Setup IO for stdin/stdout
:ok = :io.setopts(standard_error: true)
Process.flag(:trap_exit, true)
state = %{
server: server,
buffer: ""
}
# Start reading from stdin
schedule_read()
{:ok, state}
end
@impl GenServer
def handle_info(:read_stdin, state) do
case IO.read(:stdio, :line) do
:eof ->
# End of input, terminate
{:stop, :normal, state}
{:error, reason} ->
# Error reading from stdin, terminate
{:stop, reason, state}
data when is_binary(data) ->
# Process incoming data
updated_buffer = state.buffer <> data
{messages, remaining} = extract_messages(updated_buffer)
# Forward complete messages to server
Enum.each(messages, &forward_to_server(&1, state.server))
# Schedule next read
schedule_read()
{:noreply, %{state | buffer: remaining}}
end
end
@impl GenServer
def handle_cast({:send, message}, state) do
# Write message to stdout
IO.write(:stdio, message)
{:noreply, state}
end
# Private helper functions
defp schedule_read do
send(self(), :read_stdin)
end
defp extract_messages(buffer) do
case String.split(buffer, "\n", parts: 2) do
[message, rest] ->
# We have a complete message
{remaining_messages, final_rest} = extract_messages(rest)
{[message | remaining_messages], final_rest}
[incomplete] ->
# No complete message yet
{[], incomplete}
end
end
defp forward_to_server(message, server) do
spawn fn ->
case Message.decode(message) do
{:ok, [decoded]} ->
if Message.is_request(decoded) do
GenServer.call(server, {:message, decoded})
else
GenServer.cast(server, {:notification, decoded})
end
{:error, reason} ->
# Log error but don't crash
Telemetry.execute(
Telemetry.event_transport_error(),
%{},
%{error: reason, message: message}
)
end
end
end
end
Similar implementations would be created for the HTTP/SSE/Stremable HTTP.
Server Registration and Discovery
A registry system will track active servers and sessions:
defmodule Hermes.Server.Registry do
@moduledoc """
Registry for tracking MCP servers and sessions.
"""
use GenServer
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl GenServer
def init(_opts) do
# Create ETS tables for tracking servers and sessions
:ets.new(:mcp_servers, [:set, :protected, :named_table])
:ets.new(:mcp_sessions, [:set, :protected, :named_table])
{:ok, %{}}
end
@doc """
Registers a new MCP server.
"""
def register_server(server_pid, server_info) do
GenServer.call(__MODULE__, {:register_server, server_pid, server_info})
end
@doc """
Registers a new MCP session.
"""
def register_session(session_id, session_info) do
GenServer.call(__MODULE__, {:register_session, session_id, session_info})
end
# Implementation details for registry functions
# ...
end
Error Handling and Telemetry
Error handling and telemetry will be integrated into all components, using existing modules where possible:
# Add server-specific telemetry events
defmodule Hermes.Telemetry do
# Existing events...
# Server events
def event_server_init, do: [:server, :init]
def event_server_request, do: [:server, :request]
def event_server_response, do: [:server, :response]
def event_server_tool_call, do: [:server, :tool_call]
def event_server_resource_read, do: [:server, :resource_read]
def event_server_prompt_get, do: [:server, :prompt_get]
end
Integration with Existing Code
Message Handling
We'll leverage the existing Hermes.MCP.Message
module for message validation and encoding/decoding, possibly extending it with server-specific functions as needed:
# Extension to Hermes.MCP.Message
defmodule Hermes.MCP.Message do
@moduledoc """
Server-specific message handling extensions.
"""
# rest of module
@doc """
Creates a tool list response.
"""
def create_tool_list_response(tools, cursor \\ nil) do
response = %{
"tools" => tools
}
if cursor do
Map.put(response, "nextCursor", cursor)
else
response
end
end
# Similar functions for other response types
# ...
end
Telemetry Integration
We'll extend the existing telemetry system with server-specific events and spans:
# In server implementation
Telemetry.execute(
Telemetry.event_server_request(),
%{duration: duration},
%{method: method, id: id}
)
Server Supervision
The server supervision tree will ensure robust operation:
defmodule Hermes.Server.Supervisor do
@moduledoc false
use Supervisor
def start_link(_) do
Supervisor.start_link(__MODULE__, :ok)
end
@impl true
def init(_) do
children = [
Hermes.Server.Registry,
Hermes.Server.Session.Supervisor
]
Supervisor.init(children, strategy: :one_for_one, name: __MODULE__)
end
end
defmodule Hermes.Server.Session.Supervisor do
@moduledoc """
Supervisor for MCP server sessions.
"""
use DynamicSupervisor
def start_link(init_args) do
DynamicSupervisor.start_link(__MODULE__, init_args, name: __MODULE__)
end
@impl DynamicSupervisor
def init(_init_args) do
DynamicSupervisor.init(strategy: :one_for_one)
end
@doc """
Starts a new session process.
"""
def start_session(server, transport, opts \\ []) do
DynamicSupervisor.start_child(__MODULE__, {Hermes.Server.Session, [
server: server,
transport: transport
] ++ opts})
end
end
Usage Examples
Low-Level API
defmodule MyApp.MCPServer do
@behaviour Hermes.Server.Behaviour
@impl true
def init(_args) do
{:ok, %{counter: 0}}
end
@impl true
def server_info do
%{
name: "My MCP Server",
version: "1.0.0"
}
end
@impl true
def server_capabilities do
%{
"tools" => %{
"listChanged" => true
}
}
end
@impl true
def handle_request(%{"method" => "tools/list"}, state) do
response = %{
"tools" => [
%{
"name" => "counter",
"description" => "Get or increment counter",
"inputSchema" => %{
"type" => "object",
"properties" => %{
"increment" => %{"type" => "boolean"}
}
}
}
]
}
{:reply, response, state}
end
@impl true
def handle_request(%{"method" => "tools/call", "params" => %{"name" => "counter", "arguments" => args}}, state) do
increment = Map.get(args, "increment", false)
{value, new_state} = if increment do
counter = state.counter + 1
{counter, %{state | counter: counter}}
else
{state.counter, state}
end
response = %{
"content" => [
%{
"type" => "text",
"text" => "Counter value: #{value}"
}
],
"isError" => false
}
{:reply, response, new_state}
end
@impl true
def handle_request(request, state) do
# Fallback for unhandled requests
{:error, %Hermes.MCP.Error{
code: -32601,
message: "Method not implemented: #{request["method"]}"
}, state}
end
@impl true
def handle_notification(_notification, state) do
# Ignore all notifications
{:noreply, state}
end
end
# Start the server with STDIO transport
{:ok, _pid} = Hermes.Server.Base.start_link(MyApp.MCPServer, [], [])
{:ok, _transport} = Hermes.Server.Transport.STDIO.start_link(server: server)
High-Level API (with Decorators)
defmodule MyApp.MCPServer do
use Hermes.Server, name: "My MCP Server", version: "1.0.0"
@impl true
def init(_args) do
{:ok, %{counter: 0}}
end
@decorate tool()
@spec increment_counter(current :: integer()) :: integer()
def increment_counter(current) do
current + 1
end
@decorate tool()
@spec get_counter() :: integer()
def get_counter(state) do
state.counter
end
@decorate resource()
@spec readme() :: {:ok, String.t()} | {:error, String.t()}
def readme do
case File.read("README.md") do
{:ok, content} -> {:ok, content}
{:error, reason} -> {:error, "Failed to read README: #{reason}"}
end
end
@decorate prompt()
@spec greeting(name :: String.t()) :: [map()]
def greeting(name \\ nil) do
greeting = if name, do: "Hello, #{name}!", else: "Hello!"
[
%{
role: "assistant",
content: %{
type: "text",
text: greeting
}
}
]
end
end
# Start the server with STDIO transport
{:ok, server} = Hermes.Server.start_link(MyApp.MCPServer)
{:ok, _transport} = Hermes.Server.Transport.STDIO.start_link(server: server)
Distribution Support
The server implementation will support distribution via Erlang clustering:
# Register servers globally to support distribution
{:ok, server} = Hermes.Server.start_link(MyApp.MCPServer, [], [name: {:global, :my_mcp_server}])
# Access server from any node in the cluster
GenServer.call({:global, :my_mcp_server}, {:message, request})
Authentication
For HTTP transports, we'll support OAuth 2.1 authentication as specified in the MCP spec:
defmodule Hermes.Server.Auth.OAuth do
@moduledoc """
OAuth 2.1 authentication for MCP servers.
"""
# Implementation details for OAuth authentication
# ...
end
Implementation Roadmap
The implementation will proceed in phases:
Phase 1: Core Server Implementation
- Implement
Hermes.Server.Base
module - Create basic server behavior
- Adapt STDIO transport for server use
- Implement minimal request handling
- Add telemetry and logging integration
Phase 2: Component System
- Implement component behaviors (Tool, Resource, Prompt)
- Create decorator system for component registration
- Implement high-level server with component support
- Add schema generation from typespecs
Phase 3: Transport and Distribution
- Implement HTTP/SSE server transport
- Add session management
- Implement registry and discovery
- Add distribution support
Phase 4: Advanced Features
- Implement OAuth authentication for HTTP transports
- Add support for newer protocol features (audio content, batch requests)
- Optimize performance for high-load scenarios
- Add telemetry dashboards and monitoring
Compatibility Considerations
Backward Compatibility
The implementation will support older protocol versions:
- Protocol version 2024-11-05
- Future protocol versions through capability negotiation
WARNING
We should implement the server to be compliant to the latest MCP spec (2025-03-26) and then backward compatible with the first spec version (2024-11-05)
Client Compatibility
The server implementation will be tested with the existing Hermes MCP client to ensure compatibility.
Testing Strategy
The implementation will include:
- Unit tests for each component
- Integration tests with existing client
- Protocol compliance tests
- Performance benchmarks
- Distributed system tests
Conclusion
This detailed design provides a comprehensive plan for implementing a robust, OTP-compliant MCP server within the Hermes MCP library. The implementation leverages existing components where possible while introducing new server-specific modules and behaviors.
The decorator-based approach provides a clean, maintainable way to define server components without excessive macro usage or DSLs. The layered architecture ensures separation of concerns and follows Elixir best practices.
The proposed roadmap offers an incremental approach to implementation, focusing first on core functionality before adding more advanced features.