{"id":49646110,"url":"https://github.com/barrel-platform/barrel_mcp","last_synced_at":"2026-05-06T00:11:39.443Z","repository":{"id":346671370,"uuid":"1191076141","full_name":"barrel-platform/barrel_mcp","owner":"barrel-platform","description":"MCP (Model Context Protocol) server library for Erlang","archived":false,"fork":false,"pushed_at":"2026-05-02T09:36:01.000Z","size":172,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-02T09:37:14.298Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/barrel-platform.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-24T22:33:44.000Z","updated_at":"2026-05-02T09:36:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/barrel-platform/barrel_mcp","commit_stats":null,"previous_names":["barrel-platform/barrel_mcp"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/barrel-platform/barrel_mcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barrel-platform%2Fbarrel_mcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barrel-platform%2Fbarrel_mcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barrel-platform%2Fbarrel_mcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barrel-platform%2Fbarrel_mcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/barrel-platform","download_url":"https://codeload.github.com/barrel-platform/barrel_mcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barrel-platform%2Fbarrel_mcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32672735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-06T00:11:32.536Z","updated_at":"2026-05-06T00:11:39.437Z","avatar_url":"https://github.com/barrel-platform.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# barrel_mcp\n\nMCP (Model Context Protocol) library for Erlang. Implements the\nMCP specification (protocol `2025-11-25` with downward negotiation\nthrough `2024-11-05`) for both server and client modes, including\nthe Streamable HTTP transport for Claude Code and any other MCP\nclient.\n\n## Features\n\n- **Full MCP Protocol**: tools, resources, resource templates,\n  prompts, completions, sampling, **tasks** (long-running\n  operations), notifications (`*/list_changed`, `progress`,\n  `cancelled`, `resources/updated`, `tasks/changed`,\n  `replay_truncated`).\n- **Tool handlers**: arity 1 or arity 2 (`(Args, Ctx)`) with\n  `Ctx`-driven progress and cancel hooks. Return shapes:\n  plain content, `{tool_error, ...}` (→ `isError: true`), or\n  `{structured, Data, ...}` (→ `structuredContent`).\n- **Schema validation**: opt-in `validate_input` /\n  `validate_output` against registered JSON Schemas\n  (`barrel_mcp_schema`).\n- **Transports**: Streamable HTTP (Claude Code), legacy HTTP\n  (Cowboy), stdio (Claude Desktop). Streamable HTTP defaults to\n  `127.0.0.1`, validates `Origin`, and replays SSE events via\n  `Last-Event-ID`.\n- **Authentication**: bearer (JWT/opaque), API keys (peppered\n  HMAC-SHA-256), basic (PBKDF2-SHA256), custom providers.\n  Constant-time hash comparison; legacy SHA-256 hex digests still\n  verify for one release.\n- **Client library** (`barrel_mcp_client`): supervised\n  `gen_statem` with stdio + Streamable HTTP transports, OAuth 2.1\n  + PKCE, federation registry (one connection per server id),\n  pagination, schema pre-flight.\n- **Zero JSON dependency**: uses OTP 27+ built-in `json` module.\n\n## Installation\n\nAdd to your `rebar.config`:\n\n```erlang\n{deps, [\n    {barrel_mcp,\n        {git, \"https://github.com/barrel-platform/barrel_mcp.git\",\n              {tag, \"v1.2.0\"}}}\n]}.\n```\n\nTrack `main` instead of pinning a tag for the latest fixes:\n\n```erlang\n{barrel_mcp,\n    {git, \"https://github.com/barrel-platform/barrel_mcp.git\",\n          {branch, \"main\"}}}\n```\n\n## Architecture\n\nbarrel_mcp uses a supervised gen_statem process to manage the handler registry:\n\n- **Writes** (reg/unreg) go through the gen_statem for atomic operations\n- **Reads** (find/all/run) use persistent_term directly for O(1) lookups\n- **States**: `not_ready` → `ready` for flexible initialization\n- **Postpone pattern**: Calls in `not_ready` state are postponed until ready\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                       barrel_mcp_sup                             │\n│                      (supervisor)                                │\n└─────────────────────────────────────────────────────────────────┘\n                              │\n                              ▼\n┌─────────────────────────────────────────────────────────────────┐\n│                    barrel_mcp_registry                           │\n│                      (gen_statem)                                │\n│                                                                  │\n│  States: not_ready ──────────────────► ready                    │\n│              │         (self ! ready)                            │\n│              │              or                                   │\n│              └──── wait for external process ────►               │\n│                                                                  │\n│  ┌─────────────┐        ┌─────────────────────────────────────┐ │\n│  │  ETS Table  │───────►│     persistent_term (read-only)     │ │\n│  │ (authority) │  sync  │         O(1) lookups                │ │\n│  └─────────────┘        └─────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────┘\n         ▲                              │\n         │ reg/unreg                    │ find/all/run\n         │ (atomic, postponed           │ (lock-free)\n         │  if not ready)               │\n```\n\n### Configuration\n\nTo make the registry wait for an external process before becoming ready:\n\n```erlang\n%% In sys.config or application env\n{barrel_mcp, [\n    {wait_for_proc, my_init_process}  % Wait for this process to be registered\n]}.\n```\n\nIf `wait_for_proc` is not set, the registry becomes ready immediately after init.\n\n## Usage by role\n\nbarrel_mcp covers the three MCP roles in one library:\n\n- **server** — exposes tools, resources, prompts to MCP clients.\n- **client** — connects to one MCP server, calls tools, reads\n  resources, handles server-initiated requests.\n- **host (agent)** — drives one or more clients on behalf of an\n  LLM; collects each server's tool catalog, hands it to the\n  model, routes the model's tool call back through the right\n  client.\n\nThe three short examples below cover the typical wiring; deeper\nguides live under `guides/` (`getting-started.md`,\n`tools-resources-prompts.md`, `building-a-client.md`).\n\n### Server — expose a tool over Streamable HTTP\n\n```erlang\n-module(my_server).\n-export([start/0, search/1]).\n\nstart() -\u003e\n    {ok, _} = application:ensure_all_started(barrel_mcp),\n    ok = barrel_mcp:reg_tool(\u003c\u003c\"search\"\u003e\u003e, ?MODULE, search, #{\n        description =\u003e \u003c\u003c\"Search the index\"\u003e\u003e,\n        input_schema =\u003e #{\u003c\u003c\"type\"\u003e\u003e =\u003e \u003c\u003c\"object\"\u003e\u003e,\n                           \u003c\u003c\"required\"\u003e\u003e =\u003e [\u003c\u003c\"q\"\u003e\u003e]}\n    }),\n    {ok, _} = barrel_mcp:start_http_stream(#{port =\u003e 8080,\n                                              session_enabled =\u003e true}),\n    ok.\n\nsearch(#{\u003c\u003c\"q\"\u003e\u003e := Q}) -\u003e\n    iolist_to_binary([\u003c\u003c\"results for \"\u003e\u003e, Q]).\n```\n\nThat's a complete MCP server. Point any MCP client (Claude Code,\nClaude Desktop via stdio, the `barrel_mcp_client` below, …) at\n`http://127.0.0.1:8080/mcp`.\n\n### Client — connect and call a tool\n\n```erlang\nclient_demo() -\u003e\n    {ok, _} = application:ensure_all_started(barrel_mcp),\n    {ok, Pid} = barrel_mcp_client:start(#{\n        transport =\u003e {http, \u003c\u003c\"http://127.0.0.1:8080/mcp\"\u003e\u003e}\n    }),\n    {ok, Result} = barrel_mcp_client:call_tool(\n                     Pid, \u003c\u003c\"search\"\u003e\u003e, #{\u003c\u003c\"q\"\u003e\u003e =\u003e \u003c\u003c\"hello\"\u003e\u003e}),\n    barrel_mcp_client:close(Pid),\n    Result.\n```\n\nThe transport tuple selects the wire (`{http, Url}`,\n`{stdio, [Cmd | Args]}`). Auth and OAuth options live on the same\nspec — see `guides/building-a-client.md`.\n\n### Host (agent) — hand many MCP servers to an LLM\n\n```erlang\nagent_loop() -\u003e\n    {ok, _} = application:ensure_all_started(barrel_mcp),\n    {ok, _} = barrel_mcp:start_client(\u003c\u003c\"github\"\u003e\u003e, #{\n        transport =\u003e {http, \u003c\u003c\"https://mcp.github.example/mcp\"\u003e\u003e},\n        auth =\u003e {bearer, GhToken}\n    }),\n    {ok, _} = barrel_mcp:start_client(\u003c\u003c\"shell\"\u003e\u003e, #{\n        transport =\u003e {stdio, [\"mcp-shell-server\"]}\n    }),\n    %% Hand every connected server's tools to the model:\n    AnthropicTools = barrel_mcp_agent:to_anthropic(),\n    %% ... call the LLM with AnthropicTools and capture the\n    %%     tool_use block it returned ...\n    Block = ask_llm(AnthropicTools),\n    {NsName, Args} = barrel_mcp_tool_format:from_anthropic_call(Block),\n    %% Routes \"github:...\" to the github client, \"shell:...\" to\n    %% the shell client.\n    barrel_mcp_agent:call_tool(NsName, Args).\n```\n\n`barrel_mcp_agent` namespaces tool names as\n`\u003c\u003c\"ServerId:ToolName\"\u003e\u003e` across the federation.\n`barrel_mcp_tool_format` translates between MCP tool maps and the\nprovider shapes (Anthropic Messages API, OpenAI Chat Completions);\nswap `to_anthropic/0` and `from_anthropic_call/1` for the OpenAI\ncounterparts to use a different model. `ask_llm/1` is your own\nLLM HTTP call — barrel_mcp does not bundle an LLM SDK.\n\n## Quick Start\n\n### Starting the Application\n\n```erlang\n%% Start barrel_mcp application\napplication:ensure_all_started(barrel_mcp).\n\n%% Wait for registry to be ready (optional, for custom initialization)\nok = barrel_mcp_registry:wait_for_ready().\n```\n\n### Registering Tools\n\n```erlang\n%% Register a tool\nbarrel_mcp:reg_tool(\u003c\u003c\"search\"\u003e\u003e, my_module, search, #{\n    description =\u003e \u003c\u003c\"Search for items\"\u003e\u003e,\n    input_schema =\u003e #{\n        type =\u003e \u003c\u003c\"object\"\u003e\u003e,\n        properties =\u003e #{\n            query =\u003e #{type =\u003e \u003c\u003c\"string\"\u003e\u003e, description =\u003e \u003c\u003c\"Search query\"\u003e\u003e},\n            limit =\u003e #{type =\u003e \u003c\u003c\"integer\"\u003e\u003e, default =\u003e 10}\n        },\n        required =\u003e [\u003c\u003c\"query\"\u003e\u003e]\n    }\n}).\n\n%% Your handler function (must accept a map and be exported with arity 1)\n-module(my_module).\n-export([search/1]).\n\nsearch(#{\u003c\u003c\"query\"\u003e\u003e := Query} = Args) -\u003e\n    Limit = maps:get(\u003c\u003c\"limit\"\u003e\u003e, Args, 10),\n    %% Return binary, map, or list of content blocks\n    \u003c\u003c\"Found results for: \", Query/binary\u003e\u003e.\n```\n\n### Registering Resources\n\n```erlang\nbarrel_mcp:reg_resource(\u003c\u003c\"config\"\u003e\u003e, my_module, get_config, #{\n    name =\u003e \u003c\u003c\"Application Config\"\u003e\u003e,\n    uri =\u003e \u003c\u003c\"config://app/settings\"\u003e\u003e,\n    description =\u003e \u003c\u003c\"Application configuration\"\u003e\u003e,\n    mime_type =\u003e \u003c\u003c\"application/json\"\u003e\u003e\n}).\n```\n\n### Registering Prompts\n\n```erlang\nbarrel_mcp:reg_prompt(\u003c\u003c\"summarize\"\u003e\u003e, my_module, summarize_prompt, #{\n    description =\u003e \u003c\u003c\"Summarize content\"\u003e\u003e,\n    arguments =\u003e [\n        #{name =\u003e \u003c\u003c\"content\"\u003e\u003e, description =\u003e \u003c\u003c\"Content to summarize\"\u003e\u003e, required =\u003e true},\n        #{name =\u003e \u003c\u003c\"style\"\u003e\u003e, description =\u003e \u003c\u003c\"Summary style\"\u003e\u003e, required =\u003e false}\n    ]\n}).\n\n%% Handler returns prompt messages\nsummarize_prompt(Args) -\u003e\n    Content = maps:get(\u003c\u003c\"content\"\u003e\u003e, Args),\n    #{\n        description =\u003e \u003c\u003c\"Summarize the following content\"\u003e\u003e,\n        messages =\u003e [\n            #{role =\u003e \u003c\u003c\"user\"\u003e\u003e, content =\u003e #{type =\u003e \u003c\u003c\"text\"\u003e\u003e, text =\u003e Content}}\n        ]\n    }.\n```\n\n### Starting Streamable HTTP Server (Claude Code)\n\nFor Claude Code integration, use the Streamable HTTP transport:\n\n```erlang\n%% Start Streamable HTTP server on port 9090\n{ok, _} = barrel_mcp:start_http_stream(#{port =\u003e 9090}).\n\n%% With API key authentication\n{ok, _} = barrel_mcp:start_http_stream(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_apikey,\n        provider_opts =\u003e #{\n            keys =\u003e #{\u003c\u003c\"my-key\"\u003e\u003e =\u003e #{subject =\u003e \u003c\u003c\"user\"\u003e\u003e}}\n        }\n    }\n}).\n```\n\nThen add to Claude Code:\n\n```bash\nclaude mcp add my-server --transport http http://localhost:9090/mcp \\\n  --header \"X-API-Key: my-key\"\n```\n\nSee `guides/http-stream.md` for full documentation.\n\n### Starting HTTP Server (Legacy)\n\n```erlang\n%% Start HTTP server on port 9090\n{ok, _} = barrel_mcp:start_http(#{port =\u003e 9090}).\n\n%% Or with custom IP binding\n{ok, _} = barrel_mcp:start_http(#{port =\u003e 9090, ip =\u003e {127, 0, 0, 1}}).\n```\n\n## Authentication\n\nbarrel_mcp provides pluggable authentication following OAuth 2.1 patterns as recommended by the MCP specification. Authentication is optional and configurable per HTTP server.\n\n### Built-in Providers\n\n| Provider | Description |\n|----------|-------------|\n| `barrel_mcp_auth_none` | No authentication (default) |\n| `barrel_mcp_auth_bearer` | Bearer token (JWT or opaque) |\n| `barrel_mcp_auth_apikey` | API key authentication |\n| `barrel_mcp_auth_basic` | HTTP Basic authentication |\n| `barrel_mcp_auth_custom` | Custom auth module (simple interface) |\n\n### Bearer Token (JWT) Authentication\n\n```erlang\n%% Start HTTP server with JWT authentication\n{ok, _} = barrel_mcp:start_http(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_bearer,\n        provider_opts =\u003e #{\n            secret =\u003e \u003c\u003c\"your-jwt-secret-key\"\u003e\u003e,\n            issuer =\u003e \u003c\u003c\"https://auth.example.com\"\u003e\u003e,\n            audience =\u003e \u003c\u003c\"https://mcp.example.com\"\u003e\u003e,\n            clock_skew =\u003e 60  % seconds\n        },\n        required_scopes =\u003e [\u003c\u003c\"mcp:read\"\u003e\u003e, \u003c\u003c\"mcp:write\"\u003e\u003e]\n    }\n}).\n```\n\nFor RS256/ES256 or opaque tokens, use a custom verifier:\n\n```erlang\n%% Custom token verifier (e.g., for token introspection)\nVerifier = fun(Token) -\u003e\n    case my_auth_service:validate(Token) of\n        {ok, Claims} -\u003e {ok, Claims};\n        error -\u003e {error, invalid_token}\n    end\nend,\n\n{ok, _} = barrel_mcp:start_http(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_bearer,\n        provider_opts =\u003e #{verifier =\u003e Verifier}\n    }\n}).\n```\n\n### API Key Authentication\n\n```erlang\n%% Simple API key list\n{ok, _} = barrel_mcp:start_http(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_apikey,\n        provider_opts =\u003e #{\n            keys =\u003e #{\n                \u003c\u003c\"key-abc123\"\u003e\u003e =\u003e #{subject =\u003e \u003c\u003c\"user1\"\u003e\u003e, scopes =\u003e [\u003c\u003c\"read\"\u003e\u003e]},\n                \u003c\u003c\"key-xyz789\"\u003e\u003e =\u003e #{subject =\u003e \u003c\u003c\"user2\"\u003e\u003e, scopes =\u003e [\u003c\u003c\"read\"\u003e\u003e, \u003c\u003c\"write\"\u003e\u003e]}\n            }\n        }\n    }\n}).\n\n%% With hashed keys for security (recommended for production)\nHashedKey = barrel_mcp_auth_apikey:hash_key(\u003c\u003c\"my-secret-key\"\u003e\u003e),\n{ok, _} = barrel_mcp:start_http(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_apikey,\n        provider_opts =\u003e #{\n            keys =\u003e #{HashedKey =\u003e #{subject =\u003e \u003c\u003c\"user1\"\u003e\u003e}},\n            hash_keys =\u003e true\n        }\n    }\n}).\n```\n\n### Basic Authentication\n\n```erlang\n%% Simple username/password\n{ok, _} = barrel_mcp:start_http(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_basic,\n        provider_opts =\u003e #{\n            credentials =\u003e #{\n                \u003c\u003c\"admin\"\u003e\u003e =\u003e \u003c\u003c\"password123\"\u003e\u003e,\n                \u003c\u003c\"user\"\u003e\u003e =\u003e \u003c\u003c\"secret\"\u003e\u003e\n            },\n            realm =\u003e \u003c\u003c\"MCP Server\"\u003e\u003e\n        }\n    }\n}).\n\n%% With hashed passwords (recommended)\nHashedPwd = barrel_mcp_auth_basic:hash_password(\u003c\u003c\"my-password\"\u003e\u003e),\n{ok, _} = barrel_mcp:start_http(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_basic,\n        provider_opts =\u003e #{\n            credentials =\u003e #{\u003c\u003c\"admin\"\u003e\u003e =\u003e HashedPwd},\n            hash_passwords =\u003e true\n        }\n    }\n}).\n```\n\n### Custom Authentication (Simple Interface)\n\nFor integrating with existing auth systems, use `barrel_mcp_auth_custom` with a simple two-function module:\n\n```erlang\n-module(my_auth).\n-export([init/1, authenticate/2]).\n\ninit(_Opts) -\u003e\n    {ok, #{}}.\n\nauthenticate(Token, State) -\u003e\n    case my_key_store:validate(Token) of\n        {ok, Info} -\u003e\n            {ok, #{subject =\u003e Info}, State};\n        error -\u003e\n            {error, invalid_token, State}\n    end.\n```\n\nConfigure it:\n\n```erlang\n{ok, _} = barrel_mcp:start_http(#{\n    port =\u003e 9090,\n    auth =\u003e #{\n        provider =\u003e barrel_mcp_auth_custom,\n        provider_opts =\u003e #{\n            module =\u003e my_auth\n        }\n    }\n}).\n```\n\nSee `guides/custom-authentication.md` for full documentation.\n\n### Custom Authentication Provider (Full Behaviour)\n\nFor more control, implement the full `barrel_mcp_auth` behaviour:\n\n```erlang\n-module(my_auth_provider).\n-behaviour(barrel_mcp_auth).\n\n-export([init/1, authenticate/2, challenge/2]).\n\ninit(Opts) -\u003e\n    {ok, Opts}.\n\nauthenticate(Request, State) -\u003e\n    Headers = maps:get(headers, Request, #{}),\n    case barrel_mcp_auth:extract_bearer_token(Headers) of\n        {ok, Token} -\u003e\n            %% Your validation logic\n            case validate_with_my_service(Token) of\n                {ok, User} -\u003e\n                    {ok, #{\n                        subject =\u003e User,\n                        scopes =\u003e [\u003c\u003c\"read\"\u003e\u003e],\n                        claims =\u003e #{}\n                    }};\n                error -\u003e\n                    {error, invalid_token}\n            end;\n        {error, no_token} -\u003e\n            {error, unauthorized}\n    end.\n\nchallenge(Reason, _State) -\u003e\n    {401, #{\u003c\u003c\"www-authenticate\"\u003e\u003e =\u003e \u003c\u003c\"Bearer realm=\\\"mcp\\\"\"\u003e\u003e}, \u003c\u003c\u003e\u003e}.\n```\n\n### Accessing Auth Info in Handlers\n\nAuthentication info is available in the request context:\n\n```erlang\nmy_tool_handler(Args) -\u003e\n    %% Auth info is passed in the _auth key\n    case maps:get(\u003c\u003c\"_auth\"\u003e\u003e, Args, undefined) of\n        undefined -\u003e\n            \u003c\u003c\"No auth info\"\u003e\u003e;\n        AuthInfo -\u003e\n            Subject = maps:get(subject, AuthInfo),\n            \u003c\u003c\"Hello \", Subject/binary\u003e\u003e\n    end.\n```\n\n### Starting stdio Server (for Claude Desktop)\n\n```erlang\n%% This blocks and handles MCP over stdin/stdout\nbarrel_mcp:start_stdio().\n```\n\n### Using as Client\n\n`barrel_mcp_client` is a `gen_statem`. Start it, wait for the\nhandshake to complete, call tools.\n\n```erl\n{ok, Pid} = barrel_mcp_client:start_link(#{\n    transport =\u003e {http, \u003c\u003c\"http://localhost:9090/mcp\"\u003e\u003e}\n}),\n{ok, Tools}  = barrel_mcp_client:list_tools(Pid),\n{ok, Result} = barrel_mcp_client:call_tool(Pid, \u003c\u003c\"search\"\u003e\u003e,\n                                           #{\u003c\u003c\"query\"\u003e\u003e =\u003e \u003c\u003c\"hello\"\u003e\u003e}),\nok = barrel_mcp_client:close(Pid).\n```\n\nFor the full task-oriented walkthrough — transport choice, auth,\nOAuth, server-to-client handlers, federation, schema validation —\nsee [Building a client](guides/building-a-client.md). For\narchitecture and behaviour contracts, see\n[Internals](guides/internals.md). Three runnable examples live\nunder [`examples/` on\nGitHub](https://github.com/barrel-platform/barrel_mcp/tree/main/examples)\n(`echo_client`, `sampling_host`, `agent_host`).\n\n## Claude Desktop Configuration\n\nWhen using barrel_mcp with stdio transport for Claude Desktop:\n\n```json\n{\n  \"mcpServers\": {\n    \"my-server\": {\n      \"command\": \"/path/to/my_app/bin/my_app\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nYour application's entry point should call `barrel_mcp:start_stdio()`.\n\n## API Reference\n\n### Tools\n\n| Function | Description |\n|----------|-------------|\n| `barrel_mcp:reg_tool(Name, Module, Function, Opts)` | Register a tool |\n| `barrel_mcp:unreg_tool(Name)` | Unregister a tool |\n| `barrel_mcp:call_tool(Name, Args)` | Call a tool locally |\n| `barrel_mcp:list_tools()` | List all registered tools |\n\n### Resources\n\n| Function | Description |\n|----------|-------------|\n| `barrel_mcp:reg_resource(Name, Module, Function, Opts)` | Register a resource |\n| `barrel_mcp:unreg_resource(Name)` | Unregister a resource |\n| `barrel_mcp:read_resource(Name)` | Read a resource locally |\n| `barrel_mcp:list_resources()` | List all registered resources |\n\n### Prompts\n\n| Function | Description |\n|----------|-------------|\n| `barrel_mcp:reg_prompt(Name, Module, Function, Opts)` | Register a prompt |\n| `barrel_mcp:unreg_prompt(Name)` | Unregister a prompt |\n| `barrel_mcp:get_prompt(Name, Args)` | Get a prompt locally |\n| `barrel_mcp:list_prompts()` | List all registered prompts |\n\n### Registry\n\n| Function | Description |\n|----------|-------------|\n| `barrel_mcp_registry:start_link()` | Start the registry (called by supervisor) |\n| `barrel_mcp_registry:wait_for_ready()` | Wait for registry to be ready |\n| `barrel_mcp_registry:wait_for_ready(Timeout)` | Wait with custom timeout |\n\n### Server\n\n| Function | Description |\n|----------|-------------|\n| `barrel_mcp:start_http_stream(Opts)` | Start Streamable HTTP server (Claude Code) |\n| `barrel_mcp:stop_http_stream()` | Stop Streamable HTTP server |\n| `barrel_mcp:start_http(Opts)` | Start HTTP server (legacy) |\n| `barrel_mcp:stop_http()` | Stop HTTP server |\n| `barrel_mcp:start_stdio()` | Start stdio server (blocking) |\n\n### Client\n\n| Function | Description |\n|----------|-------------|\n| `barrel_mcp_client:connect(Opts)` | Connect to MCP server |\n| `barrel_mcp_client:initialize(Client)` | Initialize connection |\n| `barrel_mcp_client:list_tools(Client)` | List available tools |\n| `barrel_mcp_client:call_tool(Client, Name, Args)` | Call a tool |\n| `barrel_mcp_client:list_resources(Client)` | List available resources |\n| `barrel_mcp_client:read_resource(Client, Uri)` | Read a resource |\n| `barrel_mcp_client:list_prompts(Client)` | List available prompts |\n| `barrel_mcp_client:get_prompt(Client, Name, Args)` | Get a prompt |\n| `barrel_mcp_client:close(Client)` | Close connection |\n\n### Authentication\n\n| Function | Description |\n|----------|-------------|\n| `barrel_mcp_auth:extract_bearer_token(Headers)` | Extract Bearer token from headers |\n| `barrel_mcp_auth:extract_api_key(Headers, Opts)` | Extract API key from headers |\n| `barrel_mcp_auth:extract_basic_auth(Headers)` | Extract Basic auth credentials |\n| `barrel_mcp_auth_apikey:hash_key(Key)` | Hash an API key (SHA256) |\n| `barrel_mcp_auth_basic:hash_password(Password)` | Hash a password (SHA256) |\n\n## MCP Protocol Support\n\n### Supported Methods\n\n**Lifecycle:**\n- `initialize` / `initialized`\n- `ping`\n\n**Tools:**\n- `tools/list`\n- `tools/call`\n\n**Resources:**\n- `resources/list`\n- `resources/read`\n- `resources/templates/list`\n- `resources/subscribe` / `resources/unsubscribe`\n\n**Prompts:**\n- `prompts/list`\n- `prompts/get`\n\n**Sampling:**\n- `sampling/createMessage`\n\n**Logging:**\n- `logging/setLevel`\n\n## Pending features\n\nDesign notes and scoped sketches for things not yet implemented\nlive in [`docs/pending-features.md`](docs/pending-features.md).\nFirst entry: **Enterprise-Managed Authorization** (the second\nhalf of the MCP `ext-auth` extension — RFC 8693 token-exchange\nchained through an org IdP for SSO-driven MCP access). Open an\nissue if you want one prioritised.\n\n## Development\n\n```bash\n# Compile\nrebar3 compile\n\n# Run tests\nrebar3 eunit\n\n# Dialyzer\nrebar3 dialyzer\n\n# Shell\nrebar3 shell\n```\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbarrel-platform%2Fbarrel_mcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbarrel-platform%2Fbarrel_mcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbarrel-platform%2Fbarrel_mcp/lists"}