LibreChat/client/src/components/MCP/ServerInitializationSection.tsx
Atef Bellaaj 1b0b27b30c
🧩 refactor: Decouple MCP Config from Startup Config (#10689)
* Decouple mcp config from start up config

* Chore: Work on AI Review and Copilot Comments

- setRawConfig is not needed since the private raw config is not needed any more
- !!serversLoading bug fixed
- added unit tests for route /api/mcp/servers
- copilot comments addressed

* chore: remove comments

* chore: rename data-provider dir for MCP

* chore: reorganize mcp specific query hooks

* fix: consolidate imports for MCP server manager

* chore: add dev-staging branch to frontend review workflow triggers

* feat: add GitHub Actions workflow for building and pushing Docker images to GitHub Container Registry and Docker Hub

* fix: update label for tag input in BookmarkForm tests to improve clarity

---------

Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-04 14:34:46 -05:00

110 lines
3.1 KiB
TypeScript

import React from 'react';
import { RefreshCw } from 'lucide-react';
import { Button, Spinner } from '@librechat/client';
import { useLocalize, useMCPServerManager, useMCPConnectionStatus } from '~/hooks';
import { useGetStartupConfig } from '~/data-provider';
interface ServerInitializationSectionProps {
sidePanel?: boolean;
serverName: string;
requiresOAuth: boolean;
hasCustomUserVars?: boolean;
conversationId?: string | null;
}
export default function ServerInitializationSection({
serverName,
requiresOAuth,
conversationId,
sidePanel = false,
hasCustomUserVars = false,
}: ServerInitializationSectionProps) {
const localize = useLocalize();
const {
initializeServer,
availableMCPServers,
cancelOAuthFlow,
isInitializing,
isCancellable,
getOAuthUrl,
} = useMCPServerManager({ conversationId });
const { connectionStatus } = useMCPConnectionStatus({
enabled: !!availableMCPServers && availableMCPServers.length > 0,
});
const serverStatus = connectionStatus?.[serverName];
const isConnected = serverStatus?.connectionState === 'connected';
const canCancel = isCancellable(serverName);
const isServerInitializing = isInitializing(serverName);
const serverOAuthUrl = getOAuthUrl(serverName);
const shouldShowReinit = isConnected && (requiresOAuth || hasCustomUserVars);
const shouldShowInit = !isConnected && !serverOAuthUrl;
if (!shouldShowReinit && !shouldShowInit && !serverOAuthUrl) {
return null;
}
if (serverOAuthUrl) {
return (
<>
<div className="flex items-center gap-2">
<Button
onClick={() => cancelOAuthFlow(serverName)}
disabled={!canCancel}
variant="outline"
title={!canCancel ? 'disabled' : undefined}
>
{localize('com_ui_cancel')}
</Button>
<Button
variant="submit"
onClick={() => window.open(serverOAuthUrl, '_blank', 'noopener,noreferrer')}
className="flex-1"
>
{localize('com_ui_continue_oauth')}
</Button>
</div>
</>
);
}
// Unified button rendering
const isReinit = shouldShowReinit;
const outerClass = isReinit ? 'flex justify-start' : 'flex justify-end';
const buttonVariant = isReinit ? undefined : 'default';
let buttonText = '';
if (isServerInitializing) {
buttonText = localize('com_ui_loading');
} else if (isReinit) {
buttonText = localize('com_ui_reinitialize');
} else if (requiresOAuth) {
buttonText = localize('com_ui_authenticate');
} else {
buttonText = localize('com_ui_mcp_initialize');
}
const icon = isServerInitializing ? (
<Spinner className="h-4 w-4" />
) : (
<RefreshCw className="h-4 w-4" />
);
return (
<div className={outerClass}>
<Button
variant={buttonVariant}
onClick={() => initializeServer(serverName, false)}
disabled={isServerInitializing}
size={sidePanel ? 'sm' : 'default'}
className="w-full"
>
{icon}
{buttonText}
</Button>
</div>
);
}