Auth is application-owned. FastestMCP keeps a small runtime contract that turns credentials or framework state into normalized request context:
ctx.principalctx.authctx.capabilitiesContext.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]
endFastestMCP.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:
falseornilto copy no assigns[:current_user, :account]to copy specific assigns:allto 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.