feat: Add MCP Reinitialization to MCPPanel (#8418)

*  feat: Add MCP Reinitialization to MCPPanel

- Refactored tool caching to include user-specific tools in various service files.
- Refactored MCPManager class for clarity
- Added a new endpoint for reinitializing MCP servers, allowing for dynamic updates of server configurations.
- Enhanced the MCPPanel component to support server reinitialization with user feedback.

* 🔃 refactor: Simplify Plugin Deduplication and Clear Cache Post-MCP Initialization

- Replaced manual deduplication of tools with the dedicated `filterUniquePlugins` function for improved readability.
- Added back cache clearing for tools after MCP initialization to ensure fresh data is used.
- Removed unused exports from `PluginController.js` to clean up the codebase.
This commit is contained in:
Dustin Healy 2025-07-21 14:49:19 -07:00 committed by GitHub
parent 14660d75ae
commit faaba30af1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 499 additions and 193 deletions

View file

@ -1,8 +1,11 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { ChevronLeft } from 'lucide-react';
import { Constants } from 'librechat-data-provider';
import { ChevronLeft, RefreshCw } from 'lucide-react';
import { useForm, Controller } from 'react-hook-form';
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import {
useUpdateUserPluginsMutation,
useReinitializeMCPServerMutation,
} from 'librechat-data-provider/react-query';
import type { TUpdateUserPlugins } from 'librechat-data-provider';
import { Button, Input, Label } from '~/components/ui';
import { useGetStartupConfig } from '~/data-provider';
@ -24,6 +27,8 @@ export default function MCPPanel() {
const [selectedServerNameForEditing, setSelectedServerNameForEditing] = useState<string | null>(
null,
);
const [rotatingServers, setRotatingServers] = useState<Set<string>>(new Set());
const reinitializeMCPMutation = useReinitializeMCPServerMutation();
const mcpServerDefinitions = useMemo(() => {
if (!startupConfig?.mcpServers) {
@ -89,6 +94,32 @@ export default function MCPPanel() {
setSelectedServerNameForEditing(null);
};
const handleReinitializeServer = useCallback(
async (serverName: string) => {
setRotatingServers((prev) => new Set(prev).add(serverName));
try {
await reinitializeMCPMutation.mutateAsync(serverName);
showToast({
message: `MCP server '${serverName}' reinitialized successfully`,
status: 'success',
});
} catch (error) {
console.error('Error reinitializing MCP server:', error);
showToast({
message: 'Failed to reinitialize MCP server',
status: 'error',
});
} finally {
setRotatingServers((prev) => {
const next = new Set(prev);
next.delete(serverName);
return next;
});
}
},
[showToast, reinitializeMCPMutation],
);
if (startupConfigLoading) {
return <MCPPanelSkeleton />;
}
@ -144,14 +175,27 @@ export default function MCPPanel() {
<div className="h-auto max-w-full overflow-x-hidden p-3">
<div className="space-y-2">
{mcpServerDefinitions.map((server) => (
<Button
key={server.serverName}
variant="outline"
className="w-full justify-start dark:hover:bg-gray-700"
onClick={() => handleServerClickToEdit(server.serverName)}
>
{server.serverName}
</Button>
<div key={server.serverName} className="flex items-center gap-2">
<Button
variant="outline"
className="flex-1 justify-start dark:hover:bg-gray-700"
onClick={() => handleServerClickToEdit(server.serverName)}
>
{server.serverName}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleReinitializeServer(server.serverName)}
className="px-2 py-1"
title="Reinitialize MCP server"
disabled={reinitializeMCPMutation.isLoading}
>
<RefreshCw
className={`h-4 w-4 ${rotatingServers.has(server.serverName) ? 'animate-spin' : ''}`}
/>
</Button>
</div>
))}
</div>
</div>