Validate the iss authorization-response parameter (RFC 9207 / SEP-2468) (#2921)

This commit is contained in:
Marcelo Trylesinski 2026-06-20 16:54:18 +01:00 • committed by GitHub
commit 48cf4950dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 335 additions and 103 deletions

View file

@ -19,7 +19,7 @@ from urllib.parse import parse_qs, urlparse
import httpx
from mcp.client._transport import ReadStream, WriteStream
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider, TokenStorage
from mcp.client.session import ClientSession
from mcp.client.sse import sse_client
from mcp.client.streamable_http import streamable_http_client
@ -69,6 +69,7 @@ class CallbackHandler(BaseHTTPRequestHandler):
if "code" in query_params:
self.callback_data["authorization_code"] = query_params["code"][0]
self.callback_data["state"] = query_params.get("state", [None])[0]
self.callback_data["iss"] = query_params.get("iss", [None])[0]
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
@ -112,7 +113,7 @@ class CallbackServer:
self.port = port
self.server = None
self.thread = None
self.callback_data = {"authorization_code": None, "state": None, "error": None}
self.callback_data = {"authorization_code": None, "state": None, "iss": None, "error": None}
def _create_handler_with_data(self):
"""Create a handler class with access to callback data."""
@ -156,10 +157,16 @@ class CallbackServer:
time.sleep(0.1)
raise Exception("Timeout waiting for OAuth callback")
def get_state(self):
"""Get the received state parameter."""
@property
def state(self):
"""The received state parameter."""
return self.callback_data["state"]
@property
def iss(self):
"""The received iss parameter."""
return self.callback_data["iss"]
class SimpleAuthClient:
"""Simple MCP client with auth support."""
@ -183,12 +190,12 @@ class SimpleAuthClient:
callback_server = CallbackServer(port=3030)
callback_server.start()
async def callback_handler() -> tuple[str, str | None]:
"""Wait for OAuth callback and return auth code and state."""
async def callback_handler() -> AuthorizationCodeResult:
"""Wait for OAuth callback and return auth code, state, and iss."""
print("⏳ Waiting for authorization callback...")
try:
auth_code = callback_server.wait_for_callback(timeout=300)
return auth_code, callback_server.get_state()
return AuthorizationCodeResult(code=auth_code, state=callback_server.state, iss=callback_server.iss)
finally:
callback_server.stop()

View file

@ -13,7 +13,7 @@ import httpx
from pydantic import AnyUrl
from mcp import ClientSession
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider, TokenStorage
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
@ -46,10 +46,14 @@ async def handle_redirect(auth_url: str) -> None:
print(f"Visit: {auth_url}")
async def handle_callback() -> tuple[str, str | None]:
async def handle_callback() -> AuthorizationCodeResult:
callback_url = input("Paste callback URL: ")
params = parse_qs(urlparse(callback_url).query)
return params["code"][0], params.get("state", [None])[0]
return AuthorizationCodeResult(
code=params["code"][0],
state=params.get("state", [None])[0],
iss=params.get("iss", [None])[0],
)
async def main():