@@ -745,30 +745,59 @@ Authentication can be used by servers that want to expose tools accessing protec
745745
746746MCP servers can use authentication by providing an implementation of the ` TokenVerifier ` protocol:
747747
748+ <!-- snippet-source examples/snippets/servers/oauth_server.py -->
748749``` python
749- from mcp import FastMCP
750- from mcp.server.auth.provider import TokenVerifier, TokenInfo
750+ """
751+ Run from the repository root:
752+ uv run examples/snippets/servers/oauth_server.py
753+ """
754+
755+ from pydantic import AnyHttpUrl
756+
757+ from mcp.server.auth.provider import AccessToken, TokenVerifier
751758from mcp.server.auth.settings import AuthSettings
759+ from mcp.server.fastmcp import FastMCP
760+
752761
762+ class SimpleTokenVerifier (TokenVerifier ):
763+ """ Simple token verifier for demonstration."""
753764
754- class MyTokenVerifier (TokenVerifier ):
755- # Implement token validation logic (typically via token introspection)
756- async def verify_token (self , token : str ) -> TokenInfo:
757- # Verify with your authorization server
758- ...
765+ async def verify_token (self , token : str ) -> AccessToken | None :
766+ pass # This is where you would implement actual token validation
759767
760768
769+ # Create FastMCP instance as a Resource Server
761770mcp = FastMCP(
762- " My App" ,
763- token_verifier = MyTokenVerifier(),
771+ " Weather Service" ,
772+ # Token verifier for authentication
773+ token_verifier = SimpleTokenVerifier(),
774+ # Auth settings for RFC 9728 Protected Resource Metadata
764775 auth = AuthSettings(
765- issuer_url = " https://auth.example.com" ,
766- resource_server_url = " http://localhost:3001" ,
767- required_scopes = [" mcp:read " , " mcp:write " ],
776+ issuer_url = AnyHttpUrl( " https://auth.example.com" ), # Authorization Server URL
777+ resource_server_url = AnyHttpUrl( " http://localhost:3001" ), # This server's URL
778+ required_scopes = [" user " ],
768779 ),
769780)
781+
782+
783+ @mcp.tool ()
784+ async def get_weather (city : str = " London" ) -> dict[str , str ]:
785+ """ Get weather data for a city"""
786+ return {
787+ " city" : city,
788+ " temperature" : " 22" ,
789+ " condition" : " Partly cloudy" ,
790+ " humidity" : " 65%" ,
791+ }
792+
793+
794+ if __name__ == " __main__" :
795+ mcp.run(transport = " streamable-http" )
770796```
771797
798+ _ Full example: [ examples/snippets/servers/oauth_server.py] ( https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/oauth_server.py ) _
799+ <!-- /snippet-source -->
800+
772801For a complete example with separate Authorization Server and Resource Server implementations, see [ ` examples/servers/simple-auth/ ` ] ( examples/servers/simple-auth/ ) .
773802
774803** Architecture:**
@@ -1557,53 +1586,100 @@ This ensures your client UI shows the most user-friendly names that servers prov
15571586
15581587The SDK includes [ authorization support] ( https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization ) for connecting to protected MCP servers:
15591588
1589+ <!-- snippet-source examples/snippets/clients/oauth_client.py -->
15601590``` python
1591+ """
1592+ Before running, specify running MCP RS server URL.
1593+ To spin up RS server locally, see
1594+ examples/servers/simple-auth/README.md
1595+
1596+ cd to the `examples/snippets` directory and run:
1597+ uv run oauth-client
1598+ """
1599+
1600+ import asyncio
1601+ from urllib.parse import parse_qs, urlparse
1602+
1603+ from pydantic import AnyUrl
1604+
1605+ from mcp import ClientSession
15611606from mcp.client.auth import OAuthClientProvider, TokenStorage
1562- from mcp.client.session import ClientSession
15631607from mcp.client.streamable_http import streamablehttp_client
15641608from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
15651609
15661610
1567- class CustomTokenStorage (TokenStorage ):
1568- """ Simple in-memory token storage implementation."""
1611+ class InMemoryTokenStorage (TokenStorage ):
1612+ """ Demo In-memory token storage implementation."""
1613+
1614+ def __init__ (self ):
1615+ self .tokens: OAuthToken | None = None
1616+ self .client_info: OAuthClientInformationFull | None = None
15691617
15701618 async def get_tokens (self ) -> OAuthToken | None :
1571- pass
1619+ """ Get stored tokens."""
1620+ return self .tokens
15721621
15731622 async def set_tokens (self , tokens : OAuthToken) -> None :
1574- pass
1623+ """ Store tokens."""
1624+ self .tokens = tokens
15751625
15761626 async def get_client_info (self ) -> OAuthClientInformationFull | None :
1577- pass
1627+ """ Get stored client information."""
1628+ return self .client_info
15781629
15791630 async def set_client_info (self , client_info : OAuthClientInformationFull) -> None :
1580- pass
1631+ """ Store client information."""
1632+ self .client_info = client_info
1633+
1634+
1635+ async def handle_redirect (auth_url : str ) -> None :
1636+ print (f " Visit: { auth_url} " )
1637+
1638+
1639+ async def handle_callback () -> tuple[str , str | None ]:
1640+ callback_url = input (" Paste callback URL: " )
1641+ params = parse_qs(urlparse(callback_url).query)
1642+ return params[" code" ][0 ], params.get(" state" , [None ])[0 ]
15811643
15821644
15831645async def main ():
1584- # Set up OAuth authentication
1646+ """ Run the OAuth client example. """
15851647 oauth_auth = OAuthClientProvider(
1586- server_url = " https ://api.example.com " ,
1648+ server_url = " http ://localhost:8001 " ,
15871649 client_metadata = OAuthClientMetadata(
1588- client_name = " My Client" ,
1589- redirect_uris = [" http://localhost:3000/callback" ],
1650+ client_name = " Example MCP Client" ,
1651+ redirect_uris = [AnyUrl( " http://localhost:3000/callback" ) ],
15901652 grant_types = [" authorization_code" , " refresh_token" ],
15911653 response_types = [" code" ],
1654+ scope = " user" ,
15921655 ),
1593- storage = CustomTokenStorage (),
1594- redirect_handler = lambda url : print ( f " Visit: { url } " ) ,
1595- callback_handler = lambda : ( " auth_code " , None ) ,
1656+ storage = InMemoryTokenStorage (),
1657+ redirect_handler = handle_redirect ,
1658+ callback_handler = handle_callback ,
15961659 )
15971660
1598- # Use with streamable HTTP client
1599- async with streamablehttp_client(
1600- " https://api.example.com/mcp" , auth = oauth_auth
1601- ) as (read, write, _):
1661+ async with streamablehttp_client(" http://localhost:8001/mcp" , auth = oauth_auth) as (read, write, _):
16021662 async with ClientSession(read, write) as session:
16031663 await session.initialize()
1604- # Authenticated session ready
1664+
1665+ tools = await session.list_tools()
1666+ print (f " Available tools: { [tool.name for tool in tools.tools]} " )
1667+
1668+ resources = await session.list_resources()
1669+ print (f " Available resources: { [r.uri for r in resources.resources]} " )
1670+
1671+
1672+ def run ():
1673+ asyncio.run(main())
1674+
1675+
1676+ if __name__ == " __main__" :
1677+ run()
16051678```
16061679
1680+ _ Full example: [ examples/snippets/clients/oauth_client.py] ( https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/oauth_client.py ) _
1681+ <!-- /snippet-source -->
1682+
16071683For a complete working example, see [ ` examples/clients/simple-auth-client/ ` ] ( examples/clients/simple-auth-client/ ) .
16081684
16091685### Parsing Tool Results
0 commit comments