From 842959cc6043633c3f97d54f614b2fbec726eb8b Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 04:16:46 +0000 Subject: [PATCH] fix(mcp): strip default port from get_request_base_url netloc get_request_base_url appended X-Forwarded-Port unconditionally when building the netloc, producing redirect_uri values like https://host:443/callback. Providers that canonicalize and exact-match the registered redirect URI rejected the flow. Apply _strip_default_port (already in oauth_utils) to the constructed netloc before returning so :443 for HTTPS and :80 for HTTP are dropped. --- .../_experimental/mcp_server/oauth_utils.py | 1 + .../mcp_server/test_discoverable_endpoints.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/litellm/proxy/_experimental/mcp_server/oauth_utils.py b/litellm/proxy/_experimental/mcp_server/oauth_utils.py index 4d5813dbc5b..96647edad77 100644 --- a/litellm/proxy/_experimental/mcp_server/oauth_utils.py +++ b/litellm/proxy/_experimental/mcp_server/oauth_utils.py @@ -129,6 +129,7 @@ def get_request_base_url(request: Request) -> str: if x_forwarded_port and ":" not in netloc: netloc = f"{netloc}:{x_forwarded_port}" + netloc = _strip_default_port(scheme, netloc) return urlunparse((scheme, netloc, parsed.path, "", "", "")) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py index b4a26c911dc..54c7eb92e1a 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py @@ -1103,6 +1103,38 @@ async def test_token_endpoint_respects_x_forwarded_host(): None, "https://external.com", ), + # Case 15: X-Forwarded-Port 443 with HTTPS must NOT appear in the URL + ( + "http://localhost:4000/", + "https", + "proxy.example.com", + "443", + "https://proxy.example.com", + ), + # Case 16: X-Forwarded-Port 80 with HTTP must NOT appear in the URL + ( + "http://localhost:4000/", + "http", + "proxy.example.com", + "80", + "http://proxy.example.com", + ), + # Case 17: X-Forwarded-Port 443 without X-Forwarded-Host (portless base) + ( + "http://internal.local/", + "https", + None, + "443", + "https://internal.local", + ), + # Case 18: Non-default port is preserved + ( + "http://localhost:4000/", + "https", + "proxy.example.com", + "8443", + "https://proxy.example.com:8443", + ), ], ) def test_get_request_base_url_comprehensive(