Current section

Files

Jump to
hermes_mcp lib hermes protocol.ex
Raw

lib/hermes/protocol.ex

defmodule Hermes.Protocol do
@moduledoc """
Protocol version management and feature validation for MCP.
This module handles protocol version compatibility, feature detection,
and transport validation following the MCP specification.
## Protocol Versions
- `2024-11-05`: Initial stable release with SSE and basic features
- `2025-03-26`: Enhanced version with Streamable HTTP, authorization, batching, and extended features
## Examples
iex> Hermes.Protocol.validate_transport("2024-11-05", Hermes.Transport.SSE)
:ok
iex> Hermes.Protocol.validate_transport("2024-11-05", Hermes.Transport.StreamableHTTP)
{:error, %Hermes.MCP.Error{reason: :incompatible_transport}}
iex> Hermes.Protocol.supports_feature?("2025-03-26", :json_rpc_batching)
true
"""
alias Hermes.MCP.Error
@type version :: String.t()
@type feature :: atom()
@supported_versions ["2024-11-05", "2025-03-26"]
@latest_version "2025-03-26"
@fallback_version "2024-11-05"
# Features supported by each protocol version
@features_2024_11_05 MapSet.new([
:basic_messaging,
:resources,
:tools,
:prompts,
:logging,
:progress,
:cancellation,
:ping,
:roots,
:sampling
])
@features_2025_03_26 MapSet.new([
:basic_messaging,
:resources,
:tools,
:prompts,
:logging,
:progress,
:cancellation,
:ping,
:roots,
:sampling,
# New features in 2025-03-26
:json_rpc_batching,
:authorization,
:audio_content,
:tool_annotations,
:progress_messages,
:completion_capability
])
@doc """
Returns all supported protocol versions.
## Examples
iex> Hermes.Protocol.supported_versions()
["2024-11-05", "2025-03-26"]
"""
@spec supported_versions() :: [version()]
def supported_versions, do: @supported_versions
@doc """
Returns the latest supported protocol version.
## Examples
iex> Hermes.Protocol.latest_version()
"2025-03-26"
"""
@spec latest_version() :: version()
def latest_version, do: @latest_version
@doc """
Returns the fallback protocol version for compatibility.
## Examples
iex> Hermes.Protocol.fallback_version()
"2024-11-05"
"""
@spec fallback_version() :: version()
def fallback_version, do: @fallback_version
@doc """
Validates if a protocol version is supported.
## Parameters
* `version` - The protocol version to validate
## Examples
iex> Hermes.Protocol.validate_version("2024-11-05")
:ok
iex> Hermes.Protocol.validate_version("1.0.0")
{:error, %Hermes.MCP.Error{reason: :unsupported_protocol_version}}
"""
@spec validate_version(version()) :: :ok | {:error, Error.t()}
def validate_version(version) when version in @supported_versions, do: :ok
def validate_version(version) do
{:error,
Error.protocol(:invalid_params, %{
version: version,
supported: @supported_versions
})}
end
@doc """
Validates if a transport is compatible with a protocol version.
## Parameters
* `version` - The protocol version
* `transport_module` - The transport module to validate
## Examples
iex> Hermes.Protocol.validate_transport("2024-11-05", Hermes.Transport.SSE)
:ok
iex> Hermes.Protocol.validate_transport("2024-11-05", Hermes.Transport.StreamableHTTP)
{:error, %Hermes.MCP.Error{reason: :incompatible_transport}}
"""
@spec validate_transport(version(), module()) :: :ok | {:error, Error.t()}
def validate_transport(version, transport_module) do
supported_versions = transport_module.supported_protocol_versions()
if version in supported_versions do
:ok
else
{:error,
Error.transport(:incompatible_transport, %{
version: version,
transport: transport_module,
supported_versions: supported_versions
})}
end
end
@doc """
Returns the set of features supported by a protocol version.
## Parameters
* `version` - The protocol version
## Examples
iex> features = Hermes.Protocol.get_features("2025-03-26")
iex> MapSet.member?(features, :json_rpc_batching)
true
"""
@spec get_features(version()) :: MapSet.t(feature())
def get_features("2024-11-05"), do: @features_2024_11_05
def get_features("2025-03-26"), do: @features_2025_03_26
def get_features(_), do: MapSet.new()
@doc """
Checks if a feature is supported by a protocol version.
## Parameters
* `version` - The protocol version
* `feature` - The feature to check
## Examples
iex> Hermes.Protocol.supports_feature?("2025-03-26", :json_rpc_batching)
true
iex> Hermes.Protocol.supports_feature?("2024-11-05", :authorization)
false
"""
@spec supports_feature?(version(), feature()) :: boolean()
def supports_feature?(version, feature) do
version
|> get_features()
|> MapSet.member?(feature)
end
@doc """
Negotiates protocol version between client and server versions.
Returns the best compatible version or an error if incompatible.
## Parameters
* `client_version` - The client's preferred protocol version
* `server_version` - The server's supported protocol version
## Examples
iex> Hermes.Protocol.negotiate_version("2025-03-26", "2025-03-26")
{:ok, "2025-03-26"}
iex> Hermes.Protocol.negotiate_version("2025-03-26", "2024-11-05")
{:ok, "2024-11-05"}
iex> Hermes.Protocol.negotiate_version("2024-11-05", "1.0.0")
{:error, %Hermes.MCP.Error{reason: :incompatible_versions}}
"""
@spec negotiate_version(version(), version()) ::
{:ok, version()} | {:error, Error.t()}
def negotiate_version(client_version, server_version) do
cond do
client_version == server_version and client_version in @supported_versions ->
{:ok, client_version}
server_version in @supported_versions ->
{:ok, server_version}
client_version in @supported_versions ->
{:ok, client_version}
true ->
{:error,
Error.protocol(:invalid_params, %{
client_version: client_version,
server_version: server_version,
supported: @supported_versions
})}
end
end
@doc """
Returns transport modules that support a protocol version.
## Parameters
* `version` - The protocol version
* `transport_modules` - List of transport modules to check
## Examples
iex> transports = [Hermes.Transport.STDIO, Hermes.Transport.SSE]
iex> Hermes.Protocol.compatible_transports("2024-11-05", transports)
[Hermes.Transport.STDIO, Hermes.Transport.SSE]
"""
@spec compatible_transports(version(), [module()]) :: [module()]
def compatible_transports(version, transport_modules) do
Enum.filter(transport_modules, fn transport_module ->
case validate_transport(version, transport_module) do
:ok -> true
{:error, _} -> false
end
end)
end
@doc """
Validates client configuration for protocol compatibility.
This function checks if the client configuration is compatible with
the specified protocol version, including transport and capabilities.
## Parameters
* `version` - The protocol version
* `transport_module` - The transport module being used
* `capabilities` - The client capabilities
## Examples
iex> capabilities = %{"resources" => %{}, "tools" => %{}}
iex> Hermes.Protocol.validate_client_config("2024-11-05", Hermes.Transport.SSE, capabilities)
:ok
"""
@spec validate_client_config(version(), module(), map()) ::
:ok | {:error, Error.t()}
def validate_client_config(version, transport_module, _capabilities) do
with :ok <- validate_version(version) do
validate_transport(version, transport_module)
end
end
end