mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
* ✨ feat: Enhance MCP Connection Status Management - Introduced new functions to retrieve and manage connection status for multiple MCP servers, including OAuth flow checks and server-specific status retrieval. - Refactored the MCP connection status endpoints to support both all servers and individual server queries. - Replaced the old server initialization hook with a new `useMCPServerManager` hook for improved state management and handling of multiple OAuth flows. - Updated the MCPPanel component to utilize the new context provider for better state handling and UI updates. - Fixed a number of UI bugs when initializing servers * 🗣️ i18n: Remove unused strings from translation.json * refactor: move helper functions out of the route module into mcp service file * ci: add tests for newly added functions in mcp service file * fix: memoize setMCPValues to avoid render loop
126 lines
4.7 KiB
TypeScript
126 lines
4.7 KiB
TypeScript
import React, { useCallback } from 'react';
|
|
import { Button } from '@librechat/client';
|
|
import { RefreshCw, Link } from 'lucide-react';
|
|
import { useMCPServerManager } from '~/hooks/MCP/useMCPServerManager';
|
|
import { useLocalize } from '~/hooks';
|
|
|
|
interface ServerInitializationSectionProps {
|
|
serverName: string;
|
|
requiresOAuth: boolean;
|
|
}
|
|
|
|
export default function ServerInitializationSection({
|
|
serverName,
|
|
requiresOAuth,
|
|
}: ServerInitializationSectionProps) {
|
|
const localize = useLocalize();
|
|
|
|
// Use the centralized server manager instead of the old initialization hook so we can handle multiple oauth flows at once
|
|
const {
|
|
initializeServer,
|
|
connectionStatus,
|
|
cancelOAuthFlow,
|
|
isInitializing,
|
|
isCancellable,
|
|
getOAuthUrl,
|
|
} = useMCPServerManager();
|
|
|
|
const serverStatus = connectionStatus[serverName];
|
|
const isConnected = serverStatus?.connectionState === 'connected';
|
|
const canCancel = isCancellable(serverName);
|
|
const isServerInitializing = isInitializing(serverName);
|
|
const serverOAuthUrl = getOAuthUrl(serverName);
|
|
|
|
const handleInitializeClick = useCallback(() => {
|
|
initializeServer(serverName);
|
|
}, [initializeServer, serverName]);
|
|
|
|
const handleCancelClick = useCallback(() => {
|
|
cancelOAuthFlow(serverName);
|
|
}, [cancelOAuthFlow, serverName]);
|
|
|
|
// Show subtle reinitialize option if connected
|
|
if (isConnected) {
|
|
return (
|
|
<div className="flex justify-start">
|
|
<button
|
|
onClick={handleInitializeClick}
|
|
disabled={isServerInitializing}
|
|
className="flex items-center gap-1 text-xs text-gray-400 hover:text-gray-600 disabled:opacity-50 dark:text-gray-500 dark:hover:text-gray-400"
|
|
>
|
|
<RefreshCw className={`h-3 w-3 ${isServerInitializing ? 'animate-spin' : ''}`} />
|
|
{isServerInitializing ? localize('com_ui_loading') : localize('com_ui_reinitialize')}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-lg border border-[#991b1b] bg-[#2C1315] p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm font-medium text-red-700 dark:text-red-300">
|
|
{requiresOAuth
|
|
? localize('com_ui_mcp_not_authenticated', { 0: serverName })
|
|
: localize('com_ui_mcp_not_initialized', { 0: serverName })}
|
|
</span>
|
|
</div>
|
|
{/* Only show authenticate button when OAuth URL is not present */}
|
|
{!serverOAuthUrl && (
|
|
<Button
|
|
onClick={handleInitializeClick}
|
|
disabled={isServerInitializing}
|
|
className="flex items-center gap-2 bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:hover:bg-blue-800"
|
|
>
|
|
{isServerInitializing ? (
|
|
<>
|
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
|
{localize('com_ui_loading')}
|
|
</>
|
|
) : (
|
|
<>
|
|
<RefreshCw className="h-4 w-4" />
|
|
{requiresOAuth
|
|
? localize('com_ui_authenticate')
|
|
: localize('com_ui_mcp_initialize')}
|
|
</>
|
|
)}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{/* OAuth URL display */}
|
|
{serverOAuthUrl && (
|
|
<div className="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-700 dark:bg-blue-900/20">
|
|
<div className="mb-2 flex items-center gap-2">
|
|
<div className="flex h-4 w-4 items-center justify-center rounded-full bg-blue-500">
|
|
<Link className="h-2.5 w-2.5 text-white" />
|
|
</div>
|
|
<span className="text-sm font-medium text-blue-700 dark:text-blue-300">
|
|
{localize('com_ui_auth_url')}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
onClick={() => window.open(serverOAuthUrl, '_blank', 'noopener,noreferrer')}
|
|
className="flex-1 bg-blue-600 text-white hover:bg-blue-700 dark:hover:bg-blue-800"
|
|
>
|
|
{localize('com_ui_continue_oauth')}
|
|
</Button>
|
|
<Button
|
|
onClick={handleCancelClick}
|
|
disabled={!canCancel}
|
|
className="bg-gray-200 text-gray-700 hover:bg-gray-300 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
|
|
title={!canCancel ? 'disabled' : undefined}
|
|
>
|
|
{localize('com_ui_cancel')}
|
|
</Button>
|
|
</div>
|
|
<p className="mt-2 text-xs text-blue-600 dark:text-blue-400">
|
|
{localize('com_ui_oauth_flow_desc')}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|