hex: noizu_mcp · open source · MIT

MCP, native to the BEAM.

A full Model Context Protocol SDK for Elixir — servers and clients, compile-time tool schemas, stdio and Streamable HTTP. Not a JSON-RPC wrapper.

{:noizu_mcp, "~> 0.4.0"} mix.exs
  • 0.4.0on hex
  • 2025-11-25MCP spec (negotiates to 2025-06-18)
  • stdio + Streamable HTTPtransports
  • 1,500+tests

Temporary sandbox · GitHub + Google login · OAuth required

One session. Your assistant, files and Postgres.

Hosted sign-in requires a configured GitHub or Google provider. If sign-in is unavailable, provider setup is still pending; the setup instructions below remain available.

mcp.elixirmcp.dev gives each authorization its own in-memory playground. Sign in with GitHub or Google, approve access, then read documents, edit dummy files and try virtual device controls. Your real files and databases are never connected.

Connect your MCP client

Add https://mcp.elixirmcp.dev/mcp as a remote connector. The browser flow takes you through provider login and MCP consent, using OAuth client registration and PKCE.

Ask: “List the demo files, edit /workspace/notes.txt, then read it back.” The tools operate only on this authorization’s sandbox.

Files and virtual devices

/README.md and /catalog.json are read-only. Create and edit files under /workspace. Change /dev/settings.json or send a message to /dev/echo.

Install the VFS daemon →

Read the same session from Postgres

The local engine and pg_mcp project the session as foreign tables. Reuse one OAuth token across the daemon and engine to see the same notes.

Install the extension and engine →

Sandbox data is temporary and isolated by authorization, not shared with other visitors. Refresh keeps the same session; a new authorization creates a fresh one. Data expires after one hour or a server restart. The /dev endpoints are virtual control files over WebSocket, not operating-system devices or local Unix sockets.

Set up the filesystem sandbox

Use Linux or macOS with Elixir 1.18+, Erlang/OTP, Git and Python 3. The daemon materializes regular local files and synchronizes changes over an authenticated WebSocket; FUSE is not required.

1. Build and install the daemon

git clone https://github.com/noizu-labs-ml/elixir-mcp-lib.git
cd elixir-mcp-lib
git checkout cd6925c6f2e3932dc213b9806c2e3c71b6269d58
cd daemon/mcp_mount
mix deps.get
mix escript.build
mkdir -p "$HOME/.local/bin"
install -m 755 mcp-mount "$HOME/.local/bin/mcp-mount"

2. Sign in, authorize and connect

Review the OAuth helper, then sign in with a configured GitHub or Google provider. Credentials are saved to a private file. /mcp and /vfs are two transports for one protected resource: reuse this token file for both.

curl -fSLo oauth-token.py https://elixirmcp.dev/downloads/oauth-token.py
python3 oauth-token.py --resource /mcp --output "$HOME/.config/elixirmcp/demo-token.json"
mkdir -p "$HOME/elixirmcp-demo"
export MCP_MOUNT_TOKEN="$(python3 -c 'import json,pathlib; print(json.loads((pathlib.Path.home()/".config/elixirmcp/demo-token.json").read_text())["access_token"])')"
# From elixir-mcp-lib/daemon/mcp_mount; keeps the native watcher available:
mix run -e 'McpMount.CLI.main(System.argv())' -- --url wss://mcp.elixirmcp.dev/vfs --mount "$HOME/elixirmcp-demo"
# In another terminal, once the initial sync completes:
cat "$HOME/elixirmcp-demo/README.md"
printf 'Hello from my filesystem\n' > "$HOME/elixirmcp-demo/workspace/notes.txt"
printf '%s' '{"theme":"dark","uppercase_echo":true}' > "$HOME/elixirmcp-demo/dev/settings.json"
printf 'hello device' > "$HOME/elixirmcp-demo/dev/echo"

Run from the source checkout as shown for live write-back on macOS: the escript cannot bundle its native filesystem watcher. Add --ro for a download-only client. Read-only server files stay protected even without that flag.

Settings accept theme (light or dark) and uppercase_echo (true or false). Write a message to /dev/echo and read back the latest response. These controls affect only your sandbox.

Keep the daemon terminal open. It takes an initial snapshot, subscribes to this session’s changes and uploads local edits. Stop with Ctrl+C; downloaded files remain on disk. Access tokens expire within 15 minutes. Use python3 oauth-token.py --refresh --output "$HOME/.config/elixirmcp/demo-token.json", reload MCP_MOUNT_TOKEN and restart the daemon and engine. After the one-hour window, authorize again for a fresh sandbox.

Connect Postgres through the engine

Use a local development Postgres 18 installation with matching pg_config and development headers, Rust and the Elixir prerequisites above. Install the extension as the database administrator, then run the engine on the same machine.

1. Install the Postgres extension

# From the pinned elixir-mcp-lib checkout above:
cd pg/pg_mcp
cargo install --locked [email protected]
cargo pgrx init --pg18 "$(command -v pg_config)"
cargo pgrx install -c "$(command -v pg_config)"

2. Start the engine with your existing session

The pinned source includes HTTP bearer authentication. Use the demo launcher. The launcher reads the same token file as your VFS daemon and binds only to 127.0.0.1:4040.

# From the root of the pinned elixir-mcp-lib checkout:
mix deps.get
curl -fSLo oauth-token.py https://elixirmcp.dev/downloads/oauth-token.py
curl -fSLo demo-engine.exs https://elixirmcp.dev/downloads/demo-engine.exs
# Reuse the SAME token file created for the VFS to share one sandbox.
DEMO_TOKEN_FILE="$HOME/.config/elixirmcp/demo-token.json" mix run --no-halt demo-engine.exs

The local bridge trusts processes on your own machine; keep it on loopback. The hosted upstream always requires OAuth. A separate Claude connector authorization gets a separate sandbox; it does not automatically share this helper’s token.

3. Read your session notes with SQL

Run this once in a local development database. The required URI filter selects the resource to read. Change the notes through the VFS or MCP, then run the SELECT again.

CREATE EXTENSION pg_mcp;
CREATE SERVER elixirmcp_demo FOREIGN DATA WRAPPER mcp_fdw
  OPTIONS (url 'http://127.0.0.1:4040', auth 'none', timeout_ms '15000');
CREATE SCHEMA demo;
IMPORT FOREIGN SCHEMA mcp FROM SERVER elixirmcp_demo INTO demo;
SELECT uri, text FROM demo.resource_contents
  WHERE uri = 'mcp+engine://demo/demo://workspace';

This demo uses a read-through foreign table. For durable local tables, see opt-in PostgreSQL cache synchronization: local indexed reads, SQL write-back and explicit conflict handling for sources that support conditional writes and durable operation replay. The hosted session sandbox does not provide durable synchronization. See the extension guide for supported versions and build details.

Why Elixir MCP

The whole Model Context Protocol surface, expressed in plain Elixir — not a thin wrapper around JSON.

Compile-time tools

Define tools with @mcp annotations and a compile-time schema DSL; JSON Schema (2020-12) is emitted for you via JSV. Schema mistakes die at compile time, not in a model loop at 2am.

Every transport that matters

stdio and Streamable HTTP out of the box, over Plug (Phoenix or standalone Bandit) — server side and client side. Plus a VFS surface over unix sockets and WebSockets.

Fully bidirectional

Sampling, elicitation, roots, OAuth 2.1 with PKCE, resource subscriptions with RFC 6570 templates. The whole MCP surface, not a subset.

Features

Everything a production MCP deployment needs, in one package.

Toolkits & discovery catalogs

Group tools into toolkits and expose discoverable catalogs.

Resources & prompts with completion

Expose resources and prompt templates, with argument completion.

VFS filesystem mounting

Mount virtual filesystems over unix sockets and WebSockets.

OAuth 2.1 resource server + client

Authorization with PKCE, built in — both directions of the flow.

Noizu.MCP.Test in-memory transport

Test servers and clients without sockets or subprocesses.

Concurrent per-session requests

Requests run concurrently within a session, on the BEAM.

Define a tool once.

No hand-written JSON Schema, no protocol plumbing — just Elixir.

defmodule MyApp.Tools.Search do
  use Noizu.MCP.Server.Tool,
    name: "search_docs",                       # module-derived default name
    description: "Full-text search over project documentation",
    annotations: [read_only_hint: true, idempotent_hint: true]

  input do
    field :query, :string, required: true, min_length: 2,
      description: "Search terms"
    field :limit, :integer, min: 1, max: 50, default: 10
    field :scope, :enum, values: [:all, :guides, :api], default: :all
  end

  @impl true
  def call(%{query: query, limit: limit, scope: scope}, _ctx) do
    {:ok, "#{length(run_search(query, limit, scope))} hits"}
  end
end

Compile-time schemas via the declarative DSL — no hand-written JSON Schema.

Before you mix dep

Is this hex elixir_mcp?

No. That package is skill bundling. This product is Elixir MCP; the hex package is noizu_mcp because elixir_mcp was already taken.

Do I need Phoenix?

No. stdio runs from a supervisor. Streamable HTTP is Plug — Phoenix or standalone Bandit.

Is 0.4.0 stable?

Pre-1.0: APIs may still move. Toolset/ACL/persistence interfaces have been frozen since 0.3.0 (ADR).

Ship your first MCP server today.

Follow the getting-started guide — a working stdio server in a few minutes, Streamable HTTP when you need it.