barrel_mcp_registry (barrel_mcp v2.0.2)

View Source

Handler registry for MCP tools, resources, and prompts.

This module manages the registration and lookup of MCP handlers using a gen_statem for atomic write operations and persistent_term for O(1) read operations.

Architecture

The registry uses a two-tier storage approach:

  • ETS table: Authoritative storage, owned by the gen_statem process
  • persistent_term: Read-only copy for lock-free O(1) lookups

This ensures that write operations are atomic and supervised, while reads are extremely fast and don't block on process calls.

States

The registry has two states:

  • not_ready - Initial state, waiting for initialization signal
  • ready - Accepting registrations and lookups

In not_ready state, all calls are postponed until the registry transitions to ready.

Configuration

The registry can be configured to wait for an external process before becoming ready:

   %% In sys.config
   {barrel_mcp, [
       {wait_for_proc, my_init_process}
   ]}.

If not configured, the registry becomes ready immediately after init.

Summary

Functions

List all handlers grouped by type.

List all handlers of a specific type.

Find a handler by type and name.

Register a handler with default options.

Register a handler with options.

Execute a handler.

Run a completion handler synchronously. Completion handlers are arity 2: (PartialValue, Ctx).

Execute a tool handler asynchronously. Spawns a worker that calls Mod:Fun(Args, Ctx) (when arity 2 is exported) or Mod:Fun(Args) otherwise. The worker reports back to maps:get(reply_to, Ctx) as either

Start the registry server.

Unregister a handler.

Wait for the registry to be ready.

Wait for the registry to be ready with a custom timeout.

Types

handler_type/0

-type handler_type() :: tool | resource | prompt | resource_template | completion.

Functions

all()

-spec all() -> #{handler_type() => [{binary(), map()}]}.

List all handlers grouped by type.

Returns a map with handler types as keys and lists of {Name, Metadata} tuples as values.

Example

  All = barrel_mcp_registry:all(),
  %% Returns:
  %% #{tool => [{<<"search">>, #{...}}],
  %%   resource => [{<<"config">>, #{...}}],
  %%   prompt => []}

all(Type)

-spec all(Type :: handler_type()) -> [{binary(), map()}].

List all handlers of a specific type.

find(Type, Name)

-spec find(Type :: handler_type(), Name :: binary()) -> {ok, map()} | error.

Find a handler by type and name.

Looks up handler metadata without executing it. This is a read operation using persistent_term for O(1) lookup.

reg(Type, Name, Module, Function)

-spec reg(Type, Name, Module, Function) -> ok | {error, term()}
             when Type :: handler_type(), Name :: binary(), Module :: module(), Function :: atom().

Register a handler with default options.

Equivalent to reg(Type, Name, Module, Function, #{}).

See also: reg/5.

reg(Type, Name, Module, Function, Opts)

-spec reg(Type, Name, Module, Function, Opts) -> ok | {error, term()}
             when
                 Type :: handler_type(),
                 Name :: binary(),
                 Module :: module(),
                 Function :: atom(),
                 Opts :: map().

Register a handler with options.

Registers a handler function with the MCP server. The handler will be callable via the corresponding MCP protocol methods (tools/call, resources/read, prompts/get).

This is an atomic operation that goes through the gen_statem.

Options by Type

For tool:

  • description - Tool description
  • input_schema - JSON Schema for input validation

For resource:

  • name - Resource display name
  • uri - Resource URI
  • description - Resource description
  • mime_type - MIME type (default: text/plain)

For prompt:

  • description - Prompt description
  • arguments - List of argument definitions

run(Type, Name, Args)

-spec run(Type, Name, Args) -> {ok, term()} | {error, term()}
             when Type :: handler_type(), Name :: binary(), Args :: map().

Execute a handler.

Looks up and executes a handler with the given arguments. This is a read operation that uses persistent_term directly, bypassing the gen_statem process for maximum performance.

Example

  {ok, Result} = barrel_mcp_registry:run(tool, <<"search">>, #{
       <<"query">> => <<"erlang">>
  }).

run_completion(Key, Value, Ctx)

-spec run_completion(Key :: binary(), Value :: binary(), Ctx :: map()) -> {ok, term()} | {error, term()}.

Run a completion handler synchronously. Completion handlers are arity 2: (PartialValue, Ctx).

run_tool(Name, Args, Ctx)

-spec run_tool(Name :: binary(), Args :: map(), Ctx :: map()) -> {ok, pid()} | {error, term()}.

Execute a tool handler asynchronously. Spawns a worker that calls Mod:Fun(Args, Ctx) (when arity 2 is exported) or Mod:Fun(Args) otherwise. The worker reports back to maps:get(reply_to, Ctx) as either:

  • {tool_result, RequestId, Result} on a normal return
  • {tool_error, RequestId, Content} for {tool_error, _}
  • {tool_failed, RequestId, Reason} on exception
  • {tool_validation_failed, RequestId, Errors} if input validation was enabled and the args didn't match input_schema.

Returns the worker pid.

start_link()

-spec start_link() -> {ok, pid()} | {error, term()}.

Start the registry server.

This is called by the supervisor during application startup. You typically don't need to call this directly.

unreg(Type, Name)

-spec unreg(Type :: handler_type(), Name :: binary()) -> ok.

Unregister a handler.

Removes a previously registered handler. After unregistration, the handler will no longer appear in list operations and calls to it will return not_found errors.

This is an atomic operation that goes through the gen_statem.

wait_for_ready()

-spec wait_for_ready() -> ok | {error, timeout}.

Wait for the registry to be ready.

Blocks until the registry transitions to the ready state. Uses the default timeout of 5 seconds.

This is useful during application startup to ensure the registry is ready before registering handlers.

Example

  application:ensure_all_started(barrel_mcp),
  ok = barrel_mcp_registry:wait_for_ready(),
  %% Now safe to register handlers

See also: wait_for_ready/1.

wait_for_ready(Timeout)

-spec wait_for_ready(Timeout :: timeout()) -> ok | {error, timeout | not_started}.

Wait for the registry to be ready with a custom timeout.