FastestMCP
OTP-native MCP servers and clients for Elixir.
FastestMCP provides supervised server runtimes, connected clients, strict protocol handling, authorization, tasks, middleware, and providers for ordinary Elixir and Phoenix applications. It is designed around explicit lifecycles, bounded work, and familiar OTP supervision rather than a separate runtime.
Protocol support · Installation · Quick start · Examples · Phoenix and authentication · Documentation
Protocol support
FastestMCP supports both MCP revisions from the same server, process, and endpoint:
| Revision | Profile | Startup and state | Tasks |
|---|---|---|---|
MCP 2026-07-28 | Modern and preferred | server/discover; request-scoped protocol metadata; sessionless HTTP | Optional io.modelcontextprotocol/tasks v2 extension |
MCP 2025-11-25 | Legacy compatibility | initialize, then notifications/initialized; server-issued HTTP session | Optional legacy Tasks v1 |
Both profiles are available over Streamable HTTP and stdio. Connected clients
prefer 2026-07-28 by default, with transport-specific compatibility fallback.
Exact version pins are available for migrations and compatibility testing.
FastestMCP.supported_protocol_versions()
# => ["2026-07-28", "2025-11-25"]
FastestMCP.current_protocol_version()
# => "2026-07-28"
A single running server may serve modern and legacy clients concurrently; protocol state belongs to each request or legacy session, not to a global server switch. See Protocol Versions for the wire and lifecycle differences.
Highlights
| Area | What FastestMCP provides |
|---|---|
| Server runtime | Module-owned or dynamic servers with supervised execution, bounded concurrency, overload control, and isolated lifecycles |
| Connected client | Supervised Streamable HTTP, stdio, and in-process connections with callbacks, progress, subscriptions, Tasks, automatic protocol negotiation, and end-to-end OpenTelemetry |
| Components | Tools, resources, resource templates, prompts, completion, runtime mutation, transforms, and visibility policies |
| Security | Pluggable authentication, RFC 9728 protected-resource metadata, scope-aware authorization, and secure-by-default lexical resource-template screening |
| Extensibility | Middleware, providers, mounted servers, active negotiated extensions, request-scoped proxying, and bounded tool search |
| Protocol features | MRTR, modern and legacy Tasks, MCP Apps metadata/resources, pagination, logging, cancellation, and JSON Schema validation |
| State and operations | Request state, legacy transport sessions, application sessions, background tasks, telemetry, and structured cleanup |
Installation
Add FastestMCP to your dependencies:
def deps do
[
{:fastest_mcp, "~> 0.3.0"}
]
end
Then fetch dependencies:
mix deps.get
Quick Start
Start with a module-owned server:
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
children = [
MyApp.MCPServer
]
FastestMCP.call_tool(MyApp.MCPServer, "sum", %{"a" => 20, "b" => 22})
# => 42
Connect over HTTP with automatic latest-first negotiation:
client =
FastestMCP.Client.connect!("http://localhost:4100/mcp",
protocol_version: :auto,
client_info: %{"name" => "my_app", "version" => "1.0.0"}
)
FastestMCP.Client.protocol_version(client)
# => "2026-07-28"
%FastestMCP.Client.ToolResult{structured_content: 42} =
FastestMCP.Client.call_tool_result(client, "sum", %{"a" => 20, "b" => 22})
:ok = FastestMCP.Client.disconnect(client)
Production applications can supervise and name the connection instead of holding a manually connected handle:
children = [
{FastestMCP.Client,
target: "http://localhost:4100/mcp",
name: MyApp.MCPClient,
protocol_version: :auto}
]
:ok = FastestMCP.Client.await_ready(MyApp.MCPClient, 10_000)
%{items: tools} = FastestMCP.Client.list_tools(MyApp.MCPClient)
Client operations create OpenTelemetry CLIENT spans and propagate W3C trace context through MCP request metadata, so Phoenix request traces continue across the remote MCP call without application glue.
The full onboarding path, including transport startup and the first connected client call, lives in docs/onboarding.md.
Server and component DSL
The builder API composes every component onto one server definition. Tools, resources, templates, and prompts then share the same middleware, authorization, visibility, task, and telemetry pipeline.
input_schema = %{
"type" => "object",
"properties" => %{
"environment" => %{
"type" => "string",
"enum" => ["staging", "production"]
}
},
"required" => ["environment"]
}
server =
FastestMCP.server("operations")
|> FastestMCP.add_tool(
"deployment_status",
fn %{"environment" => environment}, _ctx ->
FastestMCP.Tools.Result.new(
"#{environment} is healthy",
structured_content: %{environment: environment, status: "healthy"}
)
end,
description: "Read the current deployment status",
input_schema: input_schema
)
|> FastestMCP.add_resource("config://environments", fn _arguments, _ctx ->
%{environments: ["staging", "production"]}
end)
|> FastestMCP.add_resource_template(
"deployments://{environment}/latest",
fn %{"environment" => environment}, _ctx ->
%{environment: environment, revision: "2026.08.14", status: "healthy"}
end
)
|> FastestMCP.add_prompt(
"review_deployment",
fn %{"environment" => environment}, _ctx ->
"Review the latest #{environment} deployment and identify operational risks."
end,
arguments: [%{name: "environment", required: true}]
)
{:ok, _pid} = FastestMCP.start_server(server)
Use FastestMCP.ServerModule when the definition belongs in an application
supervision tree, as shown in the quick start. Use FastestMCP.server/2 for
dynamic definitions, tests, and provider composition.
MCP Apps
MCP Apps link an ordinary tool to a ui:// HTML resource. The tool must keep a
useful text fallback, while a negotiated Apps-capable client receives the UI
metadata and document.
alias FastestMCP.Apps
app_uri = "ui://reports/summary.html"
app_options = [
csp: %{"connectDomains" => ["https://api.example.com"]},
prefers_border: true
]
server =
FastestMCP.server("reports",
extensions: %{Apps.extension_id() => %{}}
)
|> FastestMCP.add_tool(
"show_report",
fn _arguments, _ctx ->
FastestMCP.Tools.Result.new(
"The report is ready.",
structured_content: %{status: "ready"}
)
end,
meta: Apps.tool_meta(app_uri)
)
|> FastestMCP.add_resource(
app_uri,
fn _arguments, _ctx ->
Apps.result(
app_uri,
"<!doctype html><html><body><main>Report</main></body></html>",
app_options
)
end,
mime_type: Apps.mime_type(),
meta: Apps.resource_meta(app_options)
)
The connected client opts in with the supported Apps MIME type:
client =
FastestMCP.Client.connect!(endpoint,
extensions: %{Apps.extension_id() => Apps.client_settings()}
)
FastestMCP owns the MCP metadata and resource boundary. The consuming Host owns HTML rendering, iframe sandboxing, CSP enforcement, permissions, consent, and the Host/View bridge.
Phoenix and authentication
When Phoenix owns the listener, supervise the MCP server without its standalone HTTP transport. Normalize the identity already verified by your application and apply component scopes on the same server definition:
defmodule MyApp.MCPServer do
use FastestMCP.ServerModule,
otp_app: :my_app,
protected_resource: [
resource: "https://mcp.example.com/mcp",
authorization_servers: ["https://auth.example.com"],
scopes_supported: ["reports:read", "reports:write"],
required_scopes: ["reports:read"]
]
def server(opts) do
base_server(opts)
|> FastestMCP.add_auth(
FastestMCP.Auth.from_assign(:mcp_identity,
principal: fn identity -> {identity.issuer, identity.subject} end,
audiences: fn identity -> identity.verified_audiences end,
scopes: fn identity -> identity.verified_scopes end,
auth: fn identity -> %{source: :phoenix, subject: identity.subject} end
)
)
|> FastestMCP.add_tool(
"create_report",
fn arguments, ctx -> MyApp.Reports.create!(arguments, actor: ctx.principal) end,
auth: FastestMCP.Authorization.require_scopes(["reports:write"])
)
end
end
Add MyApp.MCPServer before MyAppWeb.Endpoint in the application supervision
tree:
children = [
MyApp.Repo,
MyApp.MCPServer,
MyAppWeb.Endpoint
]
Mount RFC 9728 discovery publicly, then mount the MCP endpoint behind a non-halting identity verifier:
pipeline :mcp_auth do
# Verifies signature/introspection, issuer, expiry, audience, and scopes.
# It assigns :mcp_identity when valid, but never redirects or halts.
plug MyAppWeb.MCPBearerVerifier
end
scope "/" do
forward "/.well-known/oauth-protected-resource/mcp",
FastestMCP.Transport.WellKnownHTTP,
server_name: MyApp.MCPServer,
path: "/mcp",
base_url: "https://mcp.example.com",
allowed_hosts: ["mcp.example.com"]
end
scope "/" do
pipe_through :mcp_auth
forward "/mcp", FastestMCP.Transport.HTTPApp,
server_name: MyApp.MCPServer,
path: "/mcp",
base_url: "https://mcp.example.com",
allowed_hosts: ["mcp.example.com"],
auth_assigns: [:mcp_identity]
end
The verifier must not issue its own redirect or challenge: FastestMCP owns the
MCP 401/403 and WWW-Authenticate response. Keep the well-known route
outside authentication, use a narrow auth_assigns: allowlist, and return only
audiences and scopes that the host actually verified. FastestMCP is the
protected resource server; token issuance, JWT verification, introspection,
and login UI remain application or authorization-server responsibilities.
Upgrading
FastestMCP 0.3.x keeps the complete MCP 2025-11-25 profile while adding
2026-07-28 as the preferred modern profile. Existing 0.2.x deployments may
pin protocol_version: "2025-11-25" during a staged migration, then move to
:auto when modern peers are ready.
Applications coming from 0.1.x must also adopt the JSON-RPC-only Streamable
HTTP boundary introduced in 0.2.0. Review the
changelog and
transport migration guide before
deploying.
Documentation
| Path | Guides |
|---|---|
| Start here | Onboarding · Why FastestMCP · Protocol Versions · Compatibility and Scope |
| Build | Components · Tools · Resources · Prompts · Context · Dependency Injection · Lifespan |
| Connect and extend | Client · Transports · Providers and Mounting · Protocol Extensions · Sampling and Interaction · Pagination · Progress |
| Secure and operate | Auth · Phoenix Deployment · Middleware · Background Tasks · Runtime State and Storage · Logging · Telemetry · Testing |
| Advanced | Dynamic Component Manager · Transforms · Versioning and Visibility · Schema Validation |
Known boundaries
FastestMCP intentionally does not provide:
- standalone SSE transport compatibility
- legacy method-specific MCP routes or JSON-RPC batches
- CLI tooling
- distributed multi-node runtime behavior out of the box
- a browser/native Apps Host and View runtime
HTTP support means Streamable HTTP at the configured MCP endpoint, /mcp by
default. FastestMCP preserves MCP Apps metadata and resources, but rendering,
sandboxing, and the Host/View bridge belong to the consuming host.