PrefectHQ/fastmcp · error · RuntimeError

Extension {self.identifier!r} is not bound to a FastMCP serv

Error message

Extension {self.identifier!r} is not bound to a FastMCP server; register it with FastMCP.add_extension() before use.

What it means

ServerExtension.server is a weakly-referenced property pointing at the FastMCP instance the extension was registered on. Accessing it before FastMCP.add_extension() has been called (or after the server was garbage-collected) finds no live server and raises RuntimeError.

Source

Thrown at fastmcp_slim/fastmcp/server/extensions.py:154

        if identifier is not None:
            validate_extension_identifier(identifier, owner=cls.__name__)

    def _bind(self, server: FastMCP) -> None:
        """Bind this extension to its FastMCP instance (called by `add_extension`).

        A weak reference avoids a reference cycle between the server and its
        extensions. Per-instance identifiers are validated here.
        """
        validate_extension_identifier(self.identifier, owner=type(self).__name__)
        self._server_ref = weakref.ref(server)

    @property
    def server(self) -> FastMCP:
        """The FastMCP server this extension is registered on.

        Handlers, interceptors, and lifespan code reach the component registry,
        `Context`, and auth scope through here. Raises if the extension has not
        been registered with `FastMCP.add_extension()`.
        """
        ref = self._server_ref
        server = ref() if ref is not None else None
        if server is None:
            raise RuntimeError(
                f"Extension {self.identifier!r} is not bound to a FastMCP server; "
                "register it with FastMCP.add_extension() before use."
            )
        return server

    def settings(self) -> dict[str, Any]:
        """Per-extension settings advertised at `capabilities.extensions[identifier]`.

        An empty dict (the default) advertises the extension with no settings.
        """
        return {}

    def methods(self) -> Sequence[MethodBinding]:

View on GitHub (pinned to 1f02114297)

Solutions

  1. Register the extension first: server = FastMCP(...); server.add_extension(ext), then access ext.server.
  2. Only access ext.server from within handlers/interceptors/lifespan, which run after registration.
  3. Keep a direct reference to your FastMCP instance in application code instead of reaching through the extension.
  4. If the server may have been garbage collected, re-create/re-register the extension on the live server.

Example fix

// before
ext = MyExtension()
registry = ext.server._component_manager  # RuntimeError: not bound
// after
server = FastMCP("demo")
server.add_extension(ext)
registry = ext.server._component_manager
Defensive patterns

Strategy: try-catch

Validate before calling

def extension_bound(ext) -> bool:
    ref = getattr(ext, "_server_ref", None)
    return bool(ref and ref() is not None)

Type guard

def is_bound(ext) -> bool:
    return ext._server_ref is not None and ext._server_ref() is not None

Try / catch

try:
    server = ext.server
except RuntimeError:
    server = None  # defer until after add_extension()

Prevention

When it happens

Trigger: Calling extension.server inside handler/interceptor/lifespan code before registration; instantiating an extension and probing .server immediately; the server object being dropped so the weakref is dead.

Common situations: Unit tests constructing an extension in isolation; module-level extension objects used at import time before the server exists; holding extensions across server restarts.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/bce14fd38b7a6fa2. Report an issue: GitHub.