mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-13 05:58:51 +01:00
* fix: Revoke OAuth and Vars. Config Regressions in MCP Panel - Introduced a new Trash2 icon button in MCPCardActions for revoking OAuth access on connected OAuth servers. - Updated MCPServerCard to handle the revoke action, allowing users to revoke OAuth for specific servers. - Enhanced user experience by ensuring the revoke option is available regardless of the server's connection state. * refactor: Reorganize Revoke Button Logic in MCPCardActions and Update Toast Messages - Moved the Revoke button for OAuth servers to a new position in MCPCardActions for improved visibility. - Updated the success message logic in useMCPServerManager to differentiate between uninstall and variable update actions, enhancing user feedback. * i18n: Add new translation for MCP server access revocation message * refactor: Centralize Deselection Logic in updateUserPluginsMutation - Updated the success handler in useUpdateUserPluginsMutation to manage deselection of MCP server values when revoking access, improving code clarity and reducing redundancy. - Simplified message assignment logic for user feedback during plugin updates.
160 lines
5.6 KiB
TypeScript
160 lines
5.6 KiB
TypeScript
import { useState, useRef } from 'react';
|
|
import { MCPIcon } from '@librechat/client';
|
|
import { PermissionBits, hasPermissions } from 'librechat-data-provider';
|
|
import type { MCPServerStatusIconProps } from '~/components/MCP/MCPServerStatusIcon';
|
|
import type { MCPServerDefinition } from '~/hooks';
|
|
import MCPServerDialog from './MCPServerDialog';
|
|
import { getStatusDotColor } from './MCPStatusBadge';
|
|
import MCPCardActions from './MCPCardActions';
|
|
import { useMCPServerManager, useLocalize } from '~/hooks';
|
|
import { cn } from '~/utils';
|
|
|
|
interface MCPServerCardProps {
|
|
server: MCPServerDefinition;
|
|
getServerStatusIconProps: (serverName: string) => MCPServerStatusIconProps;
|
|
canCreateEditMCPs: boolean;
|
|
}
|
|
|
|
/**
|
|
* Compact card component for displaying an MCP server with status and actions.
|
|
*
|
|
* Visual design:
|
|
* - Status shown via colored dot on icon (no separate badge - avoids redundancy)
|
|
* - Action buttons clearly indicate available operations
|
|
* - Consistent with MCPServerMenuItem in chat dropdown
|
|
*/
|
|
export default function MCPServerCard({
|
|
server,
|
|
getServerStatusIconProps,
|
|
canCreateEditMCPs,
|
|
}: MCPServerCardProps) {
|
|
const localize = useLocalize();
|
|
const triggerRef = useRef<HTMLDivElement>(null);
|
|
const { initializeServer, revokeOAuthForServer } = useMCPServerManager();
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
|
|
const statusIconProps = getServerStatusIconProps(server.serverName);
|
|
const {
|
|
serverStatus,
|
|
onConfigClick,
|
|
isInitializing,
|
|
canCancel,
|
|
onCancel,
|
|
hasCustomUserVars = false,
|
|
} = statusIconProps;
|
|
|
|
const canEditThisServer = hasPermissions(server.effectivePermissions, PermissionBits.EDIT);
|
|
const displayName = server.config?.title || server.serverName;
|
|
const description = server.config?.description;
|
|
const statusDotColor = getStatusDotColor(serverStatus, isInitializing);
|
|
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();
|
|
setDialogOpen(true);
|
|
};
|
|
|
|
// Determine status text for accessibility
|
|
const getStatusText = () => {
|
|
if (isInitializing) return localize('com_nav_mcp_status_initializing');
|
|
if (!serverStatus) return localize('com_nav_mcp_status_unknown');
|
|
const { connectionState, requiresOAuth } = serverStatus;
|
|
if (connectionState === 'connected') return localize('com_nav_mcp_status_connected');
|
|
if (connectionState === 'connecting') return localize('com_nav_mcp_status_connecting');
|
|
if (connectionState === 'error') return localize('com_nav_mcp_status_error');
|
|
if (connectionState === 'disconnected') {
|
|
return requiresOAuth
|
|
? localize('com_nav_mcp_status_needs_auth')
|
|
: localize('com_nav_mcp_status_disconnected');
|
|
}
|
|
return localize('com_nav_mcp_status_unknown');
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className={cn(
|
|
'group flex items-center gap-3 rounded-lg px-3 py-2.5',
|
|
'border border-border-light bg-transparent',
|
|
)}
|
|
aria-label={`${displayName} - ${getStatusText()}`}
|
|
>
|
|
{/* Server Icon with Status Dot */}
|
|
<div className="relative flex-shrink-0">
|
|
{server.config?.iconPath ? (
|
|
<img
|
|
src={server.config.iconPath}
|
|
className="size-8 rounded-lg object-cover"
|
|
alt=""
|
|
aria-hidden="true"
|
|
/>
|
|
) : (
|
|
<div className="flex size-8 items-center justify-center rounded-lg bg-surface-tertiary">
|
|
<MCPIcon className="size-5 text-text-secondary" aria-hidden="true" />
|
|
</div>
|
|
)}
|
|
{/* Status dot - color indicates connection state */}
|
|
<div
|
|
className={cn(
|
|
'absolute -bottom-0.5 -right-0.5 size-3 rounded-full',
|
|
'border-2 border-surface-primary',
|
|
statusDotColor,
|
|
(isInitializing || serverStatus?.connectionState === 'connecting') && 'animate-pulse',
|
|
)}
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
|
|
{/* Server Info */}
|
|
<div className="min-w-0 flex-1">
|
|
<span className="truncate text-sm font-medium text-text-primary">{displayName}</span>
|
|
{description && <p className="truncate text-xs text-text-secondary">{description}</p>}
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex-shrink-0">
|
|
<MCPCardActions
|
|
serverName={server.serverName}
|
|
serverStatus={serverStatus}
|
|
isInitializing={isInitializing}
|
|
canCancel={canCancel}
|
|
hasCustomUserVars={hasCustomUserVars}
|
|
canEdit={canEdit}
|
|
editButtonRef={triggerRef}
|
|
onEditClick={handleEditClick}
|
|
onConfigClick={onConfigClick}
|
|
onInitialize={handleInitialize}
|
|
onCancel={onCancel}
|
|
onRevoke={handleRevoke}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Edit Dialog - separate from card */}
|
|
{canEdit && (
|
|
<MCPServerDialog
|
|
open={dialogOpen}
|
|
onOpenChange={setDialogOpen}
|
|
triggerRef={triggerRef}
|
|
server={server}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|