4444 - [ Advanced Usage] ( #advanced-usage )
4545 - [ Low-Level Server] ( #low-level-server )
4646 - [ Writing MCP Clients] ( #writing-mcp-clients )
47+ - [ Parsing Tool Results] ( #parsing-tool-results )
4748 - [ MCP Primitives] ( #mcp-primitives )
4849 - [ Server Capabilities] ( #server-capabilities )
4950 - [ Documentation] ( #documentation )
@@ -744,30 +745,59 @@ Authentication can be used by servers that want to expose tools accessing protec
744745
745746MCP servers can use authentication by providing an implementation of the ` TokenVerifier ` protocol:
746747
748+ <!-- snippet-source examples/snippets/servers/oauth_server.py -->
747749``` 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
750758from mcp.server.auth.settings import AuthSettings
759+ from mcp.server.fastmcp import FastMCP
760+
751761
762+ class SimpleTokenVerifier (TokenVerifier ):
763+ """ Simple token verifier for demonstration."""
752764
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
758767
759768
769+ # Create FastMCP instance as a Resource Server
760770mcp = 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
763775 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 " ],
767779 ),
768780)
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" )
769796```
770797
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+
771801For a complete example with separate Authorization Server and Resource Server implementations, see [ ` examples/servers/simple-auth/ ` ] ( examples/servers/simple-auth/ ) .
772802
773803** Architecture:**
@@ -1556,46 +1586,76 @@ This ensures your client UI shows the most user-friendly names that servers prov
15561586
15571587The SDK includes [ authorization support] ( https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization ) for connecting to protected MCP servers:
15581588
1589+ <!-- snippet-source examples/snippets/clients/oauth_client.py -->
15591590``` 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
15661607from mcp.client.streamable_http import streamablehttp_client
15671608from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
15681609
15691610
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
15721617
15731618 async def get_tokens (self ) -> OAuthToken | None :
1574- pass
1619+ """ Get stored tokens."""
1620+ return self .tokens
15751621
15761622 async def set_tokens (self , tokens : OAuthToken) -> None :
1577- pass
1623+ """ Store tokens."""
1624+ self .tokens = tokens
15781625
15791626 async def get_client_info (self ) -> OAuthClientInformationFull | None :
1580- pass
1627+ """ Get stored client information."""
1628+ return self .client_info
15811629
15821630 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 ]
15841643
15851644
15861645async def main ():
1587- # Set up OAuth authentication
1646+ """ Run the OAuth client example. """
15881647 oauth_auth = OAuthClientProvider(
1589- server_url = " https ://api.example.com " ,
1648+ server_url = " http ://localhost:8001 " ,
15901649 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" ) ],
15931652 grant_types = [" authorization_code" , " refresh_token" ],
15941653 response_types = [" code" ],
1654+ scope = " user" ,
15951655 ),
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 ,
15991659 )
16001660
16011661 # For machine-to-machine scenarios, use ClientCredentialsProvider
@@ -1617,16 +1677,99 @@ async def main():
16171677 )
16181678
16191679 # 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, _):
16231681 async with ClientSession(read, write) as session:
16241682 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()
16261697```
16271698
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+
16281702For a complete working example, see [ ` examples/clients/simple-auth-client/ ` ] ( examples/clients/simple-auth-client/ ) .
16291703
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+
16301773### MCP Primitives
16311774
16321775The MCP protocol defines three core primitives that servers can implement:
0 commit comments