Auth is application-owned. FastestMCP keeps a small runtime contract that turns credentials or framework state into normalized request context:

  • ctx.principal
  • ctx.auth
  • ctx.capabilities
  • Context.client_id/1

Your application verifies sessions, tokens, cookies, or upstream identity using its normal stack. FastestMCP only needs the normalized result.

Function Auth

Pass a function directly when auth is specific to the host application:

FastestMCP.server("app")
|> FastestMCP.add_auth(fn input, _ctx ->
  case MyApp.Auth.verify_mcp_request(input) do
    {:ok, user} ->
      {:ok,
       %{
         principal: %{"sub" => to_string(user.id)},
         auth: %{source: :app, user_id: user.id},
         capabilities: MyApp.MCPScopes.for_user(user)
       }}

    :error ->
      {:error, :unauthorized}
  end
end)

The function may have arity 2 or 3. Arity 3 receives the configured auth options as the third argument.

Module Auth

Use the behaviour when you want a reusable authenticator module:

defmodule MyApp.MCPAuth do
  @behaviour FastestMCP.Auth

  @impl true
  def authenticate(input, _ctx, opts) do
    with {:ok, user} <- MyApp.Auth.verify(input, opts) do
      {:ok,
       %FastestMCP.Auth.Result{
         principal: %{"sub" => to_string(user.id)},
         auth: %{source: :app, user_id: user.id},
         capabilities: MyApp.MCPScopes.for_user(user)
       }}
    end
  end
end

FastestMCP.server("app")
|> FastestMCP.add_auth(MyApp.MCPAuth, audience: "mcp")

Auth errors should return {:error, :unauthorized}, {:error, :forbidden}, {:error, {code, message}}, or {:error, %FastestMCP.Error{}}.

Phoenix Assigns

When the HTTP transport runs behind Plug or Phoenix authentication, copy selected conn.assigns into auth input with auth_assigns:. Assigns are available only to the auth function or module under "assigns"; they are not added to normal handler request metadata.

pipeline :mcp do
  plug :fetch_session
  plug MyAppWeb.UserAuth, :fetch_current_user
end

scope "/" do
  pipe_through :mcp

  forward "/mcp", FastestMCP.Transport.HTTPApp,
    server_name: MyApp.MCPServer,
    path: "/mcp",
    auth_assigns: [:current_user]
end

FastestMCP.Auth.from_assign/2 turns one assign into a normalized auth result:

FastestMCP.server(MyApp.MCPServer)
|> FastestMCP.add_auth(
  FastestMCP.Auth.from_assign(:current_user,
    principal: fn user -> %{"sub" => to_string(user.id)} end,
    capabilities: fn user -> MyApp.MCPScopes.for_user(user) end,
    auth: fn user -> %{source: :phoenix, user_id: user.id} end
  )
)

auth_assigns: accepts:

  • false or nil to copy no assigns
  • [:current_user, :account] to copy specific assigns
  • :all to copy every assign

The default is false.

Static Token

FastestMCP.Auth.StaticToken is kept for local development, integration tests, and hermetic tooling:

FastestMCP.server("dev")
|> FastestMCP.add_auth(FastestMCP.Auth.StaticToken,
  tokens: %{
    "dev-token" => %{
      client_id: "local-client",
      scopes: ["tools:call"],
      principal: %{"sub" => "local-client"}
    }
  },
  required_scopes: ["tools:call"]
)
|> FastestMCP.add_tool("whoami", fn _arguments, ctx ->
  %{principal: ctx.principal, auth: ctx.auth}
end)

Static tokens can be supplied as an HTTP bearer token, as "authorization" in direct auth_input, or as "token" in direct auth_input.

Component Authorization

Authentication identifies the caller. Component authorization decides which tools, resources, prompts, and templates the caller may see or call.

FastestMCP.server("app")
|> FastestMCP.add_tool("admin_report", &MyApp.Report.run/2,
  auth: FastestMCP.Authorization.require_scopes(["admin:reports"])
)

Authorization rules can also filter list results with tags:

FastestMCP.Authorization.restrict_tag("internal")

HTTP Behavior

HTTP auth failures use plain bearer challenges:

WWW-Authenticate: Bearer error="invalid_token", error_description="missing credentials"

FastestMCP does not serve OAuth discovery, authorization, token, callback, or protected-resource metadata routes. Applications that need those endpoints should expose them from their Plug or Phoenix application and pass normalized auth results into FastestMCP.

Why This Shape

Phoenix applications usually already own authentication, sessions, user loading, authorization policy, and audit metadata. Keeping FastestMCP auth as a small contract avoids a second identity stack while preserving consistent context for handlers, middleware, tasks, transports, and component visibility.