🧬 refactor: Optimize MCP Tool Queries with Server-Centric Architecture

🧬 refactor: Optimize MCP Tool Queries with Server-Centric Architecture

refactor: optimize mcp tool queries by removing redundancy, making server-centric structure, enabling query only when expected, minimize looping/transforming query data, eliminating unused/compute-heavy methods

ci: MCP Server Tools Mocking in Agent Tests
This commit is contained in:
Danny Avila 2025-09-21 20:19:51 -04:00
parent 5b1a31ef4d
commit f0599ad36c
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
19 changed files with 235 additions and 1104 deletions

View file

@ -7,6 +7,7 @@ import type { TStartupConfig, TPlugin, TUser } from 'librechat-data-provider';
import { mapPlugins, selectPlugins, processPlugins } from '~/utils';
import { cleanupTimestampedStorage } from '~/utils/timestamps';
import useSpeechSettingsInit from './useSpeechSettingsInit';
import { useMCPToolsQuery } from '~/data-provider';
import store from '~/store';
const pluginStore: TPlugin = {
@ -35,6 +36,10 @@ export default function useAppStartup({
useSpeechSettingsInit(!!user);
useMCPToolsQuery({
enabled: !!startupConfig?.mcpServers && !!user,
});
/** Clean up old localStorage entries on startup */
useEffect(() => {
cleanupTimestampedStorage();

View file

@ -1,4 +1,3 @@
export * from './useGetMCPTools';
export * from './useMCPConnectionStatus';
export * from './useMCPSelect';
export * from './useVisibleTools';

View file

@ -1,48 +0,0 @@
import { useMemo } from 'react';
import { Constants } from 'librechat-data-provider';
import type { TPlugin } from 'librechat-data-provider';
import { useMCPToolsQuery, useGetStartupConfig } from '~/data-provider';
/**
* Hook for fetching and filtering MCP tools based on server configuration
* Uses the dedicated MCP tools query instead of filtering from general tools
*/
export function useGetMCPTools() {
const { data: startupConfig } = useGetStartupConfig();
// Use dedicated MCP tools query
const { data: rawMcpTools } = useMCPToolsQuery({
select: (data: TPlugin[]) => {
// Group tools by server for easier management
const mcpToolsMap = new Map<string, TPlugin>();
data.forEach((tool) => {
const parts = tool.pluginKey.split(Constants.mcp_delimiter);
const serverName = parts[parts.length - 1];
if (!mcpToolsMap.has(serverName)) {
mcpToolsMap.set(serverName, {
name: serverName,
pluginKey: tool.pluginKey,
authConfig: tool.authConfig,
authenticated: tool.authenticated,
});
}
});
return Array.from(mcpToolsMap.values());
},
});
// Filter out servers that have chatMenu disabled
const mcpToolDetails = useMemo(() => {
if (!rawMcpTools || !startupConfig?.mcpServers) {
return rawMcpTools;
}
return rawMcpTools.filter((tool) => {
const serverConfig = startupConfig?.mcpServers?.[tool.name];
return serverConfig?.chatMenu !== false;
});
}, [rawMcpTools, startupConfig?.mcpServers]);
return {
mcpToolDetails,
};
}

View file

@ -7,9 +7,9 @@ import {
useUpdateUserPluginsMutation,
useReinitializeMCPServerMutation,
} from 'librechat-data-provider/react-query';
import type { TUpdateUserPlugins, TPlugin } from 'librechat-data-provider';
import type { TUpdateUserPlugins, TPlugin, MCPServersResponse } from 'librechat-data-provider';
import type { ConfigFieldDetail } from '~/common';
import { useLocalize, useMCPSelect, useGetMCPTools, useMCPConnectionStatus } from '~/hooks';
import { useLocalize, useMCPSelect, useMCPConnectionStatus } from '~/hooks';
import { useGetStartupConfig } from '~/data-provider';
interface ServerState {
@ -24,7 +24,6 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
const localize = useLocalize();
const queryClient = useQueryClient();
const { showToast } = useToastContext();
const { mcpToolDetails } = useGetMCPTools();
const { data: startupConfig } = useGetStartupConfig();
const { mcpValues, setMCPValues, isPinned, setIsPinned } = useMCPSelect({ conversationId });
@ -448,7 +447,10 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
const getServerStatusIconProps = useCallback(
(serverName: string) => {
const tool = mcpToolDetails?.find((t) => t.name === serverName);
const mcpData = queryClient.getQueryData<MCPServersResponse | undefined>([
QueryKeys.mcpTools,
]);
const serverData = mcpData?.servers?.[serverName];
const serverStatus = connectionStatus?.[serverName];
const serverConfig = startupConfig?.mcpServers?.[serverName];
@ -458,17 +460,20 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
previousFocusRef.current = document.activeElement as HTMLElement;
const configTool = tool || {
/** Minimal TPlugin object for the config dialog */
const configTool: TPlugin = {
name: serverName,
pluginKey: `${Constants.mcp_prefix}${serverName}`,
authConfig: serverConfig?.customUserVars
? Object.entries(serverConfig.customUserVars).map(([key, config]) => ({
authField: key,
label: config.title,
description: config.description,
}))
: [],
authenticated: false,
authConfig:
serverData?.authConfig ||
(serverConfig?.customUserVars
? Object.entries(serverConfig.customUserVars).map(([key, config]) => ({
authField: key,
label: config.title,
description: config.description,
}))
: []),
authenticated: serverData?.authenticated ?? false,
};
setSelectedToolForConfig(configTool);
setIsConfigModalOpen(true);
@ -486,7 +491,14 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
return {
serverName,
serverStatus,
tool,
tool: serverData
? ({
name: serverName,
pluginKey: `${Constants.mcp_prefix}${serverName}`,
icon: serverData.icon,
authenticated: serverData.authenticated,
} as TPlugin)
: undefined,
onConfigClick: handleConfigClick,
isInitializing: isInitializing(serverName),
canCancel: isCancellable(serverName),
@ -495,8 +507,8 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
};
},
[
queryClient,
isCancellable,
mcpToolDetails,
isInitializing,
cancelOAuthFlow,
connectionStatus,