From 5245aeea8f29a96c2767362226e2c2d3631e94f9 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 12 Sep 2025 21:37:07 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20refactor:=20Consolidate=20MCP=20?= =?UTF-8?q?tool=20removal=20and=20Improve=20UX=20(#9609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔧 refactor: Consolidate MCP tool removal and Improve UX - Removed redundant tool removal logic from MCPTool, UnconfiguredMCPTool, and UninitializedMCPTool components. - Introduced `useRemoveMCPTool` hook to handle tool removal and toast notifications. - Updated translation.json to include a reminder message for saving changes after tool removal. * chore: remove unused i18n key --- .../components/SidePanel/Agents/MCPTool.tsx | 37 +---------- .../SidePanel/Agents/UnconfiguredMCPTool.tsx | 51 ++-------------- .../SidePanel/Agents/UninitializedMCPTool.tsx | 56 ++--------------- client/src/hooks/MCP/index.ts | 1 + client/src/hooks/MCP/useRemoveMCPTool.ts | 61 +++++++++++++++++++ client/src/locales/en/translation.json | 2 +- 6 files changed, 75 insertions(+), 133 deletions(-) create mode 100644 client/src/hooks/MCP/useRemoveMCPTool.ts diff --git a/client/src/components/SidePanel/Agents/MCPTool.tsx b/client/src/components/SidePanel/Agents/MCPTool.tsx index c6bfd2b09c..e36efc443e 100644 --- a/client/src/components/SidePanel/Agents/MCPTool.tsx +++ b/client/src/components/SidePanel/Agents/MCPTool.tsx @@ -4,7 +4,6 @@ import { ChevronDown } from 'lucide-react'; import { useFormContext } from 'react-hook-form'; import { Constants } from 'librechat-data-provider'; import * as AccordionPrimitive from '@radix-ui/react-accordion'; -import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; import { Label, Checkbox, @@ -14,20 +13,18 @@ import { AccordionItem, CircleHelpIcon, OGDialogTrigger, - useToastContext, AccordionContent, OGDialogTemplate, } from '@librechat/client'; import type { AgentForm, MCPServerInfo } from '~/common'; +import { useLocalize, useMCPServerManager, useRemoveMCPTool } from '~/hooks'; import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon'; import MCPConfigDialog from '~/components/MCP/MCPConfigDialog'; -import { useLocalize, useMCPServerManager } from '~/hooks'; import { cn } from '~/utils'; export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) { const localize = useLocalize(); - const { showToast } = useToastContext(); - const updateUserPlugins = useUpdateUserPluginsMutation(); + const { removeTool } = useRemoveMCPTool(); const { getValues, setValue } = useFormContext(); const { getServerStatusIconProps, getConfigDialogProps } = useMCPServerManager(); @@ -56,36 +53,6 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) setValue('tools', [...otherTools, ...newSelectedTools]); }; - const removeTool = (serverName: string) => { - if (!serverName) { - return; - } - updateUserPlugins.mutate( - { - pluginKey: `${Constants.mcp_prefix}${serverName}`, - action: 'uninstall', - auth: {}, - isEntityTool: true, - }, - { - onError: (error: unknown) => { - showToast({ message: `Error while deleting the tool: ${error}`, status: 'error' }); - }, - onSuccess: () => { - const currentTools = getValues('tools'); - const remainingToolIds = - currentTools?.filter( - (currentToolId) => - currentToolId !== serverName && - !currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`), - ) || []; - setValue('tools', remainingToolIds); - showToast({ message: 'Tool deleted successfully', status: 'success' }); - }, - }, - ); - }; - const selectedTools = getSelectedTools(); const isExpanded = accordionValue === currentServerName; diff --git a/client/src/components/SidePanel/Agents/UnconfiguredMCPTool.tsx b/client/src/components/SidePanel/Agents/UnconfiguredMCPTool.tsx index 0377243176..6d18814776 100644 --- a/client/src/components/SidePanel/Agents/UnconfiguredMCPTool.tsx +++ b/client/src/components/SidePanel/Agents/UnconfiguredMCPTool.tsx @@ -1,25 +1,12 @@ import React, { useState } from 'react'; import { CircleX } from 'lucide-react'; -import { useFormContext } from 'react-hook-form'; -import { Constants } from 'librechat-data-provider'; -import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; -import { - Label, - OGDialog, - TrashIcon, - useToastContext, - OGDialogTrigger, - OGDialogTemplate, -} from '@librechat/client'; -import type { AgentForm } from '~/common'; -import { useLocalize } from '~/hooks'; +import { Label, OGDialog, TrashIcon, OGDialogTrigger, OGDialogTemplate } from '@librechat/client'; +import { useLocalize, useRemoveMCPTool } from '~/hooks'; import { cn } from '~/utils'; export default function UnconfiguredMCPTool({ serverName }: { serverName?: string }) { const localize = useLocalize(); - const { showToast } = useToastContext(); - const updateUserPlugins = useUpdateUserPluginsMutation(); - const { getValues, setValue } = useFormContext(); + const { removeTool } = useRemoveMCPTool(); const [isFocused, setIsFocused] = useState(false); const [isHovering, setIsHovering] = useState(false); @@ -28,36 +15,6 @@ export default function UnconfiguredMCPTool({ serverName }: { serverName?: strin return null; } - const removeTool = () => { - updateUserPlugins.mutate( - { - pluginKey: `${Constants.mcp_prefix}${serverName}`, - action: 'uninstall', - auth: {}, - isEntityTool: true, - }, - { - onError: (error: unknown) => { - showToast({ - message: localize('com_ui_delete_tool_error', { error: String(error) }), - status: 'error', - }); - }, - onSuccess: () => { - const currentTools = getValues('tools'); - const remainingToolIds = - currentTools?.filter( - (currentToolId) => - currentToolId !== serverName && - !currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`), - ) || []; - setValue('tools', remainingToolIds); - showToast({ message: localize('com_ui_delete_tool_success'), status: 'success' }); - }, - }, - ); - }; - return (
} selection={{ - selectHandler: () => removeTool(), + selectHandler: () => removeTool(serverName || ''), selectClasses: 'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white', selectText: localize('com_ui_delete'), diff --git a/client/src/components/SidePanel/Agents/UninitializedMCPTool.tsx b/client/src/components/SidePanel/Agents/UninitializedMCPTool.tsx index 336b56f3d2..24d45d4681 100644 --- a/client/src/components/SidePanel/Agents/UninitializedMCPTool.tsx +++ b/client/src/components/SidePanel/Agents/UninitializedMCPTool.tsx @@ -1,29 +1,18 @@ import React, { useState } from 'react'; -import { useFormContext } from 'react-hook-form'; -import { Constants } from 'librechat-data-provider'; -import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; -import { - Label, - OGDialog, - TrashIcon, - OGDialogTrigger, - useToastContext, - OGDialogTemplate, -} from '@librechat/client'; -import type { AgentForm, MCPServerInfo } from '~/common'; +import { Label, OGDialog, TrashIcon, OGDialogTrigger, OGDialogTemplate } from '@librechat/client'; +import type { MCPServerInfo } from '~/common'; +import { useLocalize, useMCPServerManager, useRemoveMCPTool } from '~/hooks'; import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon'; import MCPConfigDialog from '~/components/MCP/MCPConfigDialog'; -import { useLocalize, useMCPServerManager } from '~/hooks'; import { cn } from '~/utils'; export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) { + const localize = useLocalize(); + const { removeTool } = useRemoveMCPTool(); + const [isFocused, setIsFocused] = useState(false); const [isHovering, setIsHovering] = useState(false); - const localize = useLocalize(); - const { showToast } = useToastContext(); - const updateUserPlugins = useUpdateUserPluginsMutation(); - const { getValues, setValue } = useFormContext(); const { initializeServer, isInitializing, getServerStatusIconProps, getConfigDialogProps } = useMCPServerManager(); @@ -31,39 +20,6 @@ export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPS return null; } - const removeTool = (serverName: string) => { - if (!serverName) { - return; - } - updateUserPlugins.mutate( - { - pluginKey: `${Constants.mcp_prefix}${serverName}`, - action: 'uninstall', - auth: {}, - isEntityTool: true, - }, - { - onError: (error: unknown) => { - showToast({ - message: localize('com_ui_delete_tool_error', { error: String(error) }), - status: 'error', - }); - }, - onSuccess: () => { - const currentTools = getValues('tools'); - const remainingToolIds = - currentTools?.filter( - (currentToolId) => - currentToolId !== serverName && - !currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`), - ) || []; - setValue('tools', remainingToolIds); - showToast({ message: localize('com_ui_delete_tool_success'), status: 'success' }); - }, - }, - ); - }; - const serverName = serverInfo.serverName; const isServerInitializing = isInitializing(serverName); const statusIconProps = getServerStatusIconProps(serverName); diff --git a/client/src/hooks/MCP/index.ts b/client/src/hooks/MCP/index.ts index 761f61b266..fa2a7d7fb6 100644 --- a/client/src/hooks/MCP/index.ts +++ b/client/src/hooks/MCP/index.ts @@ -3,3 +3,4 @@ export * from './useMCPConnectionStatus'; export * from './useMCPSelect'; export * from './useVisibleTools'; export { useMCPServerManager } from './useMCPServerManager'; +export { useRemoveMCPTool } from './useRemoveMCPTool'; diff --git a/client/src/hooks/MCP/useRemoveMCPTool.ts b/client/src/hooks/MCP/useRemoveMCPTool.ts new file mode 100644 index 0000000000..af14f5c084 --- /dev/null +++ b/client/src/hooks/MCP/useRemoveMCPTool.ts @@ -0,0 +1,61 @@ +import { useCallback } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { Constants } from 'librechat-data-provider'; +import { useToastContext } from '@librechat/client'; +import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; +import type { AgentForm } from '~/common'; +import { useLocalize } from '~/hooks'; + +/** + * Hook for removing MCP tools/servers from an agent + * Provides unified logic for MCPTool, UninitializedMCPTool, and UnconfiguredMCPTool components + */ +export function useRemoveMCPTool() { + const localize = useLocalize(); + const { showToast } = useToastContext(); + const updateUserPlugins = useUpdateUserPluginsMutation(); + const { getValues, setValue } = useFormContext(); + + const removeTool = useCallback( + (serverName: string) => { + if (!serverName) { + return; + } + + updateUserPlugins.mutate( + { + pluginKey: `${Constants.mcp_prefix}${serverName}`, + action: 'uninstall', + auth: {}, + isEntityTool: true, + }, + { + onError: (error: unknown) => { + showToast({ + message: localize('com_ui_delete_tool_error', { error: String(error) }), + status: 'error', + }); + }, + onSuccess: () => { + const currentTools = getValues('tools'); + const remainingToolIds = + currentTools?.filter( + (currentToolId) => + currentToolId !== serverName && + !currentToolId.endsWith(`${Constants.mcp_delimiter}${serverName}`), + ) || []; + setValue('tools', remainingToolIds, { shouldDirty: true }); + + showToast({ + message: localize('com_ui_delete_tool_save_reminder'), + status: 'warning', + }); + }, + }, + ); + }, + [getValues, setValue, updateUserPlugins, showToast, localize], + ); + + return { removeTool }; +} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 8b82166389..faccb8dd1e 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -834,7 +834,7 @@ "com_ui_delete_tool": "Delete Tool", "com_ui_delete_tool_confirm": "Are you sure you want to delete this tool?", "com_ui_delete_tool_error": "Error while deleting the tool: {{error}}", - "com_ui_delete_tool_success": "Tool deleted successfully", + "com_ui_delete_tool_save_reminder": "Tool removed. Save the agent to apply changes.", "com_ui_deleted": "Deleted", "com_ui_deleting_file": "Deleting file...", "com_ui_descending": "Desc",