mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +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
116
client/src/components/Tools/MCPToolItem.tsx
Normal file
116
client/src/components/Tools/MCPToolItem.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import { XCircle, PlusCircleIcon, Wrench } from 'lucide-react';
|
||||
import type { AgentToolType } from 'librechat-data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type MCPToolItemProps = {
|
||||
tool: AgentToolType;
|
||||
onAddTool: () => void;
|
||||
onRemoveTool: () => void;
|
||||
isInstalled?: boolean;
|
||||
isConfiguring?: boolean;
|
||||
isInitializing?: boolean;
|
||||
};
|
||||
|
||||
function MCPToolItem({
|
||||
tool,
|
||||
onAddTool,
|
||||
onRemoveTool,
|
||||
isInstalled = false,
|
||||
isConfiguring = false,
|
||||
isInitializing = false,
|
||||
}: MCPToolItemProps) {
|
||||
const localize = useLocalize();
|
||||
const handleClick = () => {
|
||||
if (isInstalled) {
|
||||
onRemoveTool();
|
||||
} else {
|
||||
onAddTool();
|
||||
}
|
||||
};
|
||||
|
||||
const name = tool.metadata?.name || tool.tool_id;
|
||||
const description = tool.metadata?.description || '';
|
||||
const icon = tool.metadata?.icon;
|
||||
|
||||
// Determine button state and text
|
||||
const getButtonState = () => {
|
||||
if (isInstalled) {
|
||||
return {
|
||||
text: localize('com_nav_tool_remove'),
|
||||
icon: <XCircle className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className:
|
||||
'btn relative bg-gray-300 hover:bg-gray-400 dark:bg-gray-50 dark:hover:bg-gray-200',
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (isConfiguring) {
|
||||
return {
|
||||
text: localize('com_ui_confirm'),
|
||||
icon: <PlusCircleIcon className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className: 'btn btn-primary relative',
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (isInitializing) {
|
||||
return {
|
||||
text: localize('com_ui_initializing'),
|
||||
icon: <Wrench className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className: 'btn btn-primary relative opacity-75 cursor-not-allowed',
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
text: localize('com_ui_add'),
|
||||
icon: <PlusCircleIcon className="flex h-4 w-4 items-center stroke-2" />,
|
||||
className: 'btn btn-primary relative',
|
||||
disabled: false,
|
||||
};
|
||||
};
|
||||
|
||||
const buttonState = getButtonState();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded border border-border-medium bg-transparent p-6">
|
||||
<div className="flex gap-4">
|
||||
<div className="h-[70px] w-[70px] shrink-0">
|
||||
<div className="relative h-full w-full">
|
||||
{icon ? (
|
||||
<img
|
||||
src={icon}
|
||||
alt={localize('com_ui_logo', { 0: name })}
|
||||
className="h-full w-full rounded-[5px] bg-white"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center rounded-[5px] border border-border-medium bg-transparent">
|
||||
<Wrench className="h-8 w-8 text-text-secondary" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 rounded-[5px] ring-1 ring-inset ring-black/10"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-col items-start justify-between">
|
||||
<div className="mb-2 line-clamp-1 max-w-full text-lg leading-5 text-text-primary">
|
||||
{name}
|
||||
</div>
|
||||
<button
|
||||
className={buttonState.className}
|
||||
aria-label={`${buttonState.text} ${name}`}
|
||||
onClick={handleClick}
|
||||
disabled={buttonState.disabled}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{buttonState.text}
|
||||
{buttonState.icon}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="line-clamp-3 h-[60px] text-sm text-text-secondary">{description}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MCPToolItem;
|
||||
370
client/src/components/Tools/MCPToolSelectDialog.tsx
Normal file
370
client/src/components/Tools/MCPToolSelectDialog.tsx
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Constants, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TError, AgentToolType } from 'librechat-data-provider';
|
||||
import type { AgentForm, TPluginStoreDialogProps } from '~/common';
|
||||
import { useLocalize, usePluginDialogHelpers, useMCPServerManager } from '~/hooks';
|
||||
import { useGetStartupConfig, useAvailableToolsQuery } from '~/data-provider';
|
||||
import CustomUserVarsSection from '~/components/MCP/CustomUserVarsSection';
|
||||
import { PluginPagination } from '~/components/Plugins/Store';
|
||||
import { useAgentPanelContext } from '~/Providers';
|
||||
import MCPToolItem from './MCPToolItem';
|
||||
|
||||
function MCPToolSelectDialog({
|
||||
isOpen,
|
||||
agentId,
|
||||
setIsOpen,
|
||||
mcpServerNames,
|
||||
}: TPluginStoreDialogProps & {
|
||||
agentId: string;
|
||||
mcpServerNames?: string[];
|
||||
endpoint: EModelEndpoint.agents;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { mcpServersMap } = useAgentPanelContext();
|
||||
const { initializeServer } = useMCPServerManager();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
const { refetch: refetchAvailableTools } = useAvailableToolsQuery(EModelEndpoint.agents);
|
||||
|
||||
const [isInitializing, setIsInitializing] = useState<string | null>(null);
|
||||
const [configuringServer, setConfiguringServer] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
maxPage,
|
||||
setMaxPage,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
itemsPerPage,
|
||||
searchChanged,
|
||||
setSearchChanged,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
gridRef,
|
||||
handleSearch,
|
||||
handleChangePage,
|
||||
error,
|
||||
setError,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
} = usePluginDialogHelpers();
|
||||
|
||||
const updateUserPlugins = useUpdateUserPluginsMutation();
|
||||
|
||||
const handleInstallError = (error: TError) => {
|
||||
setError(true);
|
||||
const errorMessage = error.response?.data?.message ?? '';
|
||||
if (errorMessage) {
|
||||
setErrorMessage(errorMessage);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setError(false);
|
||||
setErrorMessage('');
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
const handleDirectAdd = async (serverName: string) => {
|
||||
try {
|
||||
setIsInitializing(serverName);
|
||||
const serverInfo = mcpServersMap.get(serverName);
|
||||
if (!serverInfo?.isConnected) {
|
||||
const result = await initializeServer(serverName);
|
||||
if (result?.success && result.oauthRequired && result.oauthUrl) {
|
||||
setIsInitializing(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'install',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => {
|
||||
handleInstallError(error as TError);
|
||||
setIsInitializing(null);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
const { data: updatedAvailableTools } = await refetchAvailableTools();
|
||||
|
||||
const currentTools = getValues('tools') || [];
|
||||
const toolsToAdd: string[] = [
|
||||
`${Constants.mcp_server}${Constants.mcp_delimiter}${serverName}`,
|
||||
];
|
||||
|
||||
if (updatedAvailableTools) {
|
||||
updatedAvailableTools.forEach((tool) => {
|
||||
if (tool.pluginKey.endsWith(`${Constants.mcp_delimiter}${serverName}`)) {
|
||||
toolsToAdd.push(tool.pluginKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const newTools = toolsToAdd.filter((tool) => !currentTools.includes(tool));
|
||||
if (newTools.length > 0) {
|
||||
setValue('tools', [...currentTools, ...newTools]);
|
||||
}
|
||||
setIsInitializing(null);
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error adding MCP server:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveCustomVars = async (serverName: string, authData: Record<string, string>) => {
|
||||
try {
|
||||
await updateUserPlugins.mutateAsync({
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'install',
|
||||
auth: authData,
|
||||
isEntityTool: true,
|
||||
});
|
||||
|
||||
await handleDirectAdd(serverName);
|
||||
|
||||
setConfiguringServer(null);
|
||||
} catch (error) {
|
||||
console.error('Error saving custom vars:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRevokeCustomVars = (serverName: string) => {
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'uninstall',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => handleInstallError(error as TError),
|
||||
onSuccess: () => {
|
||||
setConfiguringServer(null);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const onAddTool = async (serverName: string) => {
|
||||
if (configuringServer === serverName) {
|
||||
setConfiguringServer(null);
|
||||
await handleDirectAdd(serverName);
|
||||
return;
|
||||
}
|
||||
|
||||
const serverConfig = startupConfig?.mcpServers?.[serverName];
|
||||
const hasCustomUserVars =
|
||||
serverConfig?.customUserVars && Object.keys(serverConfig.customUserVars).length > 0;
|
||||
|
||||
if (hasCustomUserVars) {
|
||||
setConfiguringServer(serverName);
|
||||
} else {
|
||||
await handleDirectAdd(serverName);
|
||||
}
|
||||
};
|
||||
|
||||
const onRemoveTool = (serverName: string) => {
|
||||
updateUserPlugins.mutate(
|
||||
{
|
||||
pluginKey: `${Constants.mcp_prefix}${serverName}`,
|
||||
action: 'uninstall',
|
||||
auth: {},
|
||||
isEntityTool: true,
|
||||
},
|
||||
{
|
||||
onError: (error: unknown) => handleInstallError(error as TError),
|
||||
onSuccess: () => {
|
||||
const currentTools = getValues('tools') || [];
|
||||
const remainingTools = currentTools.filter(
|
||||
(tool) =>
|
||||
tool !== serverName && !tool.endsWith(`${Constants.mcp_delimiter}${serverName}`),
|
||||
);
|
||||
setValue('tools', remainingTools);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const installedToolsSet = useMemo(() => {
|
||||
return new Set(mcpServerNames);
|
||||
}, [mcpServerNames]);
|
||||
|
||||
const mcpServers = useMemo(() => {
|
||||
const servers = Array.from(mcpServersMap.values());
|
||||
return servers.sort((a, b) => a.serverName.localeCompare(b.serverName));
|
||||
}, [mcpServersMap]);
|
||||
|
||||
const filteredServers = useMemo(() => {
|
||||
if (!searchValue) {
|
||||
return mcpServers;
|
||||
}
|
||||
return mcpServers.filter((serverInfo) =>
|
||||
serverInfo.serverName.toLowerCase().includes(searchValue.toLowerCase()),
|
||||
);
|
||||
}, [mcpServers, searchValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setMaxPage(Math.ceil(filteredServers.length / itemsPerPage));
|
||||
if (searchChanged) {
|
||||
setCurrentPage(1);
|
||||
setSearchChanged(false);
|
||||
}
|
||||
}, [
|
||||
setMaxPage,
|
||||
itemsPerPage,
|
||||
searchChanged,
|
||||
setCurrentPage,
|
||||
setSearchChanged,
|
||||
filteredServers.length,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
setSearchValue('');
|
||||
setConfiguringServer(null);
|
||||
setIsInitializing(null);
|
||||
}}
|
||||
className="relative z-[102]"
|
||||
>
|
||||
<div className="fixed inset-0 bg-surface-primary opacity-60 transition-opacity dark:opacity-80" />
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<DialogPanel
|
||||
className="relative max-h-[90vh] w-full transform overflow-hidden overflow-y-auto rounded-lg bg-surface-secondary text-left shadow-xl transition-all max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
||||
style={{ minHeight: '610px' }}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b-[1px] border-border-medium px-4 pb-4 pt-5 sm:p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="text-center sm:text-left">
|
||||
<DialogTitle className="text-lg font-medium leading-6 text-text-primary">
|
||||
{localize('com_nav_tool_dialog_mcp_server_tools')}
|
||||
</DialogTitle>
|
||||
<Description className="text-sm text-text-secondary">
|
||||
{localize('com_nav_tool_dialog_description')}
|
||||
</Description>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
setConfiguringServer(null);
|
||||
setIsInitializing(null);
|
||||
}}
|
||||
className="inline-block rounded-full text-text-secondary transition-colors hover:text-text-primary"
|
||||
aria-label="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
className="relative m-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
|
||||
role="alert"
|
||||
>
|
||||
{localize('com_nav_plugin_auth_error')} {errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{configuringServer && (
|
||||
<div className="p-4 sm:p-6 sm:pt-4">
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-text-secondary">
|
||||
{localize('com_ui_mcp_configure_server_description', { 0: configuringServer })}
|
||||
</p>
|
||||
</div>
|
||||
<CustomUserVarsSection
|
||||
serverName={configuringServer}
|
||||
fields={startupConfig?.mcpServers?.[configuringServer]?.customUserVars || {}}
|
||||
onSave={(authData) => handleSaveCustomVars(configuringServer, authData)}
|
||||
onRevoke={() => handleRevokeCustomVars(configuringServer)}
|
||||
isSubmitting={updateUserPlugins.isLoading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-4 sm:p-6 sm:pt-4">
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
<div
|
||||
className="flex items-center justify-center space-x-4"
|
||||
onClick={() => setConfiguringServer(null)}
|
||||
>
|
||||
<Search className="h-6 w-6 text-text-tertiary" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
placeholder={localize('com_nav_tool_search')}
|
||||
className="w-64 rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={gridRef}
|
||||
className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
style={{ minHeight: '410px' }}
|
||||
>
|
||||
{filteredServers
|
||||
.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
|
||||
.map((serverInfo) => {
|
||||
const isInstalled = installedToolsSet.has(serverInfo.serverName);
|
||||
const isConfiguring = configuringServer === serverInfo.serverName;
|
||||
const isServerInitializing = isInitializing === serverInfo.serverName;
|
||||
|
||||
const tool: AgentToolType = {
|
||||
agent_id: agentId,
|
||||
tool_id: serverInfo.serverName,
|
||||
metadata: {
|
||||
...serverInfo.metadata,
|
||||
description: `${localize('com_ui_tool_collection_prefix')} ${serverInfo.serverName}`,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<MCPToolItem
|
||||
tool={tool}
|
||||
isInstalled={isInstalled}
|
||||
key={serverInfo.serverName}
|
||||
isConfiguring={isConfiguring}
|
||||
isInitializing={isServerInitializing}
|
||||
onAddTool={() => onAddTool(serverInfo.serverName)}
|
||||
onRemoveTool={() => onRemoveTool(serverInfo.serverName)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex flex-col items-center gap-2 sm:flex-row sm:justify-between">
|
||||
{maxPage > 0 ? (
|
||||
<PluginPagination
|
||||
currentPage={currentPage}
|
||||
maxPage={maxPage}
|
||||
onChangePage={handleChangePage}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ height: '21px' }}></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default MCPToolSelectDialog;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Constants, isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import { isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import type {
|
||||
|
|
@ -15,7 +15,6 @@ import type { AgentForm, TPluginStoreDialogProps } from '~/common';
|
|||
import { PluginPagination, PluginAuthForm } from '~/components/Plugins/Store';
|
||||
import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
|
||||
import { useLocalize, usePluginDialogHelpers } from '~/hooks';
|
||||
import { useAvailableToolsQuery } from '~/data-provider';
|
||||
import ToolItem from './ToolItem';
|
||||
|
||||
function ToolSelectDialog({
|
||||
|
|
@ -26,10 +25,9 @@ function ToolSelectDialog({
|
|||
endpoint: AssistantsEndpoint | EModelEndpoint.agents;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
const { data: tools } = useAvailableToolsQuery(endpoint);
|
||||
const { groupedTools } = useAgentPanelContext();
|
||||
const isAgentTools = isAgentsEndpoint(endpoint);
|
||||
const { getValues, setValue } = useFormContext<AgentForm>();
|
||||
const { groupedTools, pluginTools } = useAgentPanelContext();
|
||||
|
||||
const {
|
||||
maxPage,
|
||||
|
|
@ -121,38 +119,28 @@ function ToolSelectDialog({
|
|||
|
||||
const onAddTool = (pluginKey: string) => {
|
||||
setShowPluginAuthForm(false);
|
||||
const getAvailablePluginFromKey = tools?.find((p) => p.pluginKey === pluginKey);
|
||||
setSelectedPlugin(getAvailablePluginFromKey);
|
||||
const availablePluginFromKey = pluginTools?.find((p) => p.pluginKey === pluginKey);
|
||||
setSelectedPlugin(availablePluginFromKey);
|
||||
|
||||
const isMCPTool = pluginKey.includes(Constants.mcp_delimiter);
|
||||
|
||||
if (isMCPTool) {
|
||||
// MCP tools have their variables configured elsewhere (e.g., MCPPanel or MCPSelect),
|
||||
// so we directly proceed to install without showing the auth form.
|
||||
handleInstall({ pluginKey, action: 'install', auth: {} });
|
||||
const { authConfig, authenticated = false } = availablePluginFromKey ?? {};
|
||||
if (authConfig && authConfig.length > 0 && !authenticated) {
|
||||
setShowPluginAuthForm(true);
|
||||
} else {
|
||||
const { authConfig, authenticated = false } = getAvailablePluginFromKey ?? {};
|
||||
if (authConfig && authConfig.length > 0 && !authenticated) {
|
||||
setShowPluginAuthForm(true);
|
||||
} else {
|
||||
handleInstall({
|
||||
pluginKey,
|
||||
action: 'install',
|
||||
auth: {},
|
||||
});
|
||||
}
|
||||
handleInstall({
|
||||
pluginKey,
|
||||
action: 'install',
|
||||
auth: {},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const filteredTools = Object.values(groupedTools || {}).filter(
|
||||
(tool: AgentToolType & { tools?: AgentToolType[] }) => {
|
||||
// Check if the parent tool matches
|
||||
if (tool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase())) {
|
||||
(currentTool: AgentToolType & { tools?: AgentToolType[] }) => {
|
||||
if (currentTool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
// Check if any child tools match
|
||||
if (tool.tools) {
|
||||
return tool.tools.some((childTool) =>
|
||||
if (currentTool.tools) {
|
||||
return currentTool.tools.some((childTool) =>
|
||||
childTool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
|
@ -169,9 +157,9 @@ function ToolSelectDialog({
|
|||
}
|
||||
}
|
||||
}, [
|
||||
tools,
|
||||
itemsPerPage,
|
||||
pluginTools,
|
||||
searchValue,
|
||||
itemsPerPage,
|
||||
filteredTools,
|
||||
searchChanged,
|
||||
setMaxPage,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as MCPToolSelectDialog } from './MCPToolSelectDialog';
|
||||
export { default as ToolSelectDialog } from './ToolSelectDialog';
|
||||
export { default as ToolItem } from './ToolItem';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue