Skip to content

Commit a894b6b

Browse files
committed
CI fixes
1 parent d9441a2 commit a894b6b

File tree

2 files changed

+52
-27
lines changed

2 files changed

+52
-27
lines changed

README.md

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -801,31 +801,50 @@ async def main():
801801
The SDK includes [authorization support](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for connecting to protected MCP servers:
802802

803803
```python
804-
from mcp.client.auth import OAuthClientProvider
804+
from mcp.client.auth import OAuthClientProvider, TokenStorage
805+
from mcp.client.session import ClientSession
805806
from mcp.client.streamable_http import streamablehttp_client
806-
from mcp.shared.auth import OAuthClientMetadata
807-
import webbrowser
808-
809-
# Set up OAuth authentication
810-
oauth_auth = OAuthClientProvider(
811-
server_url="https://api.example.com",
812-
client_metadata=OAuthClientMetadata(
813-
client_name="My Client",
814-
redirect_uris=["http://localhost:3000/callback"],
815-
grant_types=["authorization_code", "refresh_token"],
816-
response_types=["code"],
817-
scope="read write"
818-
),
819-
storage=InMemoryTokenStorage(), # Implement TokenStorage protocol
820-
redirect_handler=lambda url: webbrowser.open(url),
821-
callback_handler=handle_oauth_callback, # Handle OAuth callback
822-
)
807+
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
808+
809+
810+
class CustomTokenStorage(TokenStorage):
811+
"""Simple in-memory token storage implementation."""
812+
813+
async def get_tokens(self) -> OAuthToken | None:
814+
pass
815+
816+
async def set_tokens(self, tokens: OAuthToken) -> None:
817+
pass
818+
819+
async def get_client_info(self) -> OAuthClientInformationFull | None:
820+
pass
821+
822+
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
823+
pass
823824

824-
# Use with streamable HTTP client
825-
async with streamablehttp_client("https://api.example.com/mcp", auth=oauth_auth) as (read, write, _):
826-
async with ClientSession(read, write) as session:
827-
await session.initialize()
828-
# Authenticated session ready
825+
826+
async def main():
827+
# Set up OAuth authentication
828+
oauth_auth = OAuthClientProvider(
829+
server_url="https://api.example.com",
830+
client_metadata=OAuthClientMetadata(
831+
client_name="My Client",
832+
redirect_uris=["http://localhost:3000/callback"],
833+
grant_types=["authorization_code", "refresh_token"],
834+
response_types=["code"],
835+
),
836+
storage=CustomTokenStorage(),
837+
redirect_handler=lambda url: print(f"Visit: {url}"),
838+
callback_handler=lambda: ("auth_code", None),
839+
)
840+
841+
# Use with streamable HTTP client
842+
async with streamablehttp_client(
843+
"https://api.example.com/mcp", auth=oauth_auth
844+
) as (read, write, _):
845+
async with ClientSession(read, write) as session:
846+
await session.initialize()
847+
# Authenticated session ready
829848
```
830849

831850
For a complete working example, see [`examples/clients/simple-auth-client/`](examples/clients/simple-auth-client/).

src/mcp/client/auth.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def _generate_code_challenge(self, code_verifier: str) -> str:
113113
def _get_authorization_base_url(self, server_url: str) -> str:
114114
"""
115115
Extract base URL by removing path component.
116-
116+
117117
Per MCP spec 2.3.2: https://api.example.com/v1/mcp -> https://api.example.com
118118
"""
119119
from urllib.parse import urlparse, urlunparse
@@ -176,7 +176,11 @@ async def _register_oauth_client(
176176
registration_url = urljoin(auth_base_url, "/register")
177177

178178
# Handle default scope
179-
if client_metadata.scope is None and metadata and metadata.scopes_supported is not None:
179+
if (
180+
client_metadata.scope is None
181+
and metadata
182+
and metadata.scopes_supported is not None
183+
):
180184
client_metadata.scope = " ".join(metadata.scopes_supported)
181185

182186
# Serialize client metadata
@@ -245,7 +249,7 @@ def _has_valid_token(self) -> bool:
245249
async def _validate_token_scopes(self, token_response: OAuthToken) -> None:
246250
"""
247251
Validate returned scopes against requested scopes.
248-
252+
249253
Per OAuth 2.1 Section 3.3: server may grant subset, not superset.
250254
"""
251255
if not token_response.scope:
@@ -360,7 +364,9 @@ async def _perform_oauth_flow(self) -> None:
360364
if returned_state is None or not secrets.compare_digest(
361365
returned_state, self._auth_state
362366
):
363-
raise Exception(f"State parameter mismatch: {returned_state} != {self._auth_state}")
367+
raise Exception(
368+
f"State parameter mismatch: {returned_state} != {self._auth_state}"
369+
)
364370

365371
# Clear state after validation
366372
self._auth_state = None

0 commit comments

Comments
 (0)