docs/python-sdk/fastmcp-server-low_level.mdx
fastmcp.server.low_levelclient_supports_extension <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/server/low_level.py#L111" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>client_supports_extension(session: ServerSession, extension_id: str) -> bool
Check whether the connected client supports a given MCP extension.
Inspects the extensions capability on ClientCapabilities sent by the
client during initialization. In v2 the client's initialize params are
reachable via session.client_params.
SDK v2 declares extensions as a real field on ClientCapabilities, so
a client sending ClientCapabilities(extensions={...}) populates the field
directly. We read that field first and fall back to model_extra only for
legacy-serialized clients that carried extensions as an extra key.
FastMCPServerMiddleware <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/server/low_level.py#L140" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>Root dispatch for the FastMCP middleware chain, in the SDK's middleware layer.
v2 no longer lets FastMCP subclass ServerSession (the runner constructs
it per request), so the old MiddlewareServerSession._received_request
override is replaced by a ServerMiddleware — an ordinary entry in the
SDK's own middleware list. Sitting at the root of dispatch, this
is the single entry point through which every inbound message flows —
requests, notifications, cancellations, initialize, and even malformed or
unroutable messages the SDK can still hand us. It binds the FastMCP
request-context ContextVar and re-applies the app-scoped SharedContext for
the whole chain, then runs the FastMCP Middleware chain so
on_message / on_request / on_notification observe the message.
Dispatch shapes:
initialize dispatches
through on_initialize and server/discover through on_discover.
Neither has an interior FastMCP handler adapter, and the SDK serializes both
results before returning through its middleware seam, so this root adapter
restores core results to typed models before FastMCP middleware observes them.tools/call, tools/list, resources/read,
...) still run their FastMCP chain interior, in the handler adapter, where
on_call_tool receives the typed component result and a tool exception
propagates through on_message/on_request exactly where the built-in
error/logging/timing middleware expect it. The root dispatch does not re-run the
chain for these — it only steps in when such a request fails before the
interior runs (malformed params, routing), so on_message still observes
the failure.notifications/cancelled
and notifications/initialized), ping, logging/setLevel, and any
unroutable/non-component request — has no interior FastMCP dispatch, so the
root dispatch runs the "outer" pass (on_message plus
on_request/on_notification) here, wrapping the real SDK dispatch.
This closes the long-standing gap where these messages were invisible to
FastMCP middleware.LowLevelServer <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/server/low_level.py#L455" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>Methods:
fastmcp <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/server/low_level.py#L507" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>fastmcp(self) -> FastMCP
Get the FastMCP instance.
create_initialization_options <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/server/low_level.py#L514" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>create_initialization_options(self, notification_options: NotificationOptions | None = None, experimental_capabilities: dict[str, dict[str, Any]] | None = None, extensions: dict[str, dict[str, Any]] | None = None) -> InitializationOptions
get_capabilities <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/server/low_level.py#L529" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>get_capabilities(self, notification_options: NotificationOptions | None = None, experimental_capabilities: dict[str, dict[str, Any]] | None = None, extensions: dict[str, dict[str, Any]] | None = None) -> mcp_types.ServerCapabilities
Override to advertise registered extensions and the MCP Apps UI extension.
ServerCapabilities.extensions is a real declared field in v2, so we
update it directly. The
FastMCP(experimental_capabilities=...) merge also lives here rather
than in create_initialization_options: the modern server/discover
handler calls this directly, without going through
create_initialization_options at all, so merging there only reached
the handshake-era initialize response and silently dropped
constructor-configured experimental capabilities from discover.