mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-02 22:30:18 +01:00
✂️ refactor: MCP UI Separation for Agents (#9237)
* refactor: MCP UI Separation for Agents (Dustin WIP)
feat: separate MCPs into their own lists away from tools + actions and add the status indicator functionality from chat to their dropdown ui
fix: spotify mcp was not persisting on agent creation
feat: show disconnected saved servers and their tools in agent mcp list in created agents
fix: select-all regression fixed (caused by deleting tools we were drawing from for rendering list)
fix: dont show all mcps, only those installed in agent in list
feat: separate ToolSelectDialog for MCPServerTools
fix: uninitialized mcp servers not showing as added in toolselectdialog
refactor: reduce looping in AgentPanelContext for categorizing groups and mcps
refactor: split ToolSelectDialog and MCPToolSelectDialog functionality (still needs customization for custom user vars)
chore: address ESLint comments
chore: address ESLint comments
feat: one-click initialization on MCP servers in agent builder
fix: stop propagation triggering reinit on caret click
refactor: split uninitialized MCPs component from initialized MCPs
feat: new mcp tool select dialog ui with custom user vars
feat: show initialization state for CUV configurable MCPs too
chore: remove unused localization string
fix: deselecting all tools caused a re-render
fix: remove subtools so removal from MCPToolSelectDialog works more consistently
feat: added servers have all tools enabled by default
feat: mcp server list now alphabetical to prevent annoying ui behavior of servers jumping around depending on tool selection
fix: filter out placeholder group mcp tools from any actual tool calls / definitions
feat: indicator now takes you to config dialog for uninitialized servers
feat: show previously configured mcp servers that are now missing from the yaml
feat: select all enabled by default on first add to mcp server list
chore: address ESLint comments
* refactor: MCP UI Separation for Agents (Danny WIP)
chore: remove use of `{serverName}_mcp_{serverName}`
chore: import order
WIP: separate component concerns
refactor: streamline agent mcp tools
refactor: unify MCP server handling and improve tool visibility logic, remove unnecessary normalization or sorting, remove nesting button, make variable names clear
refactor: rename mcpServerIds to mcpServerNames for clarity and consistency across components
refactor: remove groupedMCPTools and toolToServerMap, streamline MCP server handling in context and components to effectively utilize mcpServersMap
refactor: optimize tool selection logic by replacing array includes with Set for improved performance
chore: add error logging for failed auth URL parsing in ToolCall component
refactor: enhance MCP tool handling by improving server name management and updating UI elements for better clarity
* refactor: decouple connection status from useMCPServerManager with useMCPConnectionStatus
* fix: improve MCP tool validation logic to handle unconfigured servers
* chore: enhance log message clarity for MCP server disconnection in updateUserPluginsController
* refactor: simplify connection status extraction in useMCPConnectionStatus hook
* refactor: improve initializing UX
* chore: replace string literal with ResourceType constant in useResourcePermissions
* refactor: cleanup code, remove redundancies, rename variables for clarity
* chore: add back filtering and sorting for mcp tools dialog
* refactor: initializeServer to return response and early return
* refactor: enhance server initialization logic and improve UI for OAuth interaction
* chore: clarify warning message for unconfigured MCP server in handleTools
* refactor: prevent CustomUserVarsSection from submitting tools dialog form
* fix: nested button of button issue in UninitializedMCPTool
* feat: add functionality to revoke custom user variables in MCPToolSelectDialog
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
d16f93b5f7
commit
49e8443ec5
30 changed files with 1589 additions and 180 deletions
183
client/src/components/SidePanel/Agents/UninitializedMCPTool.tsx
Normal file
183
client/src/components/SidePanel/Agents/UninitializedMCPTool.tsx
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
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 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 [isFocused, setIsFocused] = 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 } =
|
||||
useMCPServerManager();
|
||||
|
||||
if (!serverInfo) {
|
||||
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);
|
||||
const configDialogProps = getConfigDialogProps();
|
||||
|
||||
const statusIcon = statusIconProps && (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="cursor-pointer rounded p-0.5 hover:bg-surface-secondary"
|
||||
>
|
||||
<MCPServerStatusIcon {...statusIconProps} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<OGDialog>
|
||||
<div
|
||||
className="group relative flex w-full items-center gap-1 rounded-lg p-1 text-sm hover:bg-surface-primary-alt"
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={(e) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex grow cursor-pointer items-center gap-1 rounded bg-transparent p-0 text-left transition-colors"
|
||||
onClick={(e) => {
|
||||
if ((e.target as HTMLElement).closest('[data-status-icon]')) {
|
||||
return;
|
||||
}
|
||||
if (!isServerInitializing) {
|
||||
initializeServer(serverName);
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
if (!isServerInitializing) {
|
||||
initializeServer(serverName);
|
||||
}
|
||||
}
|
||||
}}
|
||||
aria-disabled={isServerInitializing}
|
||||
>
|
||||
{statusIcon && (
|
||||
<div className="flex items-center" data-status-icon>
|
||||
{statusIcon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{serverInfo.metadata.icon && (
|
||||
<div className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-full">
|
||||
<div
|
||||
className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full bg-center bg-no-repeat dark:bg-white/20"
|
||||
style={{
|
||||
backgroundImage: `url(${serverInfo.metadata.icon})`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="grow px-2 py-1.5"
|
||||
style={{ textOverflow: 'ellipsis', wordBreak: 'break-all', overflow: 'hidden' }}
|
||||
>
|
||||
{serverName}
|
||||
{isServerInitializing && (
|
||||
<span className="ml-2 text-xs text-text-secondary">
|
||||
{localize('com_ui_initializing')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OGDialogTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex h-7 w-7 items-center justify-center rounded transition-all duration-200 hover:bg-surface-active-alt focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
|
||||
isHovering || isFocused ? 'opacity-100' : 'pointer-events-none opacity-0',
|
||||
)}
|
||||
aria-label={`Delete ${serverName}`}
|
||||
tabIndex={0}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</OGDialogTrigger>
|
||||
</div>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_tool')}
|
||||
mainClassName="px-0"
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_tool_confirm')}
|
||||
</Label>
|
||||
}
|
||||
selection={{
|
||||
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'),
|
||||
}}
|
||||
/>
|
||||
{configDialogProps && <MCPConfigDialog {...configDialogProps} />}
|
||||
</OGDialog>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue