@@ -744,30 +744,59 @@ Authentication can be used by servers that want to expose tools accessing protec
744744
745745MCP servers can use authentication by providing an implementation of the ` TokenVerifier ` protocol:
746746
747+ <!-- snippet-source examples/snippets/servers/oauth_server.py -->
747748``` python
748- from mcp import FastMCP
749- from mcp.server.auth.provider import TokenVerifier, TokenInfo
749+ """
750+ Run from the repository root:
751+ uv run examples/snippets/servers/oauth_server.py
752+ """
753+
754+ from pydantic import AnyHttpUrl
755+
756+ from mcp.server.auth.provider import AccessToken, TokenVerifier
750757from mcp.server.auth.settings import AuthSettings
758+ from mcp.server.fastmcp import FastMCP
759+
751760
761+ class SimpleTokenVerifier (TokenVerifier ):
762+ """ Simple token verifier for demonstration."""
752763
753- class MyTokenVerifier (TokenVerifier ):
754- # Implement token validation logic (typically via token introspection)
755- async def verify_token (self , token : str ) -> TokenInfo:
756- # Verify with your authorization server
757- ...
764+ async def verify_token (self , token : str ) -> AccessToken | None :
765+ pass # This is where you would implement actual token validation
758766
759767
768+ # Create FastMCP instance as a Resource Server
760769mcp = FastMCP(
761- " My App" ,
762- token_verifier = MyTokenVerifier(),
770+ " Weather Service" ,
771+ # Token verifier for authentication
772+ token_verifier = SimpleTokenVerifier(),
773+ # Auth settings for RFC 9728 Protected Resource Metadata
763774 auth = AuthSettings(
764- issuer_url = " https://auth.example.com" ,
765- resource_server_url = " http://localhost:3001" ,
766- required_scopes = [" mcp:read " , " mcp:write " ],
775+ issuer_url = AnyHttpUrl( " https://auth.example.com" ), # Authorization Server URL
776+ resource_server_url = AnyHttpUrl( " http://localhost:3001" ), # This server's URL
777+ required_scopes = [" user " ],
767778 ),
768779)
780+
781+
782+ @mcp.tool ()
783+ async def get_weather (city : str = " London" ) -> dict[str , str ]:
784+ """ Get weather data for a city"""
785+ return {
786+ " city" : city,
787+ " temperature" : " 22°C" ,
788+ " condition" : " Partly cloudy" ,
789+ " humidity" : " 65%" ,
790+ }
791+
792+
793+ if __name__ == " __main__" :
794+ mcp.run(transport = " streamable-http" )
769795```
770796
797+ _ Full example: [ examples/snippets/servers/oauth_server.py] ( https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/oauth_server.py ) _
798+ <!-- /snippet-source -->
799+
771800For a complete example with separate Authorization Server and Resource Server implementations, see [ ` examples/servers/simple-auth/ ` ] ( examples/servers/simple-auth/ ) .
772801
773802** Architecture:**
@@ -1556,53 +1585,100 @@ This ensures your client UI shows the most user-friendly names that servers prov
15561585
15571586The SDK includes [ authorization support] ( https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization ) for connecting to protected MCP servers:
15581587
1588+ <!-- snippet-source examples/snippets/clients/oauth_client.py -->
15591589``` python
1590+ """
1591+ Before running, specify running MCP RS server URL.
1592+ To spin up RS server locally, see
1593+ examples/servers/simple-auth/README.md
1594+
1595+ cd to the `examples/snippets` directory and run:
1596+ uv run oauth-client
1597+ """
1598+
1599+ import asyncio
1600+ from urllib.parse import parse_qs, urlparse
1601+
1602+ from pydantic import AnyUrl
1603+
1604+ from mcp import ClientSession
15601605from mcp.client.auth import OAuthClientProvider, TokenStorage
1561- from mcp.client.session import ClientSession
15621606from mcp.client.streamable_http import streamablehttp_client
15631607from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
15641608
15651609
1566- class CustomTokenStorage (TokenStorage ):
1567- """ Simple in-memory token storage implementation."""
1610+ class InMemoryTokenStorage (TokenStorage ):
1611+ """ Demo In-memory token storage implementation."""
1612+
1613+ def __init__ (self ):
1614+ self .tokens: OAuthToken | None = None
1615+ self .client_info: OAuthClientInformationFull | None = None
15681616
15691617 async def get_tokens (self ) -> OAuthToken | None :
1570- pass
1618+ """ Get stored tokens."""
1619+ return self .tokens
15711620
15721621 async def set_tokens (self , tokens : OAuthToken) -> None :
1573- pass
1622+ """ Store tokens."""
1623+ self .tokens = tokens
15741624
15751625 async def get_client_info (self ) -> OAuthClientInformationFull | None :
1576- pass
1626+ """ Get stored client information."""
1627+ return self .client_info
15771628
15781629 async def set_client_info (self , client_info : OAuthClientInformationFull) -> None :
1579- pass
1630+ """ Store client information."""
1631+ self .client_info = client_info
1632+
1633+
1634+ async def handle_redirect (auth_url : str ) -> None :
1635+ print (f " Visit: { auth_url} " )
1636+
1637+
1638+ async def handle_callback () -> tuple[str , str | None ]:
1639+ callback_url = input (" Paste callback URL: " )
1640+ params = parse_qs(urlparse(callback_url).query)
1641+ return params[" code" ][0 ], params.get(" state" , [None ])[0 ]
15801642
15811643
15821644async def main ():
1583- # Set up OAuth authentication
1645+ """ Run the OAuth client example. """
15841646 oauth_auth = OAuthClientProvider(
1585- server_url = " https ://api.example.com " ,
1647+ server_url = " http ://localhost:8001 " ,
15861648 client_metadata = OAuthClientMetadata(
1587- client_name = " My Client" ,
1588- redirect_uris = [" http://localhost:3000/callback" ],
1649+ client_name = " Example MCP Client" ,
1650+ redirect_uris = [AnyUrl( " http://localhost:3000/callback" ) ],
15891651 grant_types = [" authorization_code" , " refresh_token" ],
15901652 response_types = [" code" ],
1653+ scope = " user" ,
15911654 ),
1592- storage = CustomTokenStorage (),
1593- redirect_handler = lambda url : print ( f " Visit: { url } " ) ,
1594- callback_handler = lambda : ( " auth_code " , None ) ,
1655+ storage = InMemoryTokenStorage (),
1656+ redirect_handler = handle_redirect ,
1657+ callback_handler = handle_callback ,
15951658 )
15961659
1597- # Use with streamable HTTP client
1598- async with streamablehttp_client(
1599- " https://api.example.com/mcp" , auth = oauth_auth
1600- ) as (read, write, _):
1660+ async with streamablehttp_client(" http://localhost:8001/mcp" , auth = oauth_auth) as (read, write, _):
16011661 async with ClientSession(read, write) as session:
16021662 await session.initialize()
1603- # Authenticated session ready
1663+
1664+ tools = await session.list_tools()
1665+ print (f " Available tools: { [tool.name for tool in tools.tools]} " )
1666+
1667+ resources = await session.list_resources()
1668+ print (f " Available resources: { [r.uri for r in resources.resources]} " )
1669+
1670+
1671+ def run ():
1672+ asyncio.run(main())
1673+
1674+
1675+ if __name__ == " __main__" :
1676+ run()
16041677```
16051678
1679+ _ Full example: [ examples/snippets/clients/oauth_client.py] ( https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/oauth_client.py ) _
1680+ <!-- /snippet-source -->
1681+
16061682For a complete working example, see [ ` examples/clients/simple-auth-client/ ` ] ( examples/clients/simple-auth-client/ ) .
16071683
16081684### MCP Primitives
0 commit comments