🔧 fix: MCP Selection Persist and UI Flicker Issues (#9324)

* refactor: useMCPSelect

    - Add useGetMCPTools to use in useMCPSelect and elsewhere hooks for fetching MCP tools
    - remove memoized key
    - remove use of `useChatContext` and require conversationId as prop

* feat: Add MCPPanelContext and integrate conversationId as prop for useMCPSelect across components

- Introduced MCPPanelContext to manage conversationId state.
- Updated MCPSelect, MCPSubMenu, and MCPConfigDialog to accept conversationId as a prop.
- Modified ToolsDropdown and BadgeRow to pass conversationId to relevant components.
- Refactored MCPPanel to utilize MCPPanelProvider for context management.

* fix: remove nested ternary in ServerInitializationSection

- Replaced conditional operator with if-else statements for better readability in determining button text based on server initialization state and reinitialization status.

* refactor: wrap setValueWrap in useCallback for performance optimization

* refactor: streamline useMCPSelect by consolidating storageKey definition

* fix: prevent clearing selections on page refresh by tracking initial load completion

* refactor: simplify concern of useMCPSelect hook

* refactor: move ConfigFieldDetail interface to common types for better reusability, isolate usage of `useGetMCPTools`

* refactor: integrate mcpServerNames into BadgeRowContext and update ToolsDropdown and MCPSelect components
This commit is contained in:
Danny Avila 2025-08-28 00:44:49 -04:00 committed by GitHub
parent 2483623c88
commit c0511b9a5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 270 additions and 206 deletions

View file

@ -1,12 +1,19 @@
import React, { createContext, useContext, useEffect, useRef } from 'react';
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
import { useSetRecoilState } from 'recoil';
import { Tools, Constants, LocalStorageKeys, AgentCapabilities } from 'librechat-data-provider';
import type { TAgentsEndpoint } from 'librechat-data-provider';
import { useSearchApiKeyForm, useGetAgentsConfig, useCodeApiKeyForm, useToolToggle } from '~/hooks';
import {
useSearchApiKeyForm,
useGetAgentsConfig,
useCodeApiKeyForm,
useGetMCPTools,
useToolToggle,
} from '~/hooks';
import { ephemeralAgentByConvoId } from '~/store';
interface BadgeRowContextType {
conversationId?: string | null;
mcpServerNames?: string[] | null;
agentsConfig?: TAgentsEndpoint | null;
webSearch: ReturnType<typeof useToolToggle>;
artifacts: ReturnType<typeof useToolToggle>;
@ -37,10 +44,12 @@ export default function BadgeRowProvider({
isSubmitting,
conversationId,
}: BadgeRowProviderProps) {
const hasInitializedRef = useRef(false);
const lastKeyRef = useRef<string>('');
const hasInitializedRef = useRef(false);
const { mcpToolDetails } = useGetMCPTools();
const { agentsConfig } = useGetAgentsConfig();
const key = conversationId ?? Constants.NEW_CONVO;
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(key));
/** Initialize ephemeralAgent from localStorage on mount and when conversation changes */
@ -156,11 +165,16 @@ export default function BadgeRowProvider({
isAuthenticated: true,
});
const mcpServerNames = useMemo(() => {
return (mcpToolDetails ?? []).map((tool) => tool.name);
}, [mcpToolDetails]);
const value: BadgeRowContextType = {
webSearch,
artifacts,
fileSearch,
agentsConfig,
mcpServerNames,
conversationId,
codeApiKeyForm,
codeInterpreter,

View file

@ -0,0 +1,31 @@
import React, { createContext, useContext, useMemo } from 'react';
import { Constants } from 'librechat-data-provider';
import { useChatContext } from './ChatContext';
interface MCPPanelContextValue {
conversationId: string;
}
const MCPPanelContext = createContext<MCPPanelContextValue | undefined>(undefined);
export function MCPPanelProvider({ children }: { children: React.ReactNode }) {
const { conversation } = useChatContext();
/** Context value only created when conversationId changes */
const contextValue = useMemo<MCPPanelContextValue>(
() => ({
conversationId: conversation?.conversationId ?? Constants.NEW_CONVO,
}),
[conversation?.conversationId],
);
return <MCPPanelContext.Provider value={contextValue}>{children}</MCPPanelContext.Provider>;
}
export function useMCPPanelContext() {
const context = useContext(MCPPanelContext);
if (!context) {
throw new Error('useMCPPanelContext must be used within MCPPanelProvider');
}
return context;
}

View file

@ -23,6 +23,7 @@ export * from './SetConvoContext';
export * from './SearchContext';
export * from './BadgeRowContext';
export * from './SidePanelContext';
export * from './MCPPanelContext';
export * from './ArtifactsContext';
export * from './PromptGroupsContext';
export { default as BadgeRowProvider } from './BadgeRowContext';