gen_mcp is a server library for the Model Context Protocol (MCP). You define tools, resources and prompts as plain modules, mount one HTTP plug in your router, and clients can call them.

The transport is stateless: each request is validated and answered on its own, by a fresh server process. This guide walks through installing the library, defining a tool, mounting the server, and sending it a request.

Installation

Add the library to your dependencies and you are good to go.

defp deps do
  [
    {:gen_mcp, "~> 2.0"},
  ]
end

Quick start

This guide uses the default server implementation, GenMCP.Suite, together with the Streamable HTTP transport provided by the GenMCP.Transport.StreamableHTTP plug. The Suite is the component model: you list your tools, resources and prompts, and it advertises and routes them.

Define a tool

A tool is an operation a client invokes. To have something to call, define one. This tool adds two numbers. use GenMCP.Suite.Tool generates the metadata and argument validation from the options you pass, leaving you to implement GenMCP.Suite.Tool.call/3.

defmodule MyApp.Tools.Addition do
  use GenMCP.Suite.Tool,
    name: "add",
    description: "Adds two numbers and returns the sum.",
    input_schema: %{
      type: :object,
      properties: %{
        a: %{type: :number},
        b: %{type: :number}
      },
      required: [:a, :b]
    }

  alias GenMCP.MCP.V2607, as: MCP

  @impl true
  def call(request, _channel, _arg) do
    %{"a" => a, "b" => b} = request.params.arguments
    {:result, MCP.call_tool_result(text: "#{a + b}")}
  end
end

The :input_schema is a plain JSON Schema map, so the validated arguments reach GenMCP.Suite.Tool.call/3 as a map with string keys, which the function body destructures. Build the answer with the GenMCP.MCP.V2607 helpers rather than raw structs: GenMCP.MCP.V2607.call_tool_result/1 wraps the text into a proper result. The function returns {:result, result}.

See GenMCP.Suite.Tool for richer tools, including casting arguments into a struct with JSV and streaming long-running work.

Mount the server

Add the GenMCP.Transport.StreamableHTTP plug to your router. You name the server and list its tools right here, in the plug options. The default server is the Suite, so no :server option is needed. In a Phoenix router, mount it with forward:

defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  scope "/mcp" do
    forward "/", GenMCP.Transport.StreamableHTTP,
      server_name: "My Addition Server",
      server_version: "1.0.0",
      tools: [MyApp.Tools.Addition]
  end
end

Your MCP server is now live at /mcp. :server_name and :server_version identify the server to clients and are required. For the full set of transport options, including origin checks and passing per-request data to handlers, see GenMCP.Transport.StreamableHTTP.

Send a request

Any MCP client that speaks the 2026-07-28 protocol can now connect to the endpoint and list and call your tool.

To check the installation by hand, POST a JSON-RPC message. Clients usually start with server/discover, the capability snapshot for this protocol, but tools/list is the simplest smoke test. This call asks the server what it serves:

curl http://localhost:4000/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/list" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientInfo": {"name": "curl", "version": "1.0.0"},
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  }'

The response lists the add tool. To run it, switch to a tools/call message and pass the arguments:

curl http://localhost:4000/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/call" \
  -H "Mcp-Name: add" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "add",
      "arguments": {"a": 2, "b": 3},
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientInfo": {"name": "curl", "version": "1.0.0"},
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  }'

The result holds the text content "5". In practice an MCP client builds these messages and headers for you; the raw form above is useful as a smoke test.

Next steps