mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
🔧 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:
parent
dd93db40bc
commit
5245aeea8f
6 changed files with 75 additions and 133 deletions
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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';
|
||||||
|
|
61
client/src/hooks/MCP/useRemoveMCPTool.ts
Normal file
61
client/src/hooks/MCP/useRemoveMCPTool.ts
Normal 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 };
|
||||||
|
}
|
|
@ -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",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue