snodo

Compatibility Protocol regression Hex.pm Docs Elixir License: MIT

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. The client speaks all three versions and negotiates one when it connects.

snodo is at 0.x: the API may change between minor versions until 1.0.

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_oauth OAuth 2.1 resource server plugs (protected resource metadata, bearer token verification, scope policy) and the client authorization flows for Snodo.Client HexDocs
snodo_telemetry Snodo.Instrumentation.Telemetry, an instrumentation sink that emits :telemetry events 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.2"},
{:snodo_plug, "~> 0.3.2"}
]
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

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, all 37 2026-07-28 server scenarios pass; that is the pinned runner's score, not a claim of full revision conformance. On the client side, 31 of 32 pass, including the 25 that cover OAuth with snodo_oauth as the token provider; the other one is excluded from the score because a 2026-07-28 client sends no initialize. Pinned to 2025-11-25, the client passes 16 of 18, including all 14 that cover OAuth. 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 eight 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.