From dc419d6e80db6c44ce00f507d977f8d13c2ffae9 Mon Sep 17 00:00:00 2001 From: Peter Skaronis Date: Thu, 16 Jul 2026 04:48:56 -0700 Subject: [PATCH] mcp_oauth: configurable redirect_host (WAF-safe localhost redirect URIs) Reclaim.ai's AWS API Gateway WAF 403s any /oauth2/authorize request whose query string contains a literal 127.0.0.1, so the SDK's hardcoded redirect_uri made the browser flow impossible. New optional oauth config key redirect_host (default 127.0.0.1, unchanged behavior) lets a server entry use localhost instead. Integrated into _resolve_redirect_uri so it composes with redirect_uri: an explicit redirect_uri wins; redirect_host only rewrites the loopback default's hostname. --- tools/mcp_oauth.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tools/mcp_oauth.py b/tools/mcp_oauth.py index 843584db34ff..bd41b09ef2ce 100644 --- a/tools/mcp_oauth.py +++ b/tools/mcp_oauth.py @@ -30,6 +30,7 @@ Configuration in config.yaml:: scope: "read write" # default: server-provided redirect_port: 0 # 0 = auto-pick free port redirect_uri: "https://proxy/callback" # default: loopback callback + redirect_host: "localhost" # loopback hostname (WAF-safe) client_name: "My Custom Client" # default: "Hermes Agent" """ @@ -923,12 +924,23 @@ def _resolve_redirect_uri(cfg: dict, port: int) -> str: A configured ``redirect_uri`` lets the callback go through a proxy (e.g. a Tailscale Funnel exposing a public HTTPS URL that forwards to localhost); - otherwise we default to ``http://127.0.0.1:/callback``. An empty value - is treated as unset. Both the client metadata and any pre-registered client - info must derive the redirect_uri here so they stay identical — a mismatch - makes the authorization server reject the callback. + otherwise we default to ``http://:/callback``. An empty + value is treated as unset. Both the client metadata and any pre-registered + client info must derive the redirect_uri here so they stay identical — a + mismatch makes the authorization server reject the callback. + + ``redirect_host`` (default ``127.0.0.1``) tweaks only the hostname of the + loopback callback. Some providers' WAFs (e.g. Reclaim.ai's AWS API Gateway) + reject any authorize request whose query string contains a literal + ``127.0.0.1``, returning ``{"message":"Forbidden"}``; ``redirect_host: + localhost`` works around that. The callback listener still binds + ``127.0.0.1`` either way. """ - return cfg.get("redirect_uri") or f"http://127.0.0.1:{port}/callback" + configured = cfg.get("redirect_uri") + if configured: + return configured + host = cfg.get("redirect_host") or "127.0.0.1" + return f"http://{host}:{port}/callback" def _build_client_metadata(cfg: dict) -> "OAuthClientMetadata":