🏹 feat: Concurrent MCP Initialization Support (#8677)

*  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
This commit is contained in:
Dustin Healy 2025-07-28 09:25:34 -07:00 committed by GitHub
parent 37aba18a96
commit 0ef3fefaec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1092 additions and 542 deletions

View file

@ -1,7 +1,8 @@
import React, { useState, useCallback } from 'react';
import React, { useCallback } from 'react';
import { Button } from '@librechat/client';
import { RefreshCw, Link } from 'lucide-react';
import { useLocalize, useMCPServerInitialization } from '~/hooks';
import { useMCPServerManager } from '~/hooks/MCP/useMCPServerManager';
import { useLocalize } from '~/hooks';
interface ServerInitializationSectionProps {
serverName: string;
@ -14,32 +15,27 @@ export default function ServerInitializationSection({
}: ServerInitializationSectionProps) {
const localize = useLocalize();
const [oauthUrl, setOauthUrl] = useState<string | null>(null);
// Use the shared initialization hook
const { initializeServer, isLoading, connectionStatus, cancelOAuthFlow, isCancellable } =
useMCPServerInitialization({
onOAuthStarted: (name, url) => {
// Store the OAuth URL locally for display
setOauthUrl(url);
},
onSuccess: () => {
// Clear OAuth URL on success
setOauthUrl(null);
},
});
// 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(() => {
setOauthUrl(null);
initializeServer(serverName);
}, [initializeServer, serverName]);
const handleCancelClick = useCallback(() => {
setOauthUrl(null);
cancelOAuthFlow(serverName);
}, [cancelOAuthFlow, serverName]);
@ -49,11 +45,11 @@ export default function ServerInitializationSection({
<div className="flex justify-start">
<button
onClick={handleInitializeClick}
disabled={isLoading}
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 ${isLoading ? 'animate-spin' : ''}`} />
{isLoading ? localize('com_ui_loading') : localize('com_ui_reinitialize')}
<RefreshCw className={`h-3 w-3 ${isServerInitializing ? 'animate-spin' : ''}`} />
{isServerInitializing ? localize('com_ui_loading') : localize('com_ui_reinitialize')}
</button>
</div>
);
@ -70,13 +66,13 @@ export default function ServerInitializationSection({
</span>
</div>
{/* Only show authenticate button when OAuth URL is not present */}
{!oauthUrl && (
{!serverOAuthUrl && (
<Button
onClick={handleInitializeClick}
disabled={isLoading}
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"
>
{isLoading ? (
{isServerInitializing ? (
<>
<RefreshCw className="h-4 w-4 animate-spin" />
{localize('com_ui_loading')}
@ -94,7 +90,7 @@ export default function ServerInitializationSection({
</div>
{/* OAuth URL display */}
{oauthUrl && (
{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">
@ -106,7 +102,7 @@ export default function ServerInitializationSection({
</div>
<div className="flex items-center gap-2">
<Button
onClick={() => window.open(oauthUrl, '_blank', 'noopener,noreferrer')}
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')}