PrefectHQ/fastmcp · error · RuntimeError

JWT issuer not initialized. Ensure get_routes() is called be

Error message

JWT issuer not initialized. Ensure get_routes() is called before token operations.

What it means

The OAuthProxy's jwt_issuer is created lazily when set_mcp_path() runs during get_routes(). Accessing the jwt_issuer property before the auth routes have been wired up raises RuntimeError, since token operations cannot work without an initialized issuer.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py:799

        # claim is the authorization server's issuer identifier (`issuer_url`),
        # which matches the `issuer` advertised in the metadata document.
        self._jwt_issuer = JWTIssuer(
            issuer=str(self.issuer_url),
            audience=str(self._resource_url),
            signing_key=self._jwt_signing_key,
        )

        logger.debug("Configured OAuth proxy for resource URL: %s", self._resource_url)

    @property
    def jwt_issuer(self) -> JWTIssuer:
        """Get the JWT issuer, ensuring it has been initialized.

        The JWT issuer is created when set_mcp_path() is called (via get_routes()).
        This property ensures a clear error if used before initialization.
        """
        if self._jwt_issuer is None:
            raise RuntimeError(
                "JWT issuer not initialized. Ensure get_routes() is called "
                "before token operations."
            )
        return self._jwt_issuer

    @property
    def token_endpoint_url(self) -> str:
        """The token endpoint URL, as advertised in the authorization server metadata.

        A CIMD `private_key_jwt` assertion is bound to this URL as its `aud`, so
        it must match the advertised `token_endpoint` byte-for-byte. The SDK's
        `build_metadata` builds that URL by stripping any trailing slash from
        `base_url` first, so this does too: pydantic renders a bare-authority
        `base_url` with a trailing slash, which would otherwise expect an `aud`
        of `https://example.com//token`.
        """
        return f"{str(self.base_url).rstrip('/')}/token"

View on GitHub (pinned to 1f02114297)

Solutions

  1. Ensure get_routes() (which triggers set_mcp_path()) is called before any token operation — normally done automatically when mounting the proxy into FastMCP
  2. Reorder custom code so jwt_issuer is accessed only after the server/routes are initialized
  3. In tests, explicitly call set_mcp_path() (or get_routes()) on the proxy before touching jwt_issuer

Example fix

// before
proxy = OAuthProxy(...)
issuer = proxy.jwt_issuer  # RuntimeError
// after
proxy = OAuthProxy(...)
routes = proxy.get_routes(mcp_path="/mcp")
issuer = proxy.jwt_issuer  # OK
Defensive patterns

Strategy: try-catch

Validate before calling

if proxy._jwt_issuer is None:  # or check before server start
    proxy.set_mcp_path(mcp_path)  # triggers issuer creation
issuer = proxy.jwt_issuer

Type guard

def issuer_ready(proxy) -> bool:
    return proxy._jwt_issuer is not None

Try / catch

try:
    issuer = proxy.jwt_issuer
except RuntimeError as e:
    if "JWT issuer not initialized" in str(e):
        proxy.get_routes(mcp_path="/mcp")
        issuer = proxy.jwt_issuer
    else:
        raise

Prevention

When it happens

Trigger: Accessing proxy.jwt_issuer (or calling token operation paths that use it) after constructing OAuthProxy but before get_routes() is called by the server setup.

Common situations: Writing custom token-verification or introspection code that grabs jwt_issuer directly at import/startup time; unit tests instantiating the proxy in isolation without mounting routes; composing custom auth middleware outside the normal FastMCP server bootstrap.

Related errors


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