Skip to content

Commit 418d7b2

Browse files
committed
updated auth examples
1 parent e975d05 commit 418d7b2

File tree

4 files changed

+241
-31
lines changed

4 files changed

+241
-31
lines changed

README.md

Lines changed: 107 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -744,30 +744,59 @@ Authentication can be used by servers that want to expose tools accessing protec
744744

745745
MCP 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
750757
from 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
760769
mcp = 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+
771800
For 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

15571586
The 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
15601605
from mcp.client.auth import OAuthClientProvider, TokenStorage
1561-
from mcp.client.session import ClientSession
15621606
from mcp.client.streamable_http import streamablehttp_client
15631607
from 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

15821644
async 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+
16061682
For a complete working example, see [`examples/clients/simple-auth-client/`](examples/clients/simple-auth-client/).
16071683

16081684
### MCP Primitives
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
Before running, specify running MCP RS server URL.
3+
To spin up RS server locally, see
4+
examples/servers/simple-auth/README.md
5+
6+
cd to the `examples/snippets` directory and run:
7+
uv run oauth-client
8+
"""
9+
10+
import asyncio
11+
from urllib.parse import parse_qs, urlparse
12+
13+
from pydantic import AnyUrl
14+
15+
from mcp import ClientSession
16+
from mcp.client.auth import OAuthClientProvider, TokenStorage
17+
from mcp.client.streamable_http import streamablehttp_client
18+
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
19+
20+
21+
class InMemoryTokenStorage(TokenStorage):
22+
"""Demo In-memory token storage implementation."""
23+
24+
def __init__(self):
25+
self.tokens: OAuthToken | None = None
26+
self.client_info: OAuthClientInformationFull | None = None
27+
28+
async def get_tokens(self) -> OAuthToken | None:
29+
"""Get stored tokens."""
30+
return self.tokens
31+
32+
async def set_tokens(self, tokens: OAuthToken) -> None:
33+
"""Store tokens."""
34+
self.tokens = tokens
35+
36+
async def get_client_info(self) -> OAuthClientInformationFull | None:
37+
"""Get stored client information."""
38+
return self.client_info
39+
40+
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
41+
"""Store client information."""
42+
self.client_info = client_info
43+
44+
45+
async def handle_redirect(auth_url: str) -> None:
46+
print(f"Visit: {auth_url}")
47+
48+
49+
async def handle_callback() -> tuple[str, str | None]:
50+
callback_url = input("Paste callback URL: ")
51+
params = parse_qs(urlparse(callback_url).query)
52+
return params["code"][0], params.get("state", [None])[0]
53+
54+
55+
async def main():
56+
"""Run the OAuth client example."""
57+
oauth_auth = OAuthClientProvider(
58+
server_url="http://localhost:8001",
59+
client_metadata=OAuthClientMetadata(
60+
client_name="Example MCP Client",
61+
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
62+
grant_types=["authorization_code", "refresh_token"],
63+
response_types=["code"],
64+
scope="user",
65+
),
66+
storage=InMemoryTokenStorage(),
67+
redirect_handler=handle_redirect,
68+
callback_handler=handle_callback,
69+
)
70+
71+
async with streamablehttp_client("http://localhost:8001/mcp", auth=oauth_auth) as (read, write, _):
72+
async with ClientSession(read, write) as session:
73+
await session.initialize()
74+
75+
tools = await session.list_tools()
76+
print(f"Available tools: {[tool.name for tool in tools.tools]}")
77+
78+
resources = await session.list_resources()
79+
print(f"Available resources: {[r.uri for r in resources.resources]}")
80+
81+
82+
def run():
83+
asyncio.run(main())
84+
85+
86+
if __name__ == "__main__":
87+
run()

examples/snippets/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ client = "clients.stdio_client:main"
2020
completion-client = "clients.completion_client:main"
2121
direct-execution-server = "servers.direct_execution:main"
2222
display-utilities-client = "clients.display_utilities:main"
23+
oauth-client = "clients.oauth_client:run"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Run from the repository root:
3+
uv run examples/snippets/servers/oauth_server.py
4+
"""
5+
6+
from pydantic import AnyHttpUrl
7+
8+
from mcp.server.auth.provider import AccessToken, TokenVerifier
9+
from mcp.server.auth.settings import AuthSettings
10+
from mcp.server.fastmcp import FastMCP
11+
12+
13+
class SimpleTokenVerifier(TokenVerifier):
14+
"""Simple token verifier for demonstration."""
15+
16+
async def verify_token(self, token: str) -> AccessToken | None:
17+
pass # This is where you would implement actual token validation
18+
19+
20+
# Create FastMCP instance as a Resource Server
21+
mcp = FastMCP(
22+
"Weather Service",
23+
# Token verifier for authentication
24+
token_verifier=SimpleTokenVerifier(),
25+
# Auth settings for RFC 9728 Protected Resource Metadata
26+
auth=AuthSettings(
27+
issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL
28+
resource_server_url=AnyHttpUrl("http://localhost:3001"), # This server's URL
29+
required_scopes=["user"],
30+
),
31+
)
32+
33+
34+
@mcp.tool()
35+
async def get_weather(city: str = "London") -> dict[str, str]:
36+
"""Get weather data for a city"""
37+
return {
38+
"city": city,
39+
"temperature": "22°C",
40+
"condition": "Partly cloudy",
41+
"humidity": "65%",
42+
}
43+
44+
45+
if __name__ == "__main__":
46+
mcp.run(transport="streamable-http")

0 commit comments

Comments
 (0)