👻 refactor: LocalStorage Cleanup and MCP State Optimization (#9528)

* 👻 refactor: MCP Select State with Jotai Atoms

* refactor: Implement timestamp management for ChatArea localStorage entries

* refactor: Integrate MCP Server Manager into BadgeRow context and components to avoid double-calling within BadgeRow

* refactor: add try/catch

* chore: remove comment
This commit is contained in:
Danny Avila 2025-09-09 17:32:10 -04:00 committed by GitHub
parent 519645c0b0
commit 751c2e1d17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 435 additions and 115 deletions

View file

@ -1,66 +1,50 @@
import { useRef, useCallback, useMemo } from 'react';
import { useCallback, useEffect } from 'react';
import { useAtom } from 'jotai';
import { useRecoilState } from 'recoil';
import { Constants, LocalStorageKeys } from 'librechat-data-provider';
import useLocalStorage from '~/hooks/useLocalStorageAlt';
import { ephemeralAgentByConvoId } from '~/store';
const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
if (rawCurrentValue) {
try {
const currentValue = rawCurrentValue?.trim() ?? '';
if (currentValue.length > 2) {
return true;
}
} catch (e) {
console.error(e);
}
}
return Array.isArray(value) && value.length > 0;
};
import { ephemeralAgentByConvoId, mcpValuesAtomFamily, mcpPinnedAtom } from '~/store';
import { setTimestamp } from '~/utils/timestamps';
export function useMCPSelect({ conversationId }: { conversationId?: string | null }) {
const key = conversationId ?? Constants.NEW_CONVO;
const [isPinned, setIsPinned] = useAtom(mcpPinnedAtom);
const [mcpValues, setMCPValuesRaw] = useAtom(mcpValuesAtomFamily(key));
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key));
const storageKey = `${LocalStorageKeys.LAST_MCP_}${key}`;
const mcpState = useMemo(() => {
return ephemeralAgent?.mcp ?? [];
}, [ephemeralAgent?.mcp]);
// Sync Jotai state with ephemeral agent state
useEffect(() => {
if (ephemeralAgent?.mcp && ephemeralAgent.mcp.length > 0) {
setMCPValuesRaw(ephemeralAgent.mcp);
}
}, [ephemeralAgent?.mcp, setMCPValuesRaw]);
const setSelectedValues = useCallback(
(values: string[] | null | undefined) => {
if (!values) {
return;
}
if (!Array.isArray(values)) {
return;
}
// Update ephemeral agent when Jotai state changes
useEffect(() => {
if (mcpValues.length > 0 && JSON.stringify(mcpValues) !== JSON.stringify(ephemeralAgent?.mcp)) {
setEphemeralAgent((prev) => ({
...prev,
mcp: values,
mcp: mcpValues,
}));
}
}, [mcpValues, ephemeralAgent?.mcp, setEphemeralAgent]);
useEffect(() => {
const mcpStorageKey = `${LocalStorageKeys.LAST_MCP_}${key}`;
if (mcpValues.length > 0) {
setTimestamp(mcpStorageKey);
}
}, [mcpValues, key]);
/** Stable memoized setter */
const setMCPValues = useCallback(
(value: string[]) => {
if (!Array.isArray(value)) {
return;
}
setMCPValuesRaw(value);
},
[setEphemeralAgent],
);
const [mcpValues, setMCPValuesRaw] = useLocalStorage<string[]>(
storageKey,
mcpState,
setSelectedValues,
storageCondition,
);
const setMCPValuesRawRef = useRef(setMCPValuesRaw);
setMCPValuesRawRef.current = setMCPValuesRaw;
/** Create a stable memoized setter to avoid re-creating it on every render and causing an infinite render loop */
const setMCPValues = useCallback((value: string[]) => {
setMCPValuesRawRef.current(value);
}, []);
const [isPinned, setIsPinned] = useLocalStorage<boolean>(
`${LocalStorageKeys.PIN_MCP_}${key}`,
true,
[setMCPValuesRaw],
);
return {

View file

@ -25,9 +25,8 @@ export function useMCPServerManager({ conversationId }: { conversationId?: strin
const queryClient = useQueryClient();
const { showToast } = useToastContext();
const { mcpToolDetails } = useGetMCPTools();
const mcpSelect = useMCPSelect({ conversationId });
const { data: startupConfig } = useGetStartupConfig();
const { mcpValues, setMCPValues, isPinned, setIsPinned } = mcpSelect;
const { mcpValues, setMCPValues, isPinned, setIsPinned } = useMCPSelect({ conversationId });
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
const [selectedToolForConfig, setSelectedToolForConfig] = useState<TPlugin | null>(null);