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 clients (2025-11-25 and 2025-06-18) over HTTP and stdio.
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.3.1"},
{:snodo_plug, "~> 0.3.1"}
]
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 HTTP without sessions and over stdio. 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.
Contributing
Open issues are labeled by priority, size, and area, and good first issue
marks small, well-scoped starting points.
CONTRIBUTING.md
describes the workflow.
AGENTS.md lists
the setup, the gate commands CI runs, and the project's constraints, for people
and coding agents alike. Report security problems privately, as described in
SECURITY.md.
License
MIT. See LICENSE.