🧰 refactor: Decouple MCP Tools from System Tools (#9748)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run

This commit is contained in:
Danny Avila 2025-09-21 07:56:40 -04:00 committed by GitHub
parent 9d2aba5df5
commit 386900fb4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1032 additions and 1195 deletions

View file

@ -7,8 +7,8 @@ import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-quer
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 { useGetStartupConfig, useMCPToolsQuery } from '~/data-provider';
import { PluginPagination } from '~/components/Plugins/Store';
import { useAgentPanelContext } from '~/Providers';
import MCPToolItem from './MCPToolItem';
@ -27,8 +27,8 @@ function MCPToolSelectDialog({
const { mcpServersMap } = useAgentPanelContext();
const { initializeServer } = useMCPServerManager();
const { data: startupConfig } = useGetStartupConfig();
const { refetch: refetchMCPTools } = useMCPToolsQuery();
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);
@ -90,15 +90,15 @@ function MCPToolSelectDialog({
setIsInitializing(null);
},
onSuccess: async () => {
const { data: updatedAvailableTools } = await refetchAvailableTools();
const { data: updatedMCPTools } = await refetchMCPTools();
const currentTools = getValues('tools') || [];
const toolsToAdd: string[] = [
`${Constants.mcp_server}${Constants.mcp_delimiter}${serverName}`,
];
if (updatedAvailableTools) {
updatedAvailableTools.forEach((tool) => {
if (updatedMCPTools) {
updatedMCPTools.forEach((tool) => {
if (tool.pluginKey.endsWith(`${Constants.mcp_delimiter}${serverName}`)) {
toolsToAdd.push(tool.pluginKey);
}

View file

@ -8,7 +8,7 @@ import type {
AssistantsEndpoint,
EModelEndpoint,
TPluginAction,
AgentToolType,
TPlugin,
TError,
} from 'librechat-data-provider';
import type { AgentForm, TPluginStoreDialogProps } from '~/common';
@ -27,7 +27,8 @@ function ToolSelectDialog({
const localize = useLocalize();
const isAgentTools = isAgentsEndpoint(endpoint);
const { getValues, setValue } = useFormContext<AgentForm>();
const { groupedTools, pluginTools } = useAgentPanelContext();
// Only use regular tools, not MCP tools
const { regularTools } = useAgentPanelContext();
const {
maxPage,
@ -68,19 +69,8 @@ function ToolSelectDialog({
const handleInstall = (pluginAction: TPluginAction) => {
const addFunction = () => {
const installedToolIds: string[] = getValues('tools') || [];
// Add the parent
installedToolIds.push(pluginAction.pluginKey);
// If this tool is a group, add subtools too
const groupObj = groupedTools?.[pluginAction.pluginKey];
if (groupObj?.tools && groupObj.tools.length > 0) {
for (const sub of groupObj.tools) {
if (!installedToolIds.includes(sub.tool_id)) {
installedToolIds.push(sub.tool_id);
}
}
}
setValue('tools', Array.from(new Set(installedToolIds))); // no duplicates just in case
setValue('tools', Array.from(new Set(installedToolIds)));
};
if (!pluginAction.auth) {
@ -98,19 +88,12 @@ function ToolSelectDialog({
};
const onRemoveTool = (toolId: string) => {
const groupObj = groupedTools?.[toolId];
const toolIdsToRemove = [toolId];
if (groupObj?.tools && groupObj.tools.length > 0) {
toolIdsToRemove.push(...groupObj.tools.map((sub) => sub.tool_id));
}
// Remove these from the formTools
updateUserPlugins.mutate(
{ pluginKey: toolId, action: 'uninstall', auth: {}, isEntityTool: true },
{
onError: (error: unknown) => handleInstallError(error as TError),
onSuccess: () => {
const remainingToolIds =
getValues('tools')?.filter((toolId) => !toolIdsToRemove.includes(toolId)) || [];
const remainingToolIds = getValues('tools')?.filter((id) => id !== toolId) || [];
setValue('tools', remainingToolIds);
},
},
@ -119,7 +102,8 @@ function ToolSelectDialog({
const onAddTool = (pluginKey: string) => {
setShowPluginAuthForm(false);
const availablePluginFromKey = pluginTools?.find((p) => p.pluginKey === pluginKey);
// Find the tool in regularTools
const availablePluginFromKey = regularTools?.find((p) => p.pluginKey === pluginKey);
setSelectedPlugin(availablePluginFromKey);
const { authConfig, authenticated = false } = availablePluginFromKey ?? {};
@ -134,30 +118,19 @@ function ToolSelectDialog({
}
};
const filteredTools = Object.values(groupedTools || {}).filter(
(currentTool: AgentToolType & { tools?: AgentToolType[] }) => {
if (currentTool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase())) {
return true;
}
if (currentTool.tools) {
return currentTool.tools.some((childTool) =>
childTool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase()),
);
}
return false;
},
);
const filteredTools = (regularTools || []).filter((tool: TPlugin) => {
return tool.name?.toLowerCase().includes(searchValue.toLowerCase());
});
useEffect(() => {
if (filteredTools) {
setMaxPage(Math.ceil(Object.keys(filteredTools || {}).length / itemsPerPage));
setMaxPage(Math.ceil(filteredTools.length / itemsPerPage));
if (searchChanged) {
setCurrentPage(1);
setSearchChanged(false);
}
}
}, [
pluginTools,
searchValue,
itemsPerPage,
filteredTools,
@ -254,10 +227,13 @@ function ToolSelectDialog({
.map((tool, index) => (
<ToolItem
key={index}
tool={tool}
isInstalled={getValues('tools')?.includes(tool.tool_id) || false}
onAddTool={() => onAddTool(tool.tool_id)}
onRemoveTool={() => onRemoveTool(tool.tool_id)}
tool={{
tool_id: tool.pluginKey,
metadata: tool,
}}
isInstalled={getValues('tools')?.includes(tool.pluginKey) || false}
onAddTool={() => onAddTool(tool.pluginKey)}
onRemoveTool={() => onRemoveTool(tool.pluginKey)}
/>
))}
</div>