diff --git a/client/src/components/SidePanel/MCPBuilder/MCPCardActions.tsx b/client/src/components/SidePanel/MCPBuilder/MCPCardActions.tsx
index 015dfce014..7185185132 100644
--- a/client/src/components/SidePanel/MCPBuilder/MCPCardActions.tsx
+++ b/client/src/components/SidePanel/MCPBuilder/MCPCardActions.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Pencil, PlugZap, SlidersHorizontal, RefreshCw, X } from 'lucide-react';
+import { Pencil, PlugZap, SlidersHorizontal, RefreshCw, X, Trash2 } from 'lucide-react';
import { Spinner, TooltipAnchor } from '@librechat/client';
import type { MCPServerStatus } from 'librechat-data-provider';
import { useLocalize } from '~/hooks';
@@ -17,6 +17,7 @@ interface MCPCardActionsProps {
onConfigClick: (e: React.MouseEvent) => void;
onInitialize: () => void;
onCancel: (e: React.MouseEvent) => void;
+ onRevoke?: () => void;
}
/**
@@ -26,6 +27,7 @@ interface MCPCardActionsProps {
* - Pencil: Edit server definition (Settings panel only)
* - PlugZap: Connect/Authenticate (for disconnected/error servers)
* - SlidersHorizontal: Configure custom variables (for connected servers with vars)
+ * - Trash2: Revoke OAuth access (for connected OAuth servers)
* - RefreshCw: Reconnect/Refresh (for connected servers)
* - Spinner: Loading state (with X on hover for cancel)
*/
@@ -41,6 +43,7 @@ export default function MCPCardActions({
onConfigClick,
onInitialize,
onCancel,
+ onRevoke,
}: MCPCardActionsProps) {
const localize = useLocalize();
@@ -162,6 +165,20 @@ export default function MCPCardActions({
)}
+
+ {/* Revoke button - for OAuth servers (available regardless of connection state) */}
+ {serverStatus?.requiresOAuth && onRevoke && (
+
+
+
+ )}
);
}
diff --git a/client/src/components/SidePanel/MCPBuilder/MCPServerCard.tsx b/client/src/components/SidePanel/MCPBuilder/MCPServerCard.tsx
index 34f98b34d3..1e538724fb 100644
--- a/client/src/components/SidePanel/MCPBuilder/MCPServerCard.tsx
+++ b/client/src/components/SidePanel/MCPBuilder/MCPServerCard.tsx
@@ -30,7 +30,7 @@ export default function MCPServerCard({
}: MCPServerCardProps) {
const localize = useLocalize();
const triggerRef = useRef(null);
- const { initializeServer } = useMCPServerManager();
+ const { initializeServer, revokeOAuthForServer } = useMCPServerManager();
const [dialogOpen, setDialogOpen] = useState(false);
const statusIconProps = getServerStatusIconProps(server.serverName);
@@ -50,9 +50,20 @@ export default function MCPServerCard({
const canEdit = canCreateEditMCPs && canEditThisServer;
const handleInitialize = () => {
+ /** If server has custom user vars and is not already connected, show config dialog first
+ * This ensures users can enter credentials before initialization attempts
+ */
+ if (hasCustomUserVars && serverStatus?.connectionState !== 'connected') {
+ onConfigClick({ stopPropagation: () => {}, preventDefault: () => {} } as React.MouseEvent);
+ return;
+ }
initializeServer(server.serverName);
};
+ const handleRevoke = () => {
+ revokeOAuthForServer(server.serverName);
+ };
+
const handleEditClick = (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
@@ -130,6 +141,7 @@ export default function MCPServerCard({
onConfigClick={onConfigClick}
onInitialize={handleInitialize}
onCancel={onCancel}
+ onRevoke={handleRevoke}
/>
diff --git a/client/src/hooks/MCP/useMCPServerManager.ts b/client/src/hooks/MCP/useMCPServerManager.ts
index d3ff4cbb70..bb5214be7c 100644
--- a/client/src/hooks/MCP/useMCPServerManager.ts
+++ b/client/src/hooks/MCP/useMCPServerManager.ts
@@ -94,8 +94,20 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
const cancelOAuthMutation = useCancelMCPOAuthMutation();
const updateUserPluginsMutation = useUpdateUserPluginsMutation({
- onSuccess: async () => {
- showToast({ message: localize('com_nav_mcp_vars_updated'), status: 'success' });
+ onSuccess: async (_data, variables) => {
+ const isRevoke = variables.action === 'uninstall';
+ const message = isRevoke
+ ? localize('com_nav_mcp_access_revoked')
+ : localize('com_nav_mcp_vars_updated');
+ showToast({ message, status: 'success' });
+
+ /** Deselect server from mcpValues when revoking access */
+ if (isRevoke && variables.pluginKey?.startsWith(Constants.mcp_prefix)) {
+ const serverName = variables.pluginKey.replace(Constants.mcp_prefix, '');
+ const currentValues = mcpValuesRef.current ?? [];
+ const filteredValues = currentValues.filter((name) => name !== serverName);
+ setMCPValues(filteredValues);
+ }
await Promise.all([
queryClient.invalidateQueries([QueryKeys.mcpServers]),
@@ -491,13 +503,10 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
auth: {},
};
updateUserPluginsMutation.mutate(payload);
-
- const currentValues = mcpValues ?? [];
- const filteredValues = currentValues.filter((name) => name !== targetName);
- setMCPValues(filteredValues);
+ /** Deselection is now handled centrally in updateUserPluginsMutation.onSuccess */
}
},
- [selectedToolForConfig, updateUserPluginsMutation, mcpValues, setMCPValues],
+ [selectedToolForConfig, updateUserPluginsMutation],
);
/** Standalone revoke function for OAuth servers - doesn't require selectedToolForConfig */
diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json
index aba6ea9fb0..cd1f5991f5 100644
--- a/client/src/locales/en/translation.json
+++ b/client/src/locales/en/translation.json
@@ -546,6 +546,7 @@
"com_nav_mcp_status_unknown": "Unknown",
"com_nav_mcp_vars_update_error": "Error updating MCP custom user variables",
"com_nav_mcp_vars_updated": "MCP custom user variables updated successfully.",
+ "com_nav_mcp_access_revoked": "MCP server access revoked successfully.",
"com_nav_modular_chat": "Enable switching Endpoints mid-conversation",
"com_nav_my_files": "My Files",
"com_nav_not_supported": "Not Supported",