Skip to main content
New in version 3.1.0 Production servers often need to accept tokens from multiple authentication sources. An interactive application might authenticate through an OAuth proxy, while a backend service sends machine-to-machine JWT tokens directly. MultiAuth composes these sources into a single auth provider so every valid token is accepted regardless of where it was issued.

Understanding MultiAuth

MultiAuth wraps an optional auth server (like OAuthProxy) together with one or more token verifiers (like JWTVerifier). When a request arrives with a bearer token, MultiAuth tries each source in order and accepts the first successful verification. The auth server, if provided, is tried first. It owns all OAuth routes and metadata — the verifiers contribute only token verification logic. This keeps the MCP discovery surface clean: one set of routes, one set of metadata, multiple verification paths.
from fastmcp import FastMCP
from fastmcp.server.auth import MultiAuth, OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier

auth = MultiAuth(
    server=OAuthProxy(
        issuer_url="https://login.example.com/...",
        client_id="my-app",
        client_secret="secret",
        base_url="https://my-server.com",
    ),
    verifiers=[
        JWTVerifier(
            jwks_uri="https://internal-issuer.example.com/.well-known/jwks.json",
            issuer="https://internal-issuer.example.com",
            audience="my-mcp-server",
        ),
    ],
)

mcp = FastMCP("My Server", auth=auth)
Interactive MCP clients authenticate through the OAuth proxy as usual. Backend services skip OAuth entirely and send a JWT signed by the internal issuer. Both paths are validated, and the first match wins.

Verification Order

MultiAuth checks sources in a deterministic order:
  1. Server (if provided) — the full auth provider’s verify_token runs first
  2. Verifiers — each TokenVerifier is tried in list order
The first source that returns a valid AccessToken wins. If every source returns None, the request receives a 401 response. This ordering means the server acts as the “primary” authentication path, with verifiers as fallbacks for tokens the server doesn’t recognize.

Verifiers Only

You don’t always need a full OAuth server. If your server only needs to accept tokens from multiple issuers, pass verifiers without a server:
from fastmcp import FastMCP
from fastmcp.server.auth import MultiAuth
from fastmcp.server.auth.providers.jwt import JWTVerifier, StaticTokenVerifier

auth = MultiAuth(
    verifiers=[
        JWTVerifier(
            jwks_uri="https://issuer-a.example.com/.well-known/jwks.json",
            issuer="https://issuer-a.example.com",
            audience="my-server",
        ),
        JWTVerifier(
            jwks_uri="https://issuer-b.example.com/.well-known/jwks.json",
            issuer="https://issuer-b.example.com",
            audience="my-server",
        ),
    ],
)

mcp = FastMCP("Multi-Issuer Server", auth=auth)
Without a server, no OAuth routes or metadata are served. This is appropriate for internal systems where clients already know how to obtain tokens.

API Reference

MultiAuth

ParameterTypeDescription
serverAuthProvider | NoneOptional auth provider that owns routes and OAuth metadata. Also tried first for token verification.
verifierslist[TokenVerifier] | TokenVerifierOne or more token verifiers tried after the server.
base_urlstr | NoneOverride the base URL. Defaults to the server’s base_url.
required_scopeslist[str] | NoneOverride required scopes. Defaults to the server’s scopes.