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.

  • 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 JSON and OTP. Optional sibling packages add Tasks, Plug, OAuth 2.1 resource server support, full JSON Schema validation, and :telemetry events. An optional proxy package fronts multiple MCP backends.

Packages

PackageAddsDocs
snodoProtocol core, router, server DSL, client, stdio and HTTP transportsHexDocs
snodo_plugSnodo.Transport.Plug for Plug and Bandit applicationsHexDocs
snodo_jsvFull JSON Schema 2020-12 validation through JSVHexDocs
snodo_oauthOAuth 2.1 resource server plugs (protected resource metadata, bearer token verification, scope policy) and the client authorization flows for Snodo.ClientHexDocs
snodo_telemetrySnodo.Instrumentation.Telemetry, an instrumentation sink that emits :telemetry eventsHexDocs
snodo_proxyAggregating proxy for MCP backendsHexDocs
snodo_tasksThe io.modelcontextprotocol/tasks extension with an application-owned store and runnerHexDocs
snodo_tasks_postgresPostgreSQL store for TasksHexDocs
snodo_tasks_sqliteSQLite store for TasksHexDocs

Add the packages you need to mix.exs. Each sibling brings snodo with it:

def deps do
  [
    {:snodo, "~> 0.4.1"},
    {:snodo_plug, "~> 0.4.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

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 nine packages
mix snodo.contract   # the protocol contract inventory

Conformance and interop checks against the official TypeScript and Python clients 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.