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.
This commit is contained in:
Peter Skaronis 2026-07-16 04:48:56 -07:00 committed by Teknium
parent d0afcb125c
commit dc419d6e80

View file

@ -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:<port>/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://<redirect_host>:<port>/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":