Skip to content

fix(mcp): log actionable OAuth discovery failures for misconfigured server urls - #34225

Merged
tin-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_lit4658_oauth_discovery_logging
Jul 23, 2026
Merged

fix(mcp): log actionable OAuth discovery failures for misconfigured server urls#34225
tin-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_lit4658_oauth_discovery_logging

Conversation

@tin-berri

@tin-berri tin-berri commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • A typo'd MCP server url fails OAuth discovery silently (debug-only logs)
  • The later /authorize 400 blames "servers with no url"
  • Operators cannot tell from the logs what was misconfigured

How it solves it:

  • Discovery failure logs one warning listing each attempt's outcome
  • Both server loaders warn which OAuth endpoints stayed unresolved, with remedies
  • The 400 detail matches the server's shape and points at the logs

Relevant issues

  • Makes a misconfigured MCP server url diagnosable from default-level proxy logs
  • Adds a per-step OAuth discovery failure trail to one warning per broken server
  • Corrects the misleading authorize/token/register 400 for servers that do have a url

Linear ticket

Resolves LIT-4658

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Note: the lint-e2e-basedpyright gate is red on the unmodified PR base (8 pre-existing errors in tests/e2e/load/test_session_anomaly.py from the staging tip merge #34166); this diff touches nothing under tests/e2e and every gate it does touch is green

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live proxy on 127.0.0.1:4658, no DB, config with three oauth2 servers: a typo'd host (DNS cannot resolve), a typo'd path on a host with no OAuth discovery (api.github.com/mcp), and a working control (mcp.linear.app/sse)

mcp_servers:
  typo_host_server:
    url: "https://mcp.linear-typo-lit4658.app/sse"
    transport: http
    auth_type: oauth2
    oauth2_flow: authorization_code
  typo_path_server:
    url: "https://api.github.com/mcp"
    transport: http
    auth_type: oauth2
    oauth2_flow: authorization_code
  working_server:
    url: "https://mcp.linear.app/sse"
    transport: http
    auth_type: oauth2
    oauth2_flow: authorization_code

Before, at the PR base 6375923 (stock litellm_internal_staging): startup logs contain zero warning lines about either broken server, and the authorize wall misdirects the operator

$ curl -s "http://127.0.0.1:4658/typo_host_server/authorize?redirect_uri=http://localhost:8899/callback&client_id=demo-client"
{"detail":"MCP server authorization url is not configured. Servers with no url (OpenAPI spec or stdio) run no resource discovery, so set Authorization URL and Token URL manually, or set Issuer to discover them from the identity provider (RFC 8414)."}

$ grep -icE "warning" before.log
0

After, at cfbcef3: the same startup logs the reason per server, urls reduced to origin

LiteLLM:WARNING: mcp_server_manager.py - MCP OAuth endpoint discovery against https://mcp.linear-typo-lit4658.app found no authorization server metadata. Attempts: GET https://mcp.linear-typo-lit4658.app: ConnectError: Cannot connect to host mcp.linear-typo-lit4658.app:443 [...] [nodename nor servname provided, or not known]. The MCP server url may be misconfigured, or the upstream may not support OAuth discovery (RFC 9728 / RFC 8414)
LiteLLM:WARNING: mcp_server_manager.py - MCP server typo_host_server: OAuth endpoint discovery left authorization_url, token_url unresolved (server url origin: https://mcp.linear-typo-lit4658.app). OAuth flows that need them will fail with 'not configured' errors until they resolve. Check the preceding 'MCP OAuth' log lines for why discovery failed, verify the configured server url, or set the unresolved endpoint urls manually, or set issuer to discover them from the identity provider (RFC 8414)
LiteLLM:WARNING: mcp_server_manager.py - MCP OAuth endpoint discovery against https://api.github.com found no authorization server metadata. Attempts: GET https://api.github.com: HTTP 404; no challenge-advertised resource metadata; well-known protected-resource lookup found no authorization servers; origin fallback: no authorization server metadata at https://api.github.com. The MCP server url may be misconfigured, or the upstream may not support OAuth discovery (RFC 9728 / RFC 8414)
LiteLLM:WARNING: mcp_server_manager.py - MCP server typo_path_server: OAuth endpoint discovery left authorization_url, token_url unresolved (server url origin: https://api.github.com). [...]

and the authorize wall now names the likely cause

$ curl -s "http://127.0.0.1:4658/typo_host_server/authorize?redirect_uri=http://localhost:8899/callback&client_id=demo-client"
{"detail":"MCP server authorization url is not configured. OAuth endpoint discovery against the configured server url did not resolve it; the url may be misconfigured. Check the proxy logs for 'MCP OAuth' warnings from server load, verify the server url, or set Authorization URL and Token URL manually, or set Issuer to discover them from the identity provider (RFC 8414)."}

Negative control on the same run: the correctly configured server discovers Linear's IdP and keeps redirecting, with no warnings logged for it

$ curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" "http://127.0.0.1:4658/working_server/authorize?redirect_uri=http://localhost:8899/callback&client_id=demo-client"
307 -> https://mcp.linear.app/authorize?client_id=demo-client&redirect_uri=http%3A%2F%2F127.0.0.1%3A4658%2Fcallback&state=...&response_type=code&scope=read+write

Type

🐛 Bug Fix

Changes

  • _descovery_metadata gains warn_when_no_metadata: an all-empty discovery result logs one warning listing each step's outcome (initial GET status or exception class, well-known lookups, origin fallback), with urls reduced to origin and exception text stripped of embedded urls
  • both server loaders (config yaml and DB rows) call a shared _warn_oauth_endpoints_unresolved naming the server, the endpoints its flow needs that stayed unresolved (client_credentials never needs authorization_url, OBO needs only token_url), and the remedies; this replaces the DB path's reason-less warning and closes the config path's no-warning gap
  • the authorize/token/register 400 details go through a shared _endpoint_not_configured_detail that branches on server shape: anchored issuer, url set (discovery failed, check logs), or genuinely url-less (wording unchanged)
  • _redact_mcp_resource_url moves from server.py to oauth_utils.py so the manager can import it without a cycle; behavior unchanged, server.py imports it from the new home
  • what does not change: discovery behavior and results, the issuer-anchored arm's existing RFC 8414 §3.3 warning, and the 400 status codes; the new details stay free of urls and issuer values because these endpoints are reachable pre-auth

Two things a reviewer may ask about. The DB path's old "yielded no metadata" warning also fired when manual endpoints were fully set and only scopes failed to discover; that case is deliberately silent now because scope-less metadata is normal for many servers and nothing actionable follows, and scopes never trigger the new warning on their own. The loader warning fires at build time while last-known-good carry-forward can still restore endpoints afterwards, so it says "until they resolve" rather than promising a 400

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Note

Low Risk
Observability and operator-facing error text only; discovery logic and HTTP status codes are unchanged, with broad test coverage for logging and API details.

Overview
MCP OAuth misconfiguration is now visible at default log level, and authorize/token/register 400 responses match how the server is set up instead of always blaming url-less servers.

On discovery, _descovery_metadata can emit one WARNING with a per-step attempt trail (HTTP status, well-known lookups, origin fallback), using redacted origins and sanitized exception text. Config and DB server builds call _warn_oauth_endpoints_unresolved when flows still lack needed endpoints after manual config—scoped by flow (client_credentials skips authorization_url; OBO only needs token_url).

Missing authorization / token URLs on discoverable endpoints now use _endpoint_not_configured_detail: anchored issuer failure, url present but discovery failed (check logs), or genuinely no url.

_redact_mcp_resource_url moves to oauth_utils.py (shared by manager logging); malformed ports no longer raise from lazy urlsplit validation.

Reviewed by Cursor Bugbot for commit df75d29. Bugbot is set up for automated code reviews on this repo. Configure here.

…erver urls

A typo'd MCP server url failed OAuth endpoint discovery silently: every
failure died at debug level, the config loader warned nothing, and the
/authorize 400 blamed "servers with no url" even when a url was set.

_descovery_metadata now records each attempt's outcome and, when a total
failure would leave the server's flow without a needed endpoint, logs one
warning with the trail (urls origin-only, exception text url-stripped).
Both server loaders warn which endpoints stayed unresolved for the
server's flow (client_credentials never needs authorization_url, OBO
needs only token_url) with the remedies; this replaces the DB path's
reason-less warning and closes the config path's no-warning gap. The
authorize/token/register 400 details branch on server shape via one
shared helper and point at the proxy logs. _redact_mcp_resource_url
moves to oauth_utils.py so the manager can import it without a cycle.

Resolves LIT-4658
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes failed MCP OAuth discovery easier to diagnose. The main changes are:

  • Logs a redacted trail of failed discovery attempts
  • Warns when required OAuth endpoints remain unresolved
  • Tailors endpoint errors to URL, issuer, and URL-less server configurations
  • Shares URL-redaction logic across the server and manager
  • Adds tests for loader warnings, endpoint errors, and malformed ports

Confidence Score: 5/5

This looks safe to merge.

  • The malformed-port failure is fixed by resolving lazy URL properties inside the exception guard.
  • Callers handle unparseable URLs with safe fallback labels.
  • The added test directly covers the input that previously escaped the guard.
  • No blocking issues remain in the updated code.

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py Adds server-specific details for unresolved authorization and token endpoints without exposing configured values.
litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Adds redacted discovery-attempt warnings and flow-aware unresolved-endpoint warnings for both server loaders.
litellm/proxy/_experimental/mcp_server/oauth_utils.py Centralizes MCP URL redaction and catches malformed ports during lazy URL parsing.
litellm/proxy/_experimental/mcp_server/server.py Uses the shared MCP URL-redaction helper.
tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py Covers endpoint errors for URL-based, issuer-based, and URL-less server configurations.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py Adds a direct malformed-port test for the shared URL-redaction helper.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py Covers sanitized discovery warnings and endpoint requirements across configuration and database loading.

Reviews (2): Last reviewed commit: "fix(mcp): keep url redaction total when ..." | Re-trigger Greptile

Comment thread litellm/proxy/_experimental/mcp_server/oauth_utils.py Outdated
urlsplit validates the port lazily, so a non-numeric port raised
ValueError out of _redact_mcp_resource_url after the urlsplit try
had already passed; the server loaders now call the helper while
warning about typo'd urls, which would have turned the warning into
a load failure. Resolve hostname and port inside the guard and pin
the malformed-port case in the redaction test
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_lit4658_oauth_discovery_logging (55b0046) with litellm_internal_staging (2bfd50e)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (55b0046) during the generation of this report, so 2bfd50e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@tin-berri

Copy link
Copy Markdown
Contributor Author

CI status: 79/80 checks green. The one red, "All Other Providers / Run tests", is a pre-existing flake unrelated to this diff. It fails only on tests/test_litellm/llms/bedrock/chat/agentcore/test_agentcore_transformation.py::TestAgentCoreAcceptHeader::test_accept_header_in_completion_request_jwt with Expected 'post' to have been called once. Called 0 times under a parallel xdist worker. That file is not in this PR (this PR touches only MCP OAuth files under litellm/proxy/_experimental/mcp_server) and was last modified months ago. The test passes in isolation and the whole agentcore file passes in-order locally, so this is cross-test mock pollution under sharding, not a regression from this change. Re-running the failed job

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.23301% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...oxy/_experimental/mcp_server/mcp_server_manager.py 90.24% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit df75d29. Configure here.

@tin-berri
tin-berri enabled auto-merge July 23, 2026 00:34
@tin-berri
tin-berri merged commit 712136d into litellm_internal_staging Jul 23, 2026
79 checks passed
@tin-berri
tin-berri deleted the litellm_lit4658_oauth_discovery_logging branch July 23, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants