---
title: low_level
sidebarTitle: low_level
---

# `fastmcp.server.low_level`

## Functions

### `client_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>

```python
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.


## Classes

### `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:

- Negotiation runs the *whole* FastMCP chain here: ``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.
- The component methods (``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.
- Every other message — all notifications (including ``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>

```python
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>

```python
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>

```python
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`.

