🔧 refactor: Consolidate MCP tool removal and Improve UX (#9609)

* 🔧 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
This commit is contained in:
Danny Avila 2025-09-12 21:37:07 -04:00 committed by GitHub
parent dd93db40bc
commit 5245aeea8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 75 additions and 133 deletions

View file

@ -4,7 +4,6 @@ import { ChevronDown } from 'lucide-react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { Constants } from 'librechat-data-provider'; import { Constants } from 'librechat-data-provider';
import * as AccordionPrimitive from '@radix-ui/react-accordion'; import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
import { import {
Label, Label,
Checkbox, Checkbox,
@ -14,20 +13,18 @@ import {
AccordionItem, AccordionItem,
CircleHelpIcon, CircleHelpIcon,
OGDialogTrigger, OGDialogTrigger,
useToastContext,
AccordionContent, AccordionContent,
OGDialogTemplate, OGDialogTemplate,
} from '@librechat/client'; } from '@librechat/client';
import type { AgentForm, MCPServerInfo } from '~/common'; import type { AgentForm, MCPServerInfo } from '~/common';
import { useLocalize, useMCPServerManager, useRemoveMCPTool } from '~/hooks';
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon'; import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog'; import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
import { useLocalize, useMCPServerManager } from '~/hooks';
import { cn } from '~/utils'; import { cn } from '~/utils';
export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) { export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) {
const localize = useLocalize(); const localize = useLocalize();
const { showToast } = useToastContext(); const { removeTool } = useRemoveMCPTool();
const updateUserPlugins = useUpdateUserPluginsMutation();
const { getValues, setValue } = useFormContext<AgentForm>(); const { getValues, setValue } = useFormContext<AgentForm>();
const { getServerStatusIconProps, getConfigDialogProps } = useMCPServerManager(); const { getServerStatusIconProps, getConfigDialogProps } = useMCPServerManager();
@ -56,36 +53,6 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
setValue('tools', [...otherTools, ...newSelectedTools]); 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 selectedTools = getSelectedTools();
const isExpanded = accordionValue === currentServerName; const isExpanded = accordionValue === currentServerName;

View file

@ -1,25 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { CircleX } from 'lucide-react'; import { CircleX } from 'lucide-react';
import { useFormContext } from 'react-hook-form'; import { Label, OGDialog, TrashIcon, OGDialogTrigger, OGDialogTemplate } from '@librechat/client';
import { Constants } from 'librechat-data-provider'; import { useLocalize, useRemoveMCPTool } from '~/hooks';
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 { cn } from '~/utils'; import { cn } from '~/utils';
export default function UnconfiguredMCPTool({ serverName }: { serverName?: string }) { export default function UnconfiguredMCPTool({ serverName }: { serverName?: string }) {
const localize = useLocalize(); const localize = useLocalize();
const { showToast } = useToastContext(); const { removeTool } = useRemoveMCPTool();
const updateUserPlugins = useUpdateUserPluginsMutation();
const { getValues, setValue } = useFormContext<AgentForm>();
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const [isHovering, setIsHovering] = useState(false); const [isHovering, setIsHovering] = useState(false);
@ -28,36 +15,6 @@ export default function UnconfiguredMCPTool({ serverName }: { serverName?: strin
return null; 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 ( return (
<OGDialog> <OGDialog>
<div <div
@ -116,7 +73,7 @@ export default function UnconfiguredMCPTool({ serverName }: { serverName?: strin
</Label> </Label>
} }
selection={{ selection={{
selectHandler: () => removeTool(), selectHandler: () => removeTool(serverName || ''),
selectClasses: selectClasses:
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white', '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'), selectText: localize('com_ui_delete'),

View file

@ -1,29 +1,18 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useFormContext } from 'react-hook-form'; import { Label, OGDialog, TrashIcon, OGDialogTrigger, OGDialogTemplate } from '@librechat/client';
import { Constants } from 'librechat-data-provider'; import type { MCPServerInfo } from '~/common';
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; import { useLocalize, useMCPServerManager, useRemoveMCPTool } from '~/hooks';
import {
Label,
OGDialog,
TrashIcon,
OGDialogTrigger,
useToastContext,
OGDialogTemplate,
} from '@librechat/client';
import type { AgentForm, MCPServerInfo } from '~/common';
import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon'; import MCPServerStatusIcon from '~/components/MCP/MCPServerStatusIcon';
import MCPConfigDialog from '~/components/MCP/MCPConfigDialog'; import MCPConfigDialog from '~/components/MCP/MCPConfigDialog';
import { useLocalize, useMCPServerManager } from '~/hooks';
import { cn } from '~/utils'; import { cn } from '~/utils';
export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) { export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPServerInfo }) {
const localize = useLocalize();
const { removeTool } = useRemoveMCPTool();
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const [isHovering, setIsHovering] = useState(false); const [isHovering, setIsHovering] = useState(false);
const localize = useLocalize();
const { showToast } = useToastContext();
const updateUserPlugins = useUpdateUserPluginsMutation();
const { getValues, setValue } = useFormContext<AgentForm>();
const { initializeServer, isInitializing, getServerStatusIconProps, getConfigDialogProps } = const { initializeServer, isInitializing, getServerStatusIconProps, getConfigDialogProps } =
useMCPServerManager(); useMCPServerManager();
@ -31,39 +20,6 @@ export default function UninitializedMCPTool({ serverInfo }: { serverInfo?: MCPS
return null; 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 serverName = serverInfo.serverName;
const isServerInitializing = isInitializing(serverName); const isServerInitializing = isInitializing(serverName);
const statusIconProps = getServerStatusIconProps(serverName); const statusIconProps = getServerStatusIconProps(serverName);

View file

@ -3,3 +3,4 @@ export * from './useMCPConnectionStatus';
export * from './useMCPSelect'; export * from './useMCPSelect';
export * from './useVisibleTools'; export * from './useVisibleTools';
export { useMCPServerManager } from './useMCPServerManager'; export { useMCPServerManager } from './useMCPServerManager';
export { useRemoveMCPTool } from './useRemoveMCPTool';

View file

@ -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<AgentForm>();
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 };
}

View file

@ -834,7 +834,7 @@
"com_ui_delete_tool": "Delete Tool", "com_ui_delete_tool": "Delete Tool",
"com_ui_delete_tool_confirm": "Are you sure you want to delete this 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_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_deleted": "Deleted",
"com_ui_deleting_file": "Deleting file...", "com_ui_deleting_file": "Deleting file...",
"com_ui_descending": "Desc", "com_ui_descending": "Desc",