diff --git a/tools/mcp_dashboard_oauth.py b/tools/mcp_dashboard_oauth.py index ffcac3a8cc..782cb70eb1 100644 --- a/tools/mcp_dashboard_oauth.py +++ b/tools/mcp_dashboard_oauth.py @@ -75,9 +75,7 @@ class DashboardOAuthFlow: ) -> None: """Hand the browser redirect to the waiting flow; ``state`` must match exactly. - ``iss`` (RFC 9207) is carried through: mcp 2.x rejects an authorization response that omits - it when the server advertised ``authorization_response_iss_parameter_supported``, which - Cloudflare and Resend both do. Dropping it breaks login against those providers. + ``iss`` (RFC 9207) is carried through — see ``tools.mcp_oauth._parse_redirect_query``. """ with self._lock: if self._callback_ready.is_set(): diff --git a/tui_gateway/mcp_oauth_sessions.py b/tui_gateway/mcp_oauth_sessions.py index c09e55d09f..ec98864a93 100644 --- a/tui_gateway/mcp_oauth_sessions.py +++ b/tui_gateway/mcp_oauth_sessions.py @@ -14,7 +14,7 @@ import time from contextlib import suppress from pathlib import Path from typing import Any, Dict, Optional -from urllib.parse import parse_qs, urlparse +from urllib.parse import urlparse # session_id -> record wrapping the shared DashboardOAuthFlow bridge plus bookkeeping. _sessions: Dict[str, Dict[str, Any]] = {} @@ -56,12 +56,12 @@ def _start_loopback_listener(flow) -> "http.server.HTTPServer": self.send_response(404) self.end_headers() return - qs = parse_qs(parsed.query) + from tools.mcp_oauth import _parse_redirect_query + body = b"

Authorization received

You can close this tab and return to Hermes.

" status = 200 try: - flow.deliver_callback( - **{k: (qs.get(k) or [None])[0] for k in ("code", "state", "error", "iss")}) + flow.deliver_callback(**_parse_redirect_query(parsed.query)) except Exception: body = b"

OAuth callback rejected

The callback was invalid or already used.

" status = 400