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
- Ensure get_routes() (which triggers set_mcp_path()) is called before any token operation — normally done automatically when mounting the proxy into FastMCP
- Reorder custom code so jwt_issuer is accessed only after the server/routes are initialized
- 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
- Only access jwt_issuer after mounting the proxy into the FastMCP server (get_routes runs automatically)
- In tests, call set_mcp_path() in a fixture before touching token operations
- Avoid grabbing internal auth objects at import time
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
- Invalid client_assertion_type: expected {JWT_BEARER_ASSERTIO
- Missing client_assertion
- Invalid client assertion: {e}
- CIMD document must have jwks_uri or jwks for private_key_jwt
- Invalid JWT assertion
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/314f95606a872668.
Report an issue: GitHub.