AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified Apache-2.0 Self-run

Barrel Mcp

mcp-barrel-platform-barrel-mcp · by barrel-platform

MCP (Model Context Protocol) server library for Erlang

No reviews yet
0 installs
9 views
0.0% view→install

Install

$ agentstack add mcp-barrel-platform-barrel-mcp

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-barrel-platform-barrel-mcp)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
25d ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Barrel Mcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 json module.

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_readyready for flexible initialization
  • Postpone pattern: Calls in not_ready state 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.

Versions

  • v0.1.0 Imported from the upstream source.