`MCPServer(token_verifier=...)` no longer needs `auth=AuthSettings(...)`.
On its own a verifier is now a plain bearer gate: requests without a
token it accepts get a 401 whose `WWW-Authenticate` carries no
`resource_metadata`, no protected-resource metadata route is published,
and `get_access_token()` works as before. `AuthSettings` keeps its job of
describing that gate to OAuth clients (required scopes, RFC 9728
metadata, the discovery pointer in the 401), so it is what you add when
a real authorization server issues the tokens.
Previously the constructor refused a verifier without settings, which
forced anyone with a pre-shared token to invent an issuer URL, and the
low-level `Server.streamable_http_app(token_verifier=...)` accepted the
same shape but answered every request 401, valid token included, because
the authentication backend was only installed when settings were given.
Both wiring sites (and `MCPServer.sse_app`) now install the backend
whenever a verifier is present.
The authorization docs gain a "Just a pre-shared token" section with a
runnable example, and the constructor still refuses the two shapes that
cannot work: settings with nothing to gate with, and an embedded
authorization-server provider without settings for its issuer.
MCPServer(token_verifier=...) no longer requires auth=AuthSettings(...). A verifier on its own is now a plain bearer gate; AuthSettings is what you add to describe that gate to OAuth clients.
Motivation and Context
Closes #3283 (see also #431, #702).
Someone with a pre-shared token and no authorization server anywhere had to write AuthSettings(issuer_url=<something made up>, resource_server_url=...) just to get past the constructor, and the made-up issuer then got advertised in the RFC 9728 metadata document, which sends OAuth-capable clients off to discover an AS that doesn't exist. In resource-server-only mode issuer_url is never contacted; it's only echoed into that document. The TypeScript and Go SDKs both treat "verifier, metadata optional" as the primitive (requireBearerAuth({verifier}), RequireBearerToken(verifier, nil)); this brings the Python high-level API in line.
There was also a low-level inconsistency behind it: Server.streamable_http_app(token_verifier=V) with no auth= was accepted but built an app that 401'd every request, valid token included, because AuthenticationMiddleware(BearerAuthBackend) was installed under if auth: while RequireAuthMiddleware was installed under if token_verifier:. MCPServer refused the same shape with a ValueError, so the two layers disagreed about one state.
What changes:
What this deliberately doesn't do:
How Has This Been Tested?
Breaking Changes
None. A constructor call that used to raise ValueError now succeeds; everything that worked before behaves identically. Two error messages are reworded (... without auth settings now names only auth_server_provider; ... when auth is enabled → ... with auth settings).
Types of changes
Checklist
Additional context
The spec makes authorization OPTIONAL and only SHOULD for HTTP transports, and basic allows custom authentication strategies, so a pre-shared bearer with no metadata is outside the OAuth profile rather than in violation of it. The docs section says so in practical terms: with nothing to discover, the client has to arrive already holding the token.