diff --git a/client/src/components/SidePanel/MCPBuilder/MCPServerDialog/sections/AuthSection.tsx b/client/src/components/SidePanel/MCPBuilder/MCPServerDialog/sections/AuthSection.tsx
index fb31c95949..216a68151e 100644
--- a/client/src/components/SidePanel/MCPBuilder/MCPServerDialog/sections/AuthSection.tsx
+++ b/client/src/components/SidePanel/MCPBuilder/MCPServerDialog/sections/AuthSection.tsx
@@ -3,9 +3,10 @@ import { Copy, CopyCheck } from 'lucide-react';
import { useFormContext, useWatch } from 'react-hook-form';
import { Permissions, PermissionTypes } from 'librechat-data-provider';
import { Label, Input, Checkbox, SecretInput, Radio, useToastContext } from '@librechat/client';
-import { AuthTypeEnum, AuthorizationTypeEnum } from '../hooks/useMCPServerForm';
import type { MCPServerFormData } from '../hooks/useMCPServerForm';
+import { AuthTypeEnum, AuthorizationTypeEnum } from '../hooks/useMCPServerForm';
import { useLocalize, useCopyToClipboard, useHasAccess } from '~/hooks';
+import { Collapse } from '~/components/ui';
import { cn } from '~/utils';
interface AuthSectionProps {
@@ -83,7 +84,7 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
);
return (
-
+
{/* Auth Type Radio */}
@@ -102,78 +103,82 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
{/* API Key Fields */}
- {authType === AuthTypeEnum.ServiceHttp && (
+
- {/* User provides own key checkbox */}
-
-
- setValue('auth.api_key_source', checked ? 'user' : 'admin')
- }
- aria-labelledby="user_provides_key_label"
- />
-
- {localize('com_ui_user_provides_key')}
-
+ {/* User provides own key checkbox + admin-provided key */}
+
+
+
+ setValue('auth.api_key_source', checked ? 'user' : 'admin')
+ }
+ aria-labelledby="user_provides_key_label"
+ />
+
+ {localize('com_ui_user_provides_key')}
+
+
+
+ {/* API Key input - only when admin provides */}
+
+
+
+ {localize('com_ui_api_key')}
+
+
+
+
- {/* API Key input - only when admin provides */}
- {apiKeySource !== 'user' && (
-
-
- {localize('com_ui_api_key')}
-
-
+
+
+
+
+
+ setValue('auth.api_key_authorization_type', val as AuthorizationTypeEnum)
+ }
+ fullWidth
+ aria-labelledby="header-format-label"
/>
-
- )}
+
- {/* Header Format Radio */}
-
-
-
-
-
- setValue('auth.api_key_authorization_type', val as AuthorizationTypeEnum)
- }
- fullWidth
- aria-labelledby="header-format-label"
- />
-
-
- {/* Custom header name */}
- {authorizationType === AuthorizationTypeEnum.Custom && (
-
-
- {localize('com_ui_custom_header_name')}
-
-
-
- )}
+ {/* Custom header name */}
+
+
+
+ {localize('com_ui_custom_header_name')}
+
+
+
+
+
- )}
+
{/* OAuth Fields */}
- {authType === AuthTypeEnum.OAuth && (
+
{/* Client ID & Secret in a grid */}
@@ -200,7 +205,9 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
aria-describedby={
errors.auth?.oauth_client_id ? 'oauth-client-id-error' : undefined
}
- {...register('auth.oauth_client_id', { required: !isEditMode })}
+ {...register('auth.oauth_client_id', {
+ required: !isEditMode && authType === AuthTypeEnum.OAuth,
+ })}
className={cn(errors.auth?.oauth_client_id && 'border-border-destructive')}
/>
{errors.auth?.oauth_client_id && (
@@ -288,10 +295,10 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
)}
- )}
+
{/* OBO Fields */}
- {authType === AuthTypeEnum.OBO && (
+
@@ -330,7 +337,7 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
)}
- )}
+
);
}
diff --git a/client/src/components/ui/Collapse.tsx b/client/src/components/ui/Collapse.tsx
index bffa300389..9ddcb69155 100644
--- a/client/src/components/ui/Collapse.tsx
+++ b/client/src/components/ui/Collapse.tsx
@@ -12,12 +12,14 @@ interface CollapseProps {
* the OAuth QR. The parent's content-driven height follows the tween in a single
* motion, so stacked collapses (e.g. a loading skeleton swapping to a list) can
* cross-fade smoothly without a measuring wrapper fighting nested reveals.
- * Content fades to soften the swap and is hidden from assistive tech while closed.
+ * Content fades to soften the swap; while closed it is `inert` (removed from tab
+ * order and the a11y tree) so collapsed form fields can't be focused or read.
*/
export default function Collapse({ open, children, className }: CollapseProps) {
return (