barrel_mcp_client_handler behaviour (barrel_mcp v2.0.2)

View Source

Behaviour for handling server-initiated MCP messages.

A host application implements this module to react to requests the server sends *to* the client (per the declared client capabilities) and to notifications the server emits.

Capabilities and the matching callbacks:

  • sampling — server may call sampling/createMessage to ask the host to run an LLM completion.
  • roots — server may call roots/list to enumerate the filesystem boundaries the host exposes.
  • elicitation — server may call elicitation/create to prompt the user for a value.

Notifications cover the rest of the spec: notifications/cancelled, notifications/progress, notifications/resources/updated, notifications/resources/list_changed, notifications/tools/list_changed, notifications/prompts/list_changed, notifications/message.

A default implementation in barrel_mcp_client_handler_default returns method_not_found for every request and ignores every notification, so a host only writes callbacks for capabilities it actually declares.

Async replies: when answering a request requires a long-running operation (e.g. an HTTP call to an LLM provider), return {async, Tag, State} from handle_request/3 and later send barrel_mcp_client:reply_async(ClientPid, Tag, Result) from any process. The client's state machine will not block while the handler is computing.

Example

A handler that answers sampling/createMessage synchronously:

   -module(my_sampler).
   -behaviour(barrel_mcp_client_handler).
   -export([init/1, handle_request/3, handle_notification/3,
            terminate/2]).
  
   init(Args) -> {ok, Args}.
  
   handle_request(<<"sampling/createMessage">>, Params, S) ->
       Reply = call_my_llm(Params),  %% your provider integration
       Result = #{<<"content">> => #{<<"type">> => <<"text">>,
                                      <<"text">> => Reply},
                  <<"model">> => <<"my-model">>,
                  <<"role">> => <<"assistant">>},
       {reply, Result, S};
   handle_request(Method, _Params, S) ->
       {error, -32601, <<"Method not found: ", Method/binary>>, S}.
  
   handle_notification(_Method, _Params, S) -> {ok, S}.
   terminate(_Reason, _State) -> ok.

Wire the handler in via the connect spec: #{handler => {my_sampler, []}}.

See examples/sampling_host/ for a runnable end-to-end version.

Summary

Types

async_tag/0

-type async_tag() :: term().

state/0

-type state() :: term().

Callbacks

handle_notification/3

-callback handle_notification(Method :: binary(), Params :: map(), State :: state()) -> {ok, state()}.

handle_request/3

-callback handle_request(Method :: binary(), Params :: map(), State :: state()) ->
                            {reply, Result :: term(), state()} |
                            {error, Code :: integer(), Message :: binary(), state()} |
                            {async, async_tag(), state()}.

init/1

-callback init(Args :: term()) -> {ok, state()} | {error, term()}.

terminate/2

(optional)
-callback terminate(Reason :: term(), State :: state()) -> any().