This is the shortest path from a fresh Elixir app to a working MCP server.

1. Define a server module

Use FastestMCP.ServerModule when the server belongs to your application:

defmodule MyApp.MCPServer do
  use FastestMCP.ServerModule,
    http: [port: 4100, allowed_hosts: :localhost]

  alias FastestMCP.Context

  def server(opts) do
    base_server(opts)
    |> FastestMCP.add_tool("sum", fn %{"a" => a, "b" => b}, _ctx -> a + b end)
    |> FastestMCP.add_tool("visit", fn _arguments, ctx ->
      visits = Context.get_state(ctx, :visits, 0) + 1
      :ok = Context.set_state(ctx, :visits, visits)
      %{visits: visits, server: ctx.server_name}
    end)
  end
end

base_server/1 keeps the builder DSL intact while making the module name the server identity automatically.

If you need to advertise protocol extension capabilities during initialization, pass experimental_capabilities: when constructing the server:

FastestMCP.server("capability-demo",
  experimental_capabilities: %{
    "acme.widgets" => %{"version" => 1}
  }
)

The map is exposed under initialize capabilities as "experimental".

2. Start it under your supervision tree

defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      MyApp.MCPServer
    ]

    Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
  end
end

3. Call it in process

FastestMCP.call_tool(MyApp.MCPServer, "sum", %{"a" => 20, "b" => 22})
# => 42

FastestMCP.call_tool(MyApp.MCPServer, "visit", %{}, session_id: "docs-session")
# => %{visits: 1, server: "Elixir.MyApp.MCPServer"}

Reusing the same session_id keeps session state attached to the same client conversation.

4. Serve it over streamable HTTP

The http: option in use FastestMCP.ServerModule starts the transport for you. If you want to embed it inside an existing Plug or Phoenix stack, use the shared HTTP app directly:

children = [
  {Bandit, plug: FastestMCP.http_app(MyApp.MCPServer, allowed_hosts: :localhost), port: 4100}
]

Phoenix forwarding works the same way:

forward "/mcp", FastestMCP.Transport.HTTPApp,
  server_name: MyApp.MCPServer,
  path: "/mcp",
  allowed_hosts: :any

5. Connect with a client

client =
  FastestMCP.Client.connect!("http://127.0.0.1:4100/mcp",
    client_info: %{"name" => "docs-client", "version" => "1.0.0"},
    session_stream: true
  )

%{items: tools} = FastestMCP.Client.list_tools(client)
FastestMCP.Client.call_tool(client, "sum", %{"a" => 20, "b" => 22})
FastestMCP.Client.complete(
  client,
  %{"type" => "ref/prompt", "name" => "draft_release"},
  %{"name" => "environment", "value" => "pr"}
)

From here, branch into the focused guides:

Why This Shape

FastestMCP treats the server as application infrastructure, not a separate app framework hiding inside your codebase. Module-owned startup makes server identity explicit, fits normal OTP supervision, and keeps the same builder API available for dynamic or generated cases.