Hermes.Client (hermes_mcp v0.13.0)

High-level DSL for defining MCP (Model Context Protocol) clients.

This module provides an Ecto-like interface for creating MCP clients with minimal boilerplate. By using this module, you get a fully functional MCP client with automatic supervision, transport management, and all standard MCP operations.

Usage

Define a client module:

defmodule MyApp.AnthropicClient do
  use Hermes.Client,
    name: "MyApp",
    version: "1.0.0",
    protocol_version: "2024-11-05",
    capabilities: [:roots, {:sampling, list_changed?: true}]
end

Add it to your supervision tree:

children = [
  {MyApp.AnthropicClient, 
   transport: {:stdio, command: "uvx", args: ["mcp-server-anthropic"]}}
]

Use the client:

{:ok, tools} = MyApp.AnthropicClient.list_tools()
{:ok, result} = MyApp.AnthropicClient.call_tool("search", %{query: "elixir"})

Options

The use macro accepts the following required options:

  • :name - The client name to advertise to the server (string)
  • :version - The client version (string)
  • :protocol_version - The MCP protocol version (string)
  • :capabilities - List of client capabilities (see below)

Capabilities

Capabilities can be specified as:

  • Atoms: :roots, :sampling
  • Tuples with options: {:roots, list_changed?: true}
  • Maps for custom capabilities: %{"custom" => %{"feature" => true}}

Transport Configuration

When starting the client, you must provide transport configuration:

  • {:stdio, command: "cmd", args: ["arg1", "arg2"]}
  • {:sse, base_url: "http://localhost:8000"}
  • {:websocket, url: "ws://localhost:8000/ws"}
  • {:streamable_http, url: "http://localhost:8000/mcp"}

Process Naming

By default, the client process is registered with the module name. You can override this with the :name option in child_spec or start_link:

# Custom atom name
{MyApp.AnthropicClient, name: :my_custom_client, transport: ...}

# For distributed systems with registries (e.g., Horde)
{MyApp.AnthropicClient,
 name: {:via, Horde.Registry, {MyCluster, "client_1"}},
 transport_name: {:via, Horde.Registry, {MyCluster, "transport_1"}},
 transport: ...}

When using via tuples or other non-atom names, you must explicitly provide the :transport_name option. For atom names, the transport is automatically named as Module.concat(ClientName, "Transport").

Summary

Functions

Generates an MCP client module with all necessary functions.

Guard to check if an atom is a valid client capability.

Guard to check if a capability is supported by checking map keys.

Types

capabilities()

@type capabilities() :: [capability() | {capability(), capability_opts()} | map()]

capability()

@type capability() :: :roots | :sampling

capability_opts()

@type capability_opts() :: [{:list_changed?, boolean()}]

Functions

__using__(opts)

(macro)
@spec __using__(keyword()) :: Macro.t()

Generates an MCP client module with all necessary functions.

This macro is used via the use directive and accepts the following options:

  • :name - Client name (required, string)
  • :version - Client version (required, string)
  • :protocol_version - MCP protocol version (required, string)
  • :capabilities - List of capabilities (optional, defaults to empty list)

The macro generates:

  • child_spec/1 - For supervision tree integration
  • start_link/1 - To start the client
  • All MCP operation functions (ping, list_tools, call_tool, etc.)

is_client_capability(capability)

(macro)

Guard to check if an atom is a valid client capability.

is_supported_capability(capabilities, capability)

(macro)

Guard to check if a capability is supported by checking map keys.