snodo
An Elixir library for building Model Context Protocol
servers and clients. It speaks MCP 2026-07-28, with opt-in support for
initialize-era HTTP clients (2025-11-25 and 2025-06-18).
snodo is at 0.x: the API may change between minor versions until 1.0.
- Servers from inline blocks or ordinary modules, served over stdio, a built-in Streamable HTTP listener, or Plug and Bandit.
- A client that calls any MCP server in process, over stdio, or over HTTP.
- The 2026-07-28 surface: discovery, tools, resources and templates,
prompts, completion, pagination,
subscriptions/listen, progress, cancellation, and multi round-trip requests with elicitation. - No runtime dependencies in the core: it uses Elixir's built-in
JSONand OTP. Optional sibling packages add Tasks, Plug, and full JSON Schema validation.
Packages
| Package | Adds | Docs |
|---|---|---|
snodo |
Protocol core, router, server DSL, client, stdio and HTTP transports | HexDocs |
snodo_plug |
Snodo.Transport.Plug for Plug and Bandit applications |
HexDocs |
snodo_jsv |
Full JSON Schema 2020-12 validation through JSV | HexDocs |
snodo_tasks |
The io.modelcontextprotocol/tasks extension with an application-owned store and runner |
HexDocs |
snodo_tasks_postgres |
PostgreSQL store for Tasks | HexDocs |
snodo_tasks_sqlite |
SQLite store for Tasks | HexDocs |
Add the packages you need to mix.exs. Each sibling brings snodo with it:
def deps do
[
{:snodo, "~> 0.1.0"},
{:snodo_plug, "~> 0.1.0"}
]
end
Elixir 1.18 or later is required. The sibling packages live in this repository
under integrations/ and extensions/.
Quick start
A server with one tool, one resource template, and one prompt:
defmodule Greeter do
use Snodo.Server, name: "greeter", version: "0.1.0"
tool "greet", description: "Create a greeting" do
argument "name", :string, required: true
@impl true
def call(%{"name" => name}, _context), do: {:ok, "Hello, #{name}!"}
end
resource "profile", uri_template: "people://{name}/profile", mime_type: "application/json" do
@impl true
def read(%{"name" => name}, _context), do: {:ok, %{"name" => name}}
end
prompt "introduce", description: "Introduce someone" do
argument "name", required: true
@impl true
def render(%{"name" => name}, _context), do: {:ok, "Introduce #{name} in one sentence."}
end
end
Call it in process with Snodo.Client:
{:ok, client} = Snodo.Client.direct(Greeter.runtime())
{:ok, [%{"name" => "greet"}]} = Snodo.Client.list_tools(client)
{:ok, result} = Snodo.Client.call_tool(client, "greet", %{"name" => "Ada"})
result["content"]
#=> [%{"type" => "text", "text" => "Hello, Ada!"}]
Serve it over stdio from a script or release:
:ok = Snodo.Transport.Stdio.serve(Greeter.runtime())
or over HTTP, supervised:
children = [{Snodo.Transport.StreamableHTTP.Server, runtime: Greeter.runtime(), port: 4000}]
The same client connects to either:
{:ok, client} = Snodo.Client.connect({:stdio, "elixir", ["greeter.exs"]})
{:ok, client} = Snodo.Client.connect({:http, "http://127.0.0.1:4000/mcp"})
Guides
- Getting started
- Tools, resources, and prompts
- The client
- Transports
- Choosing packages for an application
- Interactive operations (MRTR and elicitation)
- Subscriptions
- Authorization
- Extensions and Tasks
- Instrumentation
- Initialize-era clients
- Supported Elixir, OTP, and databases
- Protocol compliance
The examples are runnable scripts, each checked in CI.
Protocol support
2026-07-28 is the default and only required dialect. For clients that still
send initialize, enable the older dialects on the server:
use Snodo.Server,
name: "greeter",
version: "0.1.0",
protocols: [Snodo.Protocol.V2026_07_28, Snodo.Protocol.V2025_11_25, Snodo.Protocol.V2025_06_18]
They cover tools, resources, prompts, completion, and pagination over stateless HTTP. They add no session storage.
Against the frozen official conformance suite, 32 of 37 2026-07-28 server
scenarios pass. On the client side, 6 of 32 pass: 25 of the client scenarios
cover OAuth, which Snodo.Client does not implement. The compliance guide lists
what is measured and what is not. Design records from the project's history are
in docs/history.
Development
mix setup # fetch dependencies for every package; rerun after a mix.lock changes
mix quality # format, compile, Credo, tests, examples, and every sibling package
mix quality.types # Dialyzer across all six packages
mix snodo.contract # the protocol contract inventory
Conformance and interop checks against the official TypeScript client live in
conformance/ and interop/, and run in CI. Releases are made with
release-please; see
RELEASING.md.
License
MIT. See LICENSE.