ExMCP.Native (ex_mcp v0.9.0)

View Source

Native BEAM service dispatcher for direct process communication within Elixir clusters.

This module provides ultra-fast, direct process communication leveraging OTP's built-in features:

  • Direct GenServer.call between processes
  • Pluggable service registry (local Registry by default, Horde for distribution)
  • Automatic distribution across BEAM nodes (with Horde adapter)
  • Built-in fault tolerance and monitoring
  • Zero serialization overhead for local calls

Use Cases

  • Trusted Elixir services within the same cluster
  • High-performance internal communication (~15μs local, ~50μs cross-node)
  • Services that need to be supervised and monitored by OTP

Future API Direction

In future versions, native BEAM communication will be available as a transport option for ExMCP.Client, providing a unified API across all communication methods. The current ExMCP.Native API will remain supported but may be superseded.

Performance Characteristics

  • Local calls: ~15μs latency
  • Cross-node calls: ~50μs latency
  • Memory overhead: Single registry entry per service
  • Throughput: Limited only by service processing speed

Examples

# Service registration (typically in init/1)
ExMCP.Native.register_service(:my_tool_service)

# Direct service calls
{:ok, result} = ExMCP.Native.call(:my_service, "list_tools", %{})

# Cross-node calls (automatic)
{:ok, result} = ExMCP.Native.call({:my_service, :"node@host"}, "method", params)

# Fire-and-forget notifications
:ok = ExMCP.Native.notify(:event_service, "resource_updated", %{
  "uri" => "file:///config.json",
  "type" => "modified"
})

Service Discovery

# List all available services across the cluster
services = ExMCP.Native.list_services()

# Check if a service is available
available? = ExMCP.Native.service_available?(:my_service)

Summary

Functions

Calls a service method with the given parameters.

Lists all services registered in the distributed registry.

Sends a notification to a service (fire-and-forget).

Registers a service with the distributed registry.

Checks if a service is available in the distributed registry.

Unregisters a service from the distributed registry.

Types

method()

@type method() :: String.t()

params()

@type params() :: map()

result()

@type result() :: term()

service_id()

@type service_id() :: atom() | {atom(), node()}

Functions

call(service_id, method, params, opts \\ [])

@spec call(service_id(), method(), params(), keyword()) ::
  {:ok, result()} | {:error, term()}

Calls a service method with the given parameters.

Supports both local and cross-node calls transparently through the configured registry adapter.

Options

  • :timeout - Call timeout in milliseconds (default: 5000)
  • :progress_token - Progress token for long-running operations
  • :meta - Additional metadata map

Examples

# Simple call
{:ok, tools} = ExMCP.Native.call(:my_tools, "list_tools", %{})

# Call with timeout and metadata
{:ok, result} = ExMCP.Native.call(
  :my_tools,
  "process_data",
  %{"dataset" => "large_data"},
  timeout: 30_000,
  meta: %{"trace_id" => "abc", "user_id" => "user1"}
)

list_services()

@spec list_services() :: [{atom(), pid(), map()}]

Lists all services registered in the distributed registry.

Returns a list of tuples containing {service_name, pid, metadata}.

notify(service_id, method, params)

@spec notify(service_id(), method(), params()) :: :ok | {:error, term()}

Sends a notification to a service (fire-and-forget).

Notifications do not expect a response and are sent asynchronously.

Examples

:ok = ExMCP.Native.notify(:event_service, "resource_updated", %{
  "uri" => "file:///config.json",
  "type" => "modified"
})

register_service(name)

@spec register_service(atom()) :: :ok

Registers a service with the distributed registry.

This should typically be called in a GenServer's init/1 callback.

Examples

def init(_) do
  ExMCP.Native.register_service(:my_tools)
  {:ok, %{}}
end

service_available?(service_name)

@spec service_available?(atom()) :: boolean()

Checks if a service is available in the distributed registry.

unregister_service(name)

@spec unregister_service(atom()) :: :ok

Unregisters a service from the distributed registry.

This should typically be called in a GenServer's terminate/2 callback.