GenMCP
MCP server building blocks for Elixir.
This library packs two main features:
GenMCPitself - a low level behaviour to implement your own MCP server logic.GenMCP.Suite- a high level suite of components to build tools, resources and prompts with the default server implementation.
Installation
The usual tuple for mix.exs!
defp deps do
[
{:gen_mcp, "~> 2.0"},
]
end
Documentation
On hexdocs.pm.
Example
A small memory server: a tool that saves a memory, and a resource repository
that lists the memories and serves each one by id from a URI template. Both keep
the real work in a plain MyApp.Memory module and stay thin adapters.
A tool is an operation a client invokes. This one validates its arguments against
the :input_schema, saves a memory, and returns its id:
defmodule MyApp.Tools.CreateMemory do
use GenMCP.Suite.Tool,
name: "create_memory",
description: "Saves a memory and returns its id.",
input_schema: %{
type: :object,
properties: %{
title: %{type: :string},
content: %{type: :string}
},
required: [:title, :content]
}
alias GenMCP.MCP.V2607, as: MCP
@impl true
def call(request, _channel, _arg) do
%{"title" => title, "content" => content} = request.params.arguments
{:ok, memory} = MyApp.Memory.create(title, content)
{:result, MCP.call_tool_result(text: "Saved memory #{memory.id}")}
end
end
A resource is addressable content a client reads. A :uriTemplate lets one
repository serve a whole family of URIs: list/3 advertises the memories that
exist, and a resources/read for a matching URI is parsed into the template
variables, so read/3 receives %{"id" => id}:
defmodule MyApp.Resources.Memories do
@behaviour GenMCP.Suite.ResourceRepo
alias GenMCP.MCP.V2607, as: MCP
@impl true
def prefix(_arg), do: "memory:///"
@impl true
def template(_arg) do
%{uriTemplate: "memory:///{id}", name: "Memory"}
end
@impl true
def list(_cursor, _channel, _arg) do
memories =
for memory <- MyApp.Memory.list() do
%{uri: "memory:///#{memory.id}", name: memory.title, mimeType: "text/markdown"}
end
{memories, _cursor = nil}
end
@impl true
def read(%{"id" => id}, _channel, _arg) do
case MyApp.Memory.fetch(id) do
{:ok, memory} ->
{:ok,
MCP.read_resource_result(
uri: "memory:///#{id}",
text: memory.content,
mime_type: "text/markdown"
)}
:error ->
{:error, :not_found}
end
end
end
Mount the server in your router. The transport serves a GenMCP.Suite out of
the box, so you list the providers right in the plug options:
defmodule MyAppWeb.Router do
use MyAppWeb, :router
scope "/mcp" do
forward "/", GenMCP.Transport.StreamableHTTP,
server_name: "My Memory Server",
server_version: "1.0.0",
tools: [MyApp.Tools.CreateMemory],
resources: [MyApp.Resources.Memories]
end
end