@@ -745,30 +745,59 @@ Authentication can be used by servers that want to expose tools accessing protec
745
745
746
746
MCP servers can use authentication by providing an implementation of the ` TokenVerifier ` protocol:
747
747
748
+ <!-- snippet-source examples/snippets/servers/oauth_server.py -->
748
749
``` 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
751
758
from mcp.server.auth.settings import AuthSettings
759
+ from mcp.server.fastmcp import FastMCP
760
+
752
761
762
+ class SimpleTokenVerifier (TokenVerifier ):
763
+ """ Simple token verifier for demonstration."""
753
764
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
759
767
760
768
769
+ # Create FastMCP instance as a Resource Server
761
770
mcp = 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
764
775
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 " ],
768
779
),
769
780
)
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" )
770
796
```
771
797
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
+
772
801
For a complete example with separate Authorization Server and Resource Server implementations, see [ ` examples/servers/simple-auth/ ` ] ( examples/servers/simple-auth/ ) .
773
802
774
803
** Architecture:**
@@ -1557,53 +1586,100 @@ This ensures your client UI shows the most user-friendly names that servers prov
1557
1586
1558
1587
The SDK includes [ authorization support] ( https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization ) for connecting to protected MCP servers:
1559
1588
1589
+ <!-- snippet-source examples/snippets/clients/oauth_client.py -->
1560
1590
``` 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
1561
1606
from mcp.client.auth import OAuthClientProvider, TokenStorage
1562
- from mcp.client.session import ClientSession
1563
1607
from mcp.client.streamable_http import streamablehttp_client
1564
1608
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
1565
1609
1566
1610
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
1569
1617
1570
1618
async def get_tokens (self ) -> OAuthToken | None :
1571
- pass
1619
+ """ Get stored tokens."""
1620
+ return self .tokens
1572
1621
1573
1622
async def set_tokens (self , tokens : OAuthToken) -> None :
1574
- pass
1623
+ """ Store tokens."""
1624
+ self .tokens = tokens
1575
1625
1576
1626
async def get_client_info (self ) -> OAuthClientInformationFull | None :
1577
- pass
1627
+ """ Get stored client information."""
1628
+ return self .client_info
1578
1629
1579
1630
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 ]
1581
1643
1582
1644
1583
1645
async def main ():
1584
- # Set up OAuth authentication
1646
+ """ Run the OAuth client example. """
1585
1647
oauth_auth = OAuthClientProvider(
1586
- server_url = " https ://api.example.com " ,
1648
+ server_url = " http ://localhost:8001 " ,
1587
1649
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" ) ],
1590
1652
grant_types = [" authorization_code" , " refresh_token" ],
1591
1653
response_types = [" code" ],
1654
+ scope = " user" ,
1592
1655
),
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 ,
1596
1659
)
1597
1660
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, _):
1602
1662
async with ClientSession(read, write) as session:
1603
1663
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()
1605
1678
```
1606
1679
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
+
1607
1683
For a complete working example, see [ ` examples/clients/simple-auth-client/ ` ] ( examples/clients/simple-auth-client/ ) .
1608
1684
1609
1685
### Parsing Tool Results
0 commit comments