MCP Client (reference)
View SourceLooking for a walkthrough? Read Building a client for the task-oriented guide; this file is the older API reference and stays for cross linking. The new guide covers the gen_statem API, OAuth, handlers, federation, and runnable examples.
barrel_mcp includes a client library for connecting to external MCP servers. This allows your Erlang application to consume tools, resources, and prompts from other MCP-compatible services.
Connecting to a Server
HTTP Transport
%% Connect to an HTTP MCP server
{ok, Client} = barrel_mcp_client:connect(#{
transport => {http, <<"http://localhost:9090/mcp">>}
}).
%% With custom options
{ok, Client} = barrel_mcp_client:connect(#{
transport => {http, <<"https://api.example.com/mcp">>},
timeout => 30000, %% Request timeout in ms
headers => #{
<<"Authorization">> => <<"Bearer your-token">>
}
}).stdio Transport
For connecting to local MCP servers via stdin/stdout:
{ok, Client} = barrel_mcp_client:connect(#{
transport => {stdio, "/path/to/mcp-server", ["--arg1", "--arg2"]}
}).Initializing the Connection
After connecting, initialize to exchange capabilities:
{ok, Client} = barrel_mcp_client:connect(#{
transport => {http, <<"http://localhost:9090/mcp">>}
}),
{ok, ServerInfo, Client1} = barrel_mcp_client:initialize(Client),
%% ServerInfo contains:
%% #{
%% <<"protocolVersion">> => <<"2024-11-05">>,
%% <<"serverInfo">> => #{
%% <<"name">> => <<"example-server">>,
%% <<"version">> => <<"1.0.0">>
%% },
%% <<"capabilities">> => #{
%% <<"tools">> => #{},
%% <<"resources">> => #{},
%% <<"prompts">> => #{}
%% }
%% }Working with Tools
List Available Tools
{ok, Tools, Client2} = barrel_mcp_client:list_tools(Client1),
%% Tools is a list of tool definitions:
%% [
%% #{
%% <<"name">> => <<"search">>,
%% <<"description">> => <<"Search for information">>,
%% <<"inputSchema">> => #{...}
%% },
%% ...
%% ]Call a Tool
{ok, Result, Client3} = barrel_mcp_client:call_tool(Client2, <<"search">>, #{
<<"query">> => <<"erlang mcp">>
}),
%% Result contains the tool output:
%% #{
%% <<"content">> => [
%% #{<<"type">> => <<"text">>, <<"text">> => <<"Results...">>}
%% ]
%% }Error Handling
case barrel_mcp_client:call_tool(Client, <<"unknown">>, #{}) of
{ok, Result, NewClient} ->
process_result(Result);
{error, {method_not_found, _}, NewClient} ->
logger:warning("Tool not found"),
handle_missing_tool();
{error, Reason, NewClient} ->
logger:error("Tool call failed: ~p", [Reason]),
handle_error(Reason)
end.Working with Resources
List Resources
{ok, Resources, Client2} = barrel_mcp_client:list_resources(Client1),
%% Resources list:
%% [
%% #{
%% <<"uri">> => <<"file:///config">>,
%% <<"name">> => <<"Configuration">>,
%% <<"mimeType">> => <<"application/json">>
%% },
%% ...
%% ]Read a Resource
{ok, Content, Client3} = barrel_mcp_client:read_resource(Client2, <<"file:///config">>),
%% Content structure:
%% #{
%% <<"contents">> => [
%% #{
%% <<"uri">> => <<"file:///config">>,
%% <<"text">> => <<"{\"key\": \"value\"}">>
%% }
%% ]
%% }Working with Prompts
List Prompts
{ok, Prompts, Client2} = barrel_mcp_client:list_prompts(Client1),
%% Prompts list:
%% [
%% #{
%% <<"name">> => <<"summarize">>,
%% <<"description">> => <<"Summarize content">>,
%% <<"arguments">> => [
%% #{<<"name">> => <<"content">>, <<"required">> => true}
%% ]
%% },
%% ...
%% ]Get a Prompt
{ok, PromptResult, Client3} = barrel_mcp_client:get_prompt(Client2, <<"summarize">>, #{
<<"content">> => <<"Long text to summarize...">>
}),
%% PromptResult contains messages:
%% #{
%% <<"messages">> => [
%% #{
%% <<"role">> => <<"user">>,
%% <<"content">> => #{
%% <<"type">> => <<"text">>,
%% <<"text">> => <<"Please summarize...">>
%% }
%% }
%% ]
%% }Connection Management
Closing Connections
Always close connections when done:
ok = barrel_mcp_client:close(Client).Connection State
The client maintains state that must be threaded through calls:
%% Pattern: Always use the returned client for subsequent calls
{ok, Client1} = barrel_mcp_client:connect(Opts),
{ok, _, Client2} = barrel_mcp_client:initialize(Client1),
{ok, Tools, Client3} = barrel_mcp_client:list_tools(Client2),
{ok, Result, Client4} = barrel_mcp_client:call_tool(Client3, <<"tool">>, #{}),
ok = barrel_mcp_client:close(Client4).Using with gen_server
-module(my_mcp_worker).
-behaviour(gen_server).
-export([start_link/1, call_tool/2]).
-export([init/1, handle_call/3, handle_cast/2, terminate/2]).
start_link(Opts) ->
gen_server:start_link(?MODULE, Opts, []).
call_tool(Pid, {ToolName, Args}) ->
gen_server:call(Pid, {call_tool, ToolName, Args}).
init(Opts) ->
{ok, Client} = barrel_mcp_client:connect(Opts),
{ok, _, Client1} = barrel_mcp_client:initialize(Client),
{ok, #{client => Client1}}.
handle_call({call_tool, Name, Args}, _From, #{client := Client} = State) ->
case barrel_mcp_client:call_tool(Client, Name, Args) of
{ok, Result, NewClient} ->
{reply, {ok, Result}, State#{client := NewClient}};
{error, Reason, NewClient} ->
{reply, {error, Reason}, State#{client := NewClient}}
end.
handle_cast(_Msg, State) ->
{noreply, State}.
terminate(_Reason, #{client := Client}) ->
barrel_mcp_client:close(Client),
ok.Authentication
Bearer Token
{ok, Client} = barrel_mcp_client:connect(#{
transport => {http, <<"https://api.example.com/mcp">>},
headers => #{
<<"Authorization">> => <<"Bearer eyJhbGciOiJIUzI1NiIs...">>
}
}).API Key
{ok, Client} = barrel_mcp_client:connect(#{
transport => {http, <<"https://api.example.com/mcp">>},
headers => #{
<<"X-API-Key">> => <<"your-api-key">>
}
}).Basic Auth
Credentials = base64:encode(<<"user:password">>),
{ok, Client} = barrel_mcp_client:connect(#{
transport => {http, <<"https://api.example.com/mcp">>},
headers => #{
<<"Authorization">> => <<"Basic ", Credentials/binary>>
}
}).Error Handling
Common errors and how to handle them:
case barrel_mcp_client:call_tool(Client, Name, Args) of
{ok, Result, NewClient} ->
{ok, Result, NewClient};
{error, {http_error, 401}, NewClient} ->
%% Authentication failed - refresh token and retry
NewHeaders = refresh_auth_token(),
retry_with_new_auth(NewClient, NewHeaders);
{error, {http_error, 404}, NewClient} ->
%% Endpoint not found
{error, server_not_found, NewClient};
{error, {http_error, 500}, NewClient} ->
%% Server error - maybe retry
{error, server_error, NewClient};
{error, timeout, NewClient} ->
%% Request timed out
{error, timeout, NewClient};
{error, {method_not_found, _}, NewClient} ->
%% Tool/resource/prompt doesn't exist
{error, not_found, NewClient};
{error, Reason, NewClient} ->
%% Other errors
logger:error("MCP client error: ~p", [Reason]),
{error, Reason, NewClient}
end.Best Practices
- Always thread client state - Each operation returns an updated client
- Close connections - Use
close/1or handle interminate/2 - Handle errors - MCP servers may be unavailable or return errors
- Set appropriate timeouts - Especially for slow operations
- Cache tool/resource lists - Don't fetch on every call if they don't change