44
44
- [ Advanced Usage] ( #advanced-usage )
45
45
- [ Low-Level Server] ( #low-level-server )
46
46
- [ Writing MCP Clients] ( #writing-mcp-clients )
47
+ - [ Parsing Tool Results] ( #parsing-tool-results )
47
48
- [ MCP Primitives] ( #mcp-primitives )
48
49
- [ Server Capabilities] ( #server-capabilities )
49
50
- [ Documentation] ( #documentation )
@@ -744,30 +745,59 @@ Authentication can be used by servers that want to expose tools accessing protec
744
745
745
746
MCP servers can use authentication by providing an implementation of the ` TokenVerifier ` protocol:
746
747
748
+ <!-- snippet-source examples/snippets/servers/oauth_server.py -->
747
749
``` python
748
- from mcp import FastMCP
749
- 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
750
758
from mcp.server.auth.settings import AuthSettings
759
+ from mcp.server.fastmcp import FastMCP
760
+
751
761
762
+ class SimpleTokenVerifier (TokenVerifier ):
763
+ """ Simple token verifier for demonstration."""
752
764
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
- ...
765
+ async def verify_token (self , token : str ) -> AccessToken | None :
766
+ pass # This is where you would implement actual token validation
758
767
759
768
769
+ # Create FastMCP instance as a Resource Server
760
770
mcp = FastMCP(
761
- " My App" ,
762
- token_verifier = MyTokenVerifier(),
771
+ " Weather Service" ,
772
+ # Token verifier for authentication
773
+ token_verifier = SimpleTokenVerifier(),
774
+ # Auth settings for RFC 9728 Protected Resource Metadata
763
775
auth = AuthSettings(
764
- issuer_url = " https://auth.example.com" ,
765
- resource_server_url = " http://localhost:3001" ,
766
- 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 " ],
767
779
),
768
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" )
769
796
```
770
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
+
771
801
For a complete example with separate Authorization Server and Resource Server implementations, see [ ` examples/servers/simple-auth/ ` ] ( examples/servers/simple-auth/ ) .
772
802
773
803
** Architecture:**
@@ -1556,46 +1586,76 @@ This ensures your client UI shows the most user-friendly names that servers prov
1556
1586
1557
1587
The SDK includes [ authorization support] ( https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization ) for connecting to protected MCP servers:
1558
1588
1589
+ <!-- snippet-source examples/snippets/clients/oauth_client.py -->
1559
1590
``` python
1560
- from mcp.client.auth import (
1561
- OAuthClientProvider,
1562
- TokenExchangeProvider,
1563
- TokenStorage,
1564
- )
1565
- from mcp.client.session import ClientSession
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
1606
+ from mcp.client.auth import OAuthClientProvider, TokenExchangeProvider, TokenStorage
1566
1607
from mcp.client.streamable_http import streamablehttp_client
1567
1608
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
1568
1609
1569
1610
1570
- class CustomTokenStorage (TokenStorage ):
1571
- """ 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
1572
1617
1573
1618
async def get_tokens (self ) -> OAuthToken | None :
1574
- pass
1619
+ """ Get stored tokens."""
1620
+ return self .tokens
1575
1621
1576
1622
async def set_tokens (self , tokens : OAuthToken) -> None :
1577
- pass
1623
+ """ Store tokens."""
1624
+ self .tokens = tokens
1578
1625
1579
1626
async def get_client_info (self ) -> OAuthClientInformationFull | None :
1580
- pass
1627
+ """ Get stored client information."""
1628
+ return self .client_info
1581
1629
1582
1630
async def set_client_info (self , client_info : OAuthClientInformationFull) -> None :
1583
- 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 ]
1584
1643
1585
1644
1586
1645
async def main ():
1587
- # Set up OAuth authentication
1646
+ """ Run the OAuth client example. """
1588
1647
oauth_auth = OAuthClientProvider(
1589
- server_url = " https ://api.example.com " ,
1648
+ server_url = " http ://localhost:8001 " ,
1590
1649
client_metadata = OAuthClientMetadata(
1591
- client_name = " My Client" ,
1592
- redirect_uris = [" http://localhost:3000/callback" ],
1650
+ client_name = " Example MCP Client" ,
1651
+ redirect_uris = [AnyUrl( " http://localhost:3000/callback" ) ],
1593
1652
grant_types = [" authorization_code" , " refresh_token" ],
1594
1653
response_types = [" code" ],
1654
+ scope = " user" ,
1595
1655
),
1596
- storage = CustomTokenStorage (),
1597
- redirect_handler = lambda url : print ( f " Visit: { url } " ) ,
1598
- callback_handler = lambda : ( " auth_code " , None ) ,
1656
+ storage = InMemoryTokenStorage (),
1657
+ redirect_handler = handle_redirect ,
1658
+ callback_handler = handle_callback ,
1599
1659
)
1600
1660
1601
1661
# For machine-to-machine scenarios, use ClientCredentialsProvider
@@ -1617,16 +1677,99 @@ async def main():
1617
1677
)
1618
1678
1619
1679
# Use with streamable HTTP client
1620
- async with streamablehttp_client(
1621
- " https://api.example.com/mcp" , auth = oauth_auth
1622
- ) as (read, write, _):
1680
+ async with streamablehttp_client(" http://localhost:8001/mcp" , auth = oauth_auth) as (read, write, _):
1623
1681
async with ClientSession(read, write) as session:
1624
1682
await session.initialize()
1625
- # Authenticated session ready
1683
+
1684
+ tools = await session.list_tools()
1685
+ print (f " Available tools: { [tool.name for tool in tools.tools]} " )
1686
+
1687
+ resources = await session.list_resources()
1688
+ print (f " Available resources: { [r.uri for r in resources.resources]} " )
1689
+
1690
+
1691
+ def run ():
1692
+ asyncio.run(main())
1693
+
1694
+
1695
+ if __name__ == " __main__" :
1696
+ run()
1626
1697
```
1627
1698
1699
+ _ Full example: [ examples/snippets/clients/oauth_client.py] ( https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/oauth_client.py ) _
1700
+ <!-- /snippet-source -->
1701
+
1628
1702
For a complete working example, see [ ` examples/clients/simple-auth-client/ ` ] ( examples/clients/simple-auth-client/ ) .
1629
1703
1704
+ ### Parsing Tool Results
1705
+
1706
+ When calling tools through MCP, the ` CallToolResult ` object contains the tool's response in a structured format. Understanding how to parse this result is essential for properly handling tool outputs.
1707
+
1708
+ ``` python
1709
+ """ examples/snippets/clients/parsing_tool_results.py"""
1710
+
1711
+ import asyncio
1712
+
1713
+ from mcp import ClientSession, StdioServerParameters, types
1714
+ from mcp.client.stdio import stdio_client
1715
+
1716
+
1717
+ async def parse_tool_results ():
1718
+ """ Demonstrates how to parse different types of content in CallToolResult."""
1719
+ server_params = StdioServerParameters(
1720
+ command = " python" , args = [" path/to/mcp_server.py" ]
1721
+ )
1722
+
1723
+ async with stdio_client(server_params) as (read, write):
1724
+ async with ClientSession(read, write) as session:
1725
+ await session.initialize()
1726
+
1727
+ # Example 1: Parsing text content
1728
+ result = await session.call_tool(" get_data" , {" format" : " text" })
1729
+ for content in result.content:
1730
+ if isinstance (content, types.TextContent):
1731
+ print (f " Text: { content.text} " )
1732
+
1733
+ # Example 2: Parsing structured content from JSON tools
1734
+ result = await session.call_tool(" get_user" , {" id" : " 123" })
1735
+ if hasattr (result, " structuredContent" ) and result.structuredContent:
1736
+ # Access structured data directly
1737
+ user_data = result.structuredContent
1738
+ print (f " User: { user_data.get(' name' )} , Age: { user_data.get(' age' )} " )
1739
+
1740
+ # Example 3: Parsing embedded resources
1741
+ result = await session.call_tool(" read_config" , {})
1742
+ for content in result.content:
1743
+ if isinstance (content, types.EmbeddedResource):
1744
+ resource = content.resource
1745
+ if isinstance (resource, types.TextResourceContents):
1746
+ print (f " Config from { resource.uri} : { resource.text} " )
1747
+ elif isinstance (resource, types.BlobResourceContents):
1748
+ print (f " Binary data from { resource.uri} " )
1749
+
1750
+ # Example 4: Parsing image content
1751
+ result = await session.call_tool(" generate_chart" , {" data" : [1 , 2 , 3 ]})
1752
+ for content in result.content:
1753
+ if isinstance (content, types.ImageContent):
1754
+ print (f " Image ( { content.mimeType} ): { len (content.data)} bytes " )
1755
+
1756
+ # Example 5: Handling errors
1757
+ result = await session.call_tool(" failing_tool" , {})
1758
+ if result.isError:
1759
+ print (" Tool execution failed!" )
1760
+ for content in result.content:
1761
+ if isinstance (content, types.TextContent):
1762
+ print (f " Error: { content.text} " )
1763
+
1764
+
1765
+ async def main ():
1766
+ await parse_tool_results()
1767
+
1768
+
1769
+ if __name__ == " __main__" :
1770
+ asyncio.run(main())
1771
+ ```
1772
+
1630
1773
### MCP Primitives
1631
1774
1632
1775
The MCP protocol defines three core primitives that servers can implement:
0 commit comments