Files
modelcontextprotocol--pytho…/docs_src/authorization/tutorial003.py
T
Max Isbey 196b9598e6 Let a token verifier gate the server without AuthSettings
`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.
2026-08-11 15:39:56 +00:00

28 lines
807 B
Python

import os
import secrets
from mcp.server import MCPServer
from mcp.server.auth.middleware.auth_context import get_access_token
from mcp.server.auth.provider import AccessToken, TokenVerifier
API_TOKEN = os.environ.get("NOTES_API_TOKEN") or secrets.token_urlsafe(32)
class PresharedTokenVerifier(TokenVerifier):
async def verify_token(self, token: str) -> AccessToken | None:
if secrets.compare_digest(token.encode(), API_TOKEN.encode()):
return AccessToken(token=token, client_id="notes-client", scopes=[])
return None
mcp = MCPServer("Notes", token_verifier=PresharedTokenVerifier())
@mcp.tool()
def whoami() -> str:
"""Report which client is calling."""
token = get_access_token()
if token is None:
return "anonymous"
return token.client_id