Install
$ agentstack add mcp-barrel-platform-barrel-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
barrel_mcp
MCP (Model Context Protocol) library for Erlang. Implements the MCP specification (protocol 2025-11-25 with downward negotiation through 2024-11-05) for both server and client modes, including the Streamable HTTP transport for Claude Code and any other MCP client.
Features
- Full MCP Protocol: tools, resources, resource templates
(with runtime RFC 6570 expansion on resources/read), prompts, completions, sampling, tasks (long-running operations), _meta extension hook end-to-end, notifications (*/list_changed, progress, cancelled, resources/updated, tasks/status, replay_truncated).
- Tool handlers: arity 1 or arity 2 (
(Args, Ctx)) with
Ctx-driven progress, cancel, and _meta hooks. Return shapes: plain content, {tool_error, ...} (→ isError: true), {structured, Data, ...} (→ structuredContent), or any of the meta-bearing variants that attach _meta to the response.
- Schema validation: opt-in
validate_input/
validate_output against registered JSON Schemas (barrel_mcp_schema).
- Transports: Streamable HTTP (Claude Code), legacy HTTP,
stdio (Claude Desktop). The HTTP server is built on h1/h2 (HTTP/1.1 + HTTP/2 on one port via ALPN) — no Cowboy. Streamable HTTP defaults to 127.0.0.1, validates Origin, and replays SSE events via Last-Event-ID.
- Authentication: bearer (JWT/opaque), API keys (peppered
HMAC-SHA-256), basic (PBKDF2-SHA256), custom providers. Constant-time hash comparison; legacy SHA-256 hex digests still verify for one release. RFC 9728 Protected Resource Metadata endpoint with spec-correct WWW-Authenticate for OAuth client auto-discovery.
- Client library (
barrel_mcp_client): supervised
gen_statem with stdio + Streamable HTTP transports, OAuth 2.1
- PKCE, federation registry (one connection per server id),
pagination, schema pre-flight.
- Zero JSON dependency: uses OTP 27+ built-in
jsonmodule.
Installation
Add to your rebar.config:
{deps, [
{barrel_mcp,
{git, "https://github.com/barrel-platform/barrel_mcp.git",
{tag, "v2.2.0"}}}
]}.
Track main instead of pinning a tag for the latest fixes:
{barrel_mcp,
{git, "https://github.com/barrel-platform/barrel_mcp.git",
{branch, "main"}}}
Architecture
barrelmcp uses a supervised genstatem process to manage the handler registry:
- Writes (reg/unreg) go through the gen_statem for atomic operations
- Reads (find/all/run) use persistent_term directly for O(1) lookups
- States:
not_ready→readyfor flexible initialization - Postpone pattern: Calls in
not_readystate are postponed until ready
┌─────────────────────────────────────────────────────────────────┐
│ barrel_mcp_sup │
│ (supervisor) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ barrel_mcp_registry │
│ (gen_statem) │
│ │
│ States: not_ready ──────────────────► ready │
│ │ (self ! ready) │
│ │ or │
│ └──── wait for external process ────► │
│ │
│ ┌─────────────┐ ┌─────────────────────────────────────┐ │
│ │ ETS Table │───────►│ persistent_term (read-only) │ │
│ │ (authority) │ sync │ O(1) lookups │ │
│ └─────────────┘ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▲ │
│ reg/unreg │ find/all/run
│ (atomic, postponed │ (lock-free)
│ if not ready) │
Configuration
To make the registry wait for an external process before becoming ready:
%% In sys.config or application env
{barrel_mcp, [
{wait_for_proc, my_init_process} % Wait for this process to be registered
]}.
If wait_for_proc is not set, the registry becomes ready immediately after init.
Usage by role
barrel_mcp covers the three MCP roles in one library:
- server — exposes tools, resources, prompts to MCP clients.
- client — connects to one MCP server, calls tools, reads
resources, handles server-initiated requests.
- host (agent) — drives one or more clients on behalf of an
LLM; collects each server's tool catalog, hands it to the model, routes the model's tool call back through the right client.
The three short examples below cover the typical wiring; deeper guides live under guides/ (getting-started.md, tools-resources-prompts.md, building-a-client.md).
Server — expose a tool over Streamable HTTP
-module(my_server).
-export([start/0, search/1]).
start() ->
{ok, _} = application:ensure_all_started(barrel_mcp),
ok = barrel_mcp:reg_tool(>, ?MODULE, search, #{
description => >,
input_schema => #{> => >,
> => [>]}
}),
{ok, _} = barrel_mcp:start_http_stream(#{port => 8080,
session_enabled => true}),
ok.
search(#{> := Q}) ->
iolist_to_binary([>, Q]).
That's a complete MCP server. Point any MCP client (Claude Code, Claude Desktop via stdio, the barrel_mcp_client below, …) at http://127.0.0.1:8080/mcp.
Client — connect and call a tool
client_demo() ->
{ok, _} = application:ensure_all_started(barrel_mcp),
{ok, Pid} = barrel_mcp_client:start(#{
transport => {http, >}
}),
{ok, Result} = barrel_mcp_client:call_tool(
Pid, >, #{> => >}),
barrel_mcp_client:close(Pid),
Result.
The transport tuple selects the wire ({http, Url}, {stdio, [Cmd | Args]}). Auth and OAuth options live on the same spec — see guides/building-a-client.md.
Host (agent) — hand many MCP servers to an LLM
agent_loop() ->
{ok, _} = application:ensure_all_started(barrel_mcp),
{ok, _} = barrel_mcp:start_client(>, #{
transport => {http, >},
auth => {bearer, GhToken}
}),
{ok, _} = barrel_mcp:start_client(>, #{
transport => {stdio, ["mcp-shell-server"]}
}),
%% Hand every connected server's tools to the model:
AnthropicTools = barrel_mcp_agent:to_anthropic(),
%% ... call the LLM with AnthropicTools and capture the
%% tool_use block it returned ...
Block = ask_llm(AnthropicTools),
{NsName, Args} = barrel_mcp_tool_format:from_anthropic_call(Block),
%% Routes "github:..." to the github client, "shell:..." to
%% the shell client.
barrel_mcp_agent:call_tool(NsName, Args).
barrel_mcp_agent namespaces tool names as > across the federation. barrel_mcp_tool_format translates between MCP tool maps and the provider shapes (Anthropic Messages API, OpenAI Chat Completions); swap to_anthropic/0 and from_anthropic_call/1 for the OpenAI counterparts to use a different model. ask_llm/1 is your own LLM HTTP call — barrel_mcp does not bundle an LLM SDK.
Quick Start
Starting the Application
%% Start barrel_mcp application
application:ensure_all_started(barrel_mcp).
%% Wait for registry to be ready (optional, for custom initialization)
ok = barrel_mcp_registry:wait_for_ready().
Registering Tools
%% Register a tool
barrel_mcp:reg_tool(>, my_module, search, #{
description => >,
input_schema => #{
type => >,
properties => #{
query => #{type => >, description => >},
limit => #{type => >, default => 10}
},
required => [>]
}
}).
%% Your handler function (must accept a map and be exported with arity 1)
-module(my_module).
-export([search/1]).
search(#{> := Query} = Args) ->
Limit = maps:get(>, Args, 10),
%% Return binary, map, or list of content blocks
>.
Registering Resources
barrel_mcp:reg_resource(>, my_module, get_config, #{
name => >,
uri => >,
description => >,
mime_type => >
}).
Registering Prompts
barrel_mcp:reg_prompt(>, my_module, summarize_prompt, #{
description => >,
arguments => [
#{name => >, description => >, required => true},
#{name => >, description => >, required => false}
]
}).
%% Handler returns prompt messages
summarize_prompt(Args) ->
Content = maps:get(>, Args),
#{
description => >,
messages => [
#{role => >, content => #{type => >, text => Content}}
]
}.
Starting Streamable HTTP Server (Claude Code)
For Claude Code integration, use the Streamable HTTP transport:
%% Start Streamable HTTP server on port 9090
{ok, _} = barrel_mcp:start_http_stream(#{port => 9090}).
%% With API key authentication
{ok, _} = barrel_mcp:start_http_stream(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_apikey,
provider_opts => #{
keys => #{> => #{subject => >}}
}
}
}).
Then add to Claude Code:
claude mcp add my-server --transport http http://localhost:9090/mcp \
--header "X-API-Key: my-key"
See guides/http-stream.md for full documentation.
Starting HTTP Server (Legacy)
%% Start HTTP server on port 9090
{ok, _} = barrel_mcp:start_http(#{port => 9090}).
%% Or with custom IP binding
{ok, _} = barrel_mcp:start_http(#{port => 9090, ip => {127, 0, 0, 1}}).
Authentication
barrel_mcp provides pluggable authentication following OAuth 2.1 patterns as recommended by the MCP specification. Authentication is optional and configurable per HTTP server.
Built-in Providers
| Provider | Description | |----------|-------------| | barrel_mcp_auth_none | No authentication (default) | | barrel_mcp_auth_bearer | Bearer token (JWT or opaque) | | barrel_mcp_auth_apikey | API key authentication | | barrel_mcp_auth_basic | HTTP Basic authentication | | barrel_mcp_auth_custom | Custom auth module (simple interface) |
Bearer Token (JWT) Authentication
%% Start HTTP server with JWT authentication
{ok, _} = barrel_mcp:start_http(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_bearer,
provider_opts => #{
secret => >,
issuer => >,
audience => >,
clock_skew => 60 % seconds
},
required_scopes => [>, >]
}
}).
For RS256/ES256 or opaque tokens, use a custom verifier:
%% Custom token verifier (e.g., for token introspection)
Verifier = fun(Token) ->
case my_auth_service:validate(Token) of
{ok, Claims} -> {ok, Claims};
error -> {error, invalid_token}
end
end,
{ok, _} = barrel_mcp:start_http(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_bearer,
provider_opts => #{verifier => Verifier}
}
}).
API Key Authentication
%% Simple API key list
{ok, _} = barrel_mcp:start_http(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_apikey,
provider_opts => #{
keys => #{
> => #{subject => >, scopes => [>]},
> => #{subject => >, scopes => [>, >]}
}
}
}
}).
%% With hashed keys for security (recommended for production)
HashedKey = barrel_mcp_auth_apikey:hash_key(>),
{ok, _} = barrel_mcp:start_http(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_apikey,
provider_opts => #{
keys => #{HashedKey => #{subject => >}},
hash_keys => true
}
}
}).
Basic Authentication
%% Simple username/password
{ok, _} = barrel_mcp:start_http(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_basic,
provider_opts => #{
credentials => #{
> => >,
> => >
},
realm => >
}
}
}).
%% With hashed passwords (recommended)
HashedPwd = barrel_mcp_auth_basic:hash_password(>),
{ok, _} = barrel_mcp:start_http(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_basic,
provider_opts => #{
credentials => #{> => HashedPwd},
hash_passwords => true
}
}
}).
Custom Authentication (Simple Interface)
For integrating with existing auth systems, use barrel_mcp_auth_custom with a simple two-function module:
-module(my_auth).
-export([init/1, authenticate/2]).
init(_Opts) ->
{ok, #{}}.
authenticate(Token, State) ->
case my_key_store:validate(Token) of
{ok, Info} ->
{ok, #{subject => Info}, State};
error ->
{error, invalid_token, State}
end.
Configure it:
{ok, _} = barrel_mcp:start_http(#{
port => 9090,
auth => #{
provider => barrel_mcp_auth_custom,
provider_opts => #{
module => my_auth
}
}
}).
See guides/custom-authentication.md for full documentation.
Custom Authentication Provider (Full Behaviour)
For more control, implement the full barrel_mcp_auth behaviour:
-module(my_auth_provider).
-behaviour(barrel_mcp_auth).
-export([init/1, authenticate/2, challenge/2]).
init(Opts) ->
{ok, Opts}.
authenticate(Request, State) ->
Headers = maps:get(headers, Request, #{}),
case barrel_mcp_auth:extract_bearer_token(Headers) of
{ok, Token} ->
%% Your validation logic
case validate_with_my_service(Token) of
{ok, User} ->
{ok, #{
subject => User,
scopes => [>],
claims => #{}
}};
error ->
{error, invalid_token}
end;
{error, no_token} ->
{error, unauthorized}
end.
challenge(Reason, _State) ->
{401, #{> => >}, >}.
Accessing Auth Info in Handlers
Authentication info is available in the request context:
my_tool_handler(Args) ->
%% Auth info is passed in the _auth key
case maps:get(>, Args, undefined) of
undefined ->
>;
AuthInfo ->
Subject = maps:get(subject, AuthInfo),
>
end.
Starting stdio Server (for Claude Desktop)
%% This blocks and handles MCP over stdin/stdout
barrel_mcp:start_stdio().
Using as Client
barrel_mcp_client is a gen_statem. Start it, wait for the handshake to complete, call tools.
{ok, Pid} = barrel_mcp_client:start_link(#{
transport => {http, >}
}),
{ok, Tools} = barrel_mcp_client:list_tools(Pid),
{ok, Result} = barrel_mcp_client:call_tool(Pid, >,
#{> => >}),
ok = barrel_mcp_client:close(Pid).
For the full task-oriented walkthrough — transport choice, auth, OAuth, server-to-client handlers, federation, schema validation — see [Building a client](guides/building-a-client.md). For architecture and behaviour contracts, see [Internals](guides/internals.md). Three runnable examples live under examples/ on GitHub (echo_client, sampling_host, agent_host).
Claude Desktop Configuration
When using barrel_mcp with stdio transport for Claude Desktop:
{
"mcpServers": {
"my-server": {
"comman
…
## Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [barrel-platform](https://github.com/barrel-platform)
- **Source:** [barrel-platform/barrel_mcp](https://github.com/barrel-platform/barrel_mcp)
- **License:** Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.