# Barrel Mcp

> MCP (Model Context Protocol) server library for Erlang

- **Type:** MCP server
- **Install:** `agentstack add mcp-barrel-platform-barrel-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [barrel-platform](https://agentstack.voostack.com/s/barrel-platform)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [barrel-platform](https://github.com/barrel-platform)
- **Source:** https://github.com/barrel-platform/barrel_mcp

## Install

```sh
agentstack add mcp-barrel-platform-barrel-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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`:

```erlang
{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:

```erlang
{barrel_mcp,
    {git, "https://github.com/barrel-platform/barrel_mcp.git",
          {branch, "main"}}}
```

## Architecture

barrel_mcp uses a supervised gen_statem 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` → `ready` 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:

```erlang
%% 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

```erlang
-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

```erlang
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

```erlang
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

```erlang
%% 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

```erlang
%% 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

```erlang
barrel_mcp:reg_resource(>, my_module, get_config, #{
    name => >,
    uri => >,
    description => >,
    mime_type => >
}).
```

### Registering Prompts

```erlang
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:

```erlang
%% 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:

```bash
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)

```erlang
%% 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

```erlang
%% 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:

```erlang
%% 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

```erlang
%% 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

```erlang
%% 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:

```erlang
-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:

```erlang
{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:

```erlang
-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:

```erlang
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)

```erlang
%% 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.

```erl
{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](https://github.com/barrel-platform/barrel_mcp/tree/main/examples)
(`echo_client`, `sampling_host`, `agent_host`).

## Claude Desktop Configuration

When using barrel_mcp with stdio transport for Claude Desktop:

```json
{
  "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.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-barrel-platform-barrel-mcp
- Seller: https://agentstack.voostack.com/s/barrel-platform
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
