diff --git a/ui/litellm-dashboard/src/components/mcp_tools/PassthroughAuthorizeSection.tsx b/ui/litellm-dashboard/src/components/mcp_tools/PassthroughAuthorizeSection.tsx index 314b45fff6c..8d53e8b86ee 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/PassthroughAuthorizeSection.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/PassthroughAuthorizeSection.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Button, Form, Input } from "antd"; +import { Button, Checkbox, Form, Input } from "antd"; import { isClientForwardedTokenMode } from "./types"; interface PassthroughOAuthFlow { @@ -19,13 +19,26 @@ interface PassthroughOAuthFlow { * pre-registered Slack app); unlike the token they ARE saved onto the server * as declared config, so internal users' Authorize relays through the org's * app instead of dead-ending on upstreams that cannot mint clients. + * + * Blank fields follow the same convention as the M2M credential fields: on + * create they mean "no app configured" (dynamic client registration), while on + * edit the backend's partial update keeps whatever app is already stored, so + * blanks mean "keep existing". Removing a stored app is therefore an explicit + * action (the checkbox below, edit only), which saves an explicit-null + * credential write instead of omitting the field. */ export default function PassthroughAuthorizeSection({ authType, oauthFlow, + isEditing = false, + removeStoredApp = false, + onRemoveStoredAppChange, }: { authType?: string | null; oauthFlow: PassthroughOAuthFlow; + isEditing?: boolean; + removeStoredApp?: boolean; + onRemoveStoredAppChange?: (remove: boolean) => void; }) { if (!isClientForwardedTokenMode(authType)) return null; const authorizeButtonLabels: Record = { @@ -33,6 +46,9 @@ export default function PassthroughAuthorizeSection({ exchanging: "Exchanging authorization code...", }; const authorizeButtonLabel = authorizeButtonLabels[oauthFlow.status] ?? "Authorize & Fetch Tools (browser-only)"; + const blankMeaning = isEditing + ? "Leave blank to keep the currently saved app (if any)" + : "Leave blank to use dynamic client registration"; return (

@@ -47,7 +63,8 @@ export default function PassthroughAuthorizeSection({ extra="Set this to make everyone authorize through a specific app; required for upstreams without dynamic client registration (e.g. a pre-registered Slack app)." > @@ -57,9 +74,17 @@ export default function PassthroughAuthorizeSection({ > + {isEditing && onRemoveStoredAppChange && ( + onRemoveStoredAppChange(e.target.checked)}> + + Remove the saved OAuth app on save (the server goes back to dynamic client registration) + + + )}