Skip to content

fix(mcp): warn on upstream change when the stored OAuth app is redacted from the edit form - #32922

Open
tin-berri wants to merge 1 commit into
litellm_internal_stagingfrom
litellm_mcp_stored_app_upstream_warning
Open

fix(mcp): warn on upstream change when the stored OAuth app is redacted from the edit form#32922
tin-berri wants to merge 1 commit into
litellm_internal_stagingfrom
litellm_mcp_stored_app_upstream_warning

Conversation

@tin-berri

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

Copy link
Copy Markdown
Contributor

Relevant issues

Follow-up to #32752, which noted this gap: the "app may not match upstream" warning fires on a URL change only when a client is sitting in the form, so it works on create (the admin just typed the app) but stays silent on edit for a stored app, because the GET redacts credentials to null and blank fields mean keep-existing. An admin could repoint a client-forwarded server at a different upstream and silently keep an OAuth app registered for the old one. The form cannot fix this alone since it never sees the stored client; it needs a non-secret "a client exists" bit from the backend, which is a response-shape change and therefore its own PR

Linear ticket

Resolves LIT-4682

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)

Screenshots / Proof of Fix

UI flow to capture (before = 0c23c40, the #32752 merge on staging; after = 559fa91):

  1. python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver and npm run dev in ui/litellm-dashboard (port 3000)
  2. MCP Servers page, Add New Server: transport HTTP, any URL, auth type True Passthrough, fill OAuth Client ID and Client Secret (any values), save
  3. Open the server, Edit Server: the client fields are blank with the "Leave blank to keep the currently saved app (if any)" placeholder, because the GET redacted the stored app
  4. Change the Server URL to a different host
  5. Before: nothing appears. After: the amber "registered for the previous upstream" warning appears above the client fields
  6. Check "Remove the saved OAuth app on save": the warning hides. Uncheck it: the warning returns

Backend flag, observable end to end on the same server:

curl -s http://localhost:4000/v1/mcp/server -H "Authorization: Bearer sk-1234" \
  | jq '.[] | {server_id, credentials, has_configured_client}'

returns credentials: null with has_configured_client: true for the server above, and false for servers without a stored client. Non-admin and virtual-key views return has_configured_client: null

Type

🐛 Bug Fix

Changes

Backend: LiteLLM_MCPServerTable gains a response-only has_configured_client boolean. _redact_mcp_credentials stamps it from the credentials blob it is about to remove; _build_mcp_server_table stamps it from the registry's decrypted client_id, because the list path (which feeds the dashboard grid and the edit form) builds table objects that never carry the blob, and the redactor preserves that stamp rather than resetting it. The non-admin and virtual-key sanitizers null the flag back out since only the admin edit form needs it. Nothing writes the field to the DB; create and update payloads go through the request models, which do not have it

Frontend: the edit form's URL-change check now also fires when has_configured_client is true, gated on the credential class being unchanged (a cross-class auth switch replaces the stored app on save, so there is nothing kept to mismatch). The banner hides while the remove-app checkbox is checked, since removal writes an explicit-null credential, and reappears if it is unchecked. The warning copy now says "the OAuth app configured for this server" instead of "entered here", which was wrong for the stored case where the fields are blank

Tests: backend unit tests for the redactor stamp (blob present, absent, empty client_id, preserve-build-stamp), the sanitizer nulling, the table builder stamp, and an endpoint-level test through fetch_mcp_server; frontend tests for the redacted-stored-app warning firing on URL change, the remove-checkbox hide/restore, the cross-class no-warn case, and the banner suppression prop

…ed from the edit form

The edit form's "app may not match upstream" warning only fired when a client
was sitting in the form, so a stored app (redacted to null by the GET) never
triggered it: an admin could repoint a client-forwarded server at a different
upstream and silently keep an app registered for the old one.

The backend now stamps a non-secret has_configured_client boolean on redacted
responses (derived from the credentials blob at redaction time, or from the
registry's decrypted client_id on the list path, whose table objects never
carry the blob). The non-admin and virtual-key sanitizers null it back out;
only the admin edit form needs it. The edit form fires the warning from the
flag when the credential class is unchanged (a cross-class switch replaces the
stored app, so nothing kept can mismatch), and the banner hides while the
remove-app checkbox is checked since removal writes an explicit-null credential
@greptile-apps

greptile-apps Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a backend has_configured_client response-only boolean to LiteLLM_MCPServerTable so the admin edit form can detect that a stored OAuth app exists even though the credentials are redacted, and uses it to fire the "app may not match upstream" warning on URL changes for stored-but-blank-field apps.

  • Backend: _redact_mcp_credentials derives has_configured_client from the blob it is about to remove and preserves a truthy flag already stamped by the list-path builder; _build_mcp_server_table stamps it from bool(server.client_id); both sanitizers null it back out.
  • Frontend: the edit form adds a hasStoredKeptApp guard keyed on has_configured_client and a credential-class comparison so cross-class auth switches correctly suppress the warning; the banner also hides while the remove-app checkbox is checked.
  • Tests: backend unit tests cover stamping/preserve/sanitize paths and an end-to-end fetch_mcp_server test; frontend tests cover the stored-app warning, remove-checkbox hide/restore, and cross-class no-warn case.

Confidence Score: 4/5

Safe to merge; the backend change is additive (a new response-only field never written to the DB) and the frontend change adds a warning path without altering any save logic.

The implementation is well-structured and thoroughly tested across both the registry list path and the single-fetch DB path. The only issue found is a minor copy inconsistency in the warning banner on the create form.

PassthroughAuthorizeSection.tsx — the revised warning text references 'remove the app' which is only actionable on the edit form.

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/mcp_management_endpoints.py Stamps has_configured_client from the credentials blob before redacting; preserves a truthy flag from the build step for blob-free registry objects; correctly nulls it in both sanitizers.
ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx Adds hasStoredKeptApp check using has_configured_client and credential-class comparison, correctly gating the URL-change warning for stored-but-redacted OAuth apps.
ui/litellm-dashboard/src/components/mcp_tools/PassthroughAuthorizeSection.tsx Warning suppressed while removeStoredApp is checked; copy updated but references 'remove the app' even on the create form where no remove-app checkbox exists.
litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Stamps has_configured_client=bool(server.client_id) in _build_mcp_server_table for the registry list path.
tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py Thorough unit tests covering all stamping/preserve/sanitize paths and an end-to-end fetch_mcp_server test.
litellm/models/mcp_server.py Adds response-only has_configured_client: Optional[bool] field; never persisted, correctly defaulted to None.
ui/litellm-dashboard/src/components/mcp_tools/types.tsx Adds `has_configured_client?: boolean

Reviews (1): Last reviewed commit: "fix(mcp): warn on upstream change when t..." | Re-trigger Greptile

Comment on lines +80 to +82
You changed the upstream URL or endpoints; the OAuth app configured for this server was registered for the
previous upstream and may not be valid. Enter a client ID registered for the new upstream, or remove the app
to use dynamic client registration.

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.

P2 The revised warning text says "remove the app to use dynamic client registration", but on the create form PassthroughAuthorizeSection is rendered with isEditing=false, so the remove-app checkbox is never shown. A user on the create flow who changes the upstream URL after typing a client ID will see the warning but find no checkbox to act on the "remove" instruction — the only way to dismiss it is to clear the client ID field themselves.

Suggested change
You changed the upstream URL or endpoints; the OAuth app configured for this server was registered for the
previous upstream and may not be valid. Enter a client ID registered for the new upstream, or remove the app
to use dynamic client registration.
You changed the upstream URL or endpoints; the OAuth app configured for this server was registered for the
previous upstream and may not be valid. Enter a client ID registered for the new upstream
{isEditing ? ", or remove the app to use dynamic client registration" : ", or clear the client ID to use dynamic client registration"}.

@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_mcp_stored_app_upstream_warning (559fa91) with litellm_internal_staging (80c5217)1

Open in CodSpeed

Footnotes

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

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.

1 participant