This page covers OAuth 2.1 authentication for both MCP servers and clients.
Server-Side Authentication
Authentication can be used by servers that want to expose tools accessing protected resources.
mcp.server.auth implements OAuth 2.1 resource server functionality, where MCP servers act as Resource Servers (RS) that validate tokens issued by separate Authorization Servers (AS). This follows the MCP authorization specification and implements RFC 9728 (Protected Resource Metadata) for AS discovery.
MCP servers can use authentication by providing an implementation of the TokenVerifier protocol:
""" Run from the repository root: uv run examples/snippets/servers/oauth_server.py """ from pydantic import AnyHttpUrl from mcp.server.auth.provider import AccessToken, TokenVerifier from mcp.server.auth.settings import AuthSettings from mcp.server.fastmcp import FastMCP class SimpleTokenVerifier(TokenVerifier): """Simple token verifier for demonstration.""" async def verify_token(self, token: str) -> AccessToken | None: pass # This is where you would implement actual token validation # Create FastMCP instance as a Resource Server mcp = FastMCP( "Weather Service", json_response=True, # Token verifier for authentication token_verifier=SimpleTokenVerifier(), # Auth settings for RFC 9728 Protected Resource Metadata auth=AuthSettings( issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL resource_server_url=AnyHttpUrl("http://localhost:3001"), # This server's URL required_scopes=["user"], ), ) @mcp.tool() async def get_weather(city: str = "London") -> dict[str, str]: """Get weather data for a city""" return { "city": city, "temperature": "22", "condition": "Partly cloudy", "humidity": "65%", } if __name__ == "__main__": mcp.run(transport="streamable-http")
Full example: examples/snippets/servers/oauth_server.py
For a complete example with separate Authorization Server and Resource Server implementations, see examples/servers/simple-auth/.
Architecture:
- Authorization Server (AS): Handles OAuth flows, user authentication, and token issuance
- Resource Server (RS): Your MCP server that validates tokens and serves protected resources
- Client: Discovers AS through RFC 9728, obtains tokens, and uses them with the MCP server
See TokenVerifier for more details on implementing token validation.
Client-Side Authentication
The SDK includes authorization support for connecting to protected MCP servers:
""" Before running, specify running MCP RS server URL. To spin up RS server locally, see examples/servers/simple-auth/README.md cd to the `examples/snippets` directory and run: uv run oauth-client """ import asyncio from urllib.parse import parse_qs, urlparse import httpx from pydantic import AnyUrl from mcp import ClientSession from mcp.client.auth import OAuthClientProvider, TokenStorage from mcp.client.streamable_http import streamable_http_client from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken class InMemoryTokenStorage(TokenStorage): """Demo In-memory token storage implementation.""" def __init__(self): self.tokens: OAuthToken | None = None self.client_info: OAuthClientInformationFull | None = None async def get_tokens(self) -> OAuthToken | None: """Get stored tokens.""" return self.tokens async def set_tokens(self, tokens: OAuthToken) -> None: """Store tokens.""" self.tokens = tokens async def get_client_info(self) -> OAuthClientInformationFull | None: """Get stored client information.""" return self.client_info async def set_client_info(self, client_info: OAuthClientInformationFull) -> None: """Store client information.""" self.client_info = client_info async def handle_redirect(auth_url: str) -> None: print(f"Visit: {auth_url}") async def handle_callback() -> tuple[str, str | None]: callback_url = input("Paste callback URL: ") params = parse_qs(urlparse(callback_url).query) return params["code"][0], params.get("state", [None])[0] async def main(): """Run the OAuth client example.""" oauth_auth = OAuthClientProvider( server_url="http://localhost:8001", client_metadata=OAuthClientMetadata( client_name="Example MCP Client", redirect_uris=[AnyUrl("http://localhost:3000/callback")], grant_types=["authorization_code", "refresh_token"], response_types=["code"], scope="user", ), storage=InMemoryTokenStorage(), redirect_handler=handle_redirect, callback_handler=handle_callback, ) async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client: async with streamable_http_client("http://localhost:8001/mcp", http_client=custom_client) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print(f"Available tools: {[tool.name for tool in tools.tools]}") resources = await session.list_resources() print(f"Available resources: {[r.uri for r in resources.resources]}") def run(): asyncio.run(main()) if __name__ == "__main__": run()
Full example: examples/snippets/clients/oauth_client.py
For a complete working example, see examples/clients/simple-auth-client/.