barrel_mcp_registry (barrel_mcp v2.0.2)
View SourceHandler 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 signalready- 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
Functions
-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 => []}
-spec all(Type :: handler_type()) -> [{binary(), map()}].
List all handlers of a specific type.
-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.
-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.
-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 descriptioninput_schema- JSON Schema for input validation
For resource:
name- Resource display nameuri- Resource URIdescription- Resource descriptionmime_type- MIME type (default: text/plain)
For prompt:
description- Prompt descriptionarguments- List of argument definitions
-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">>
}).
-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).
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 matchinput_schema.
Returns the worker pid.
Start the registry server.
This is called by the supervisor during application startup. You typically don't need to call this directly.
-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.
-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 handlersSee also: wait_for_ready/1.
-spec wait_for_ready(Timeout :: timeout()) -> ok | {error, timeout | not_started}.
Wait for the registry to be ready with a custom timeout.