196b9598e6
`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.
28 lines
807 B
Python
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
|