import React, { useState, useCallback, useMemo, useEffect } from 'react'; import { ChevronLeft } from 'lucide-react'; import { Constants } from 'librechat-data-provider'; import { useForm, Controller } from 'react-hook-form'; import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; import type { TUpdateUserPlugins } from 'librechat-data-provider'; import { Button, Input, Label } from '~/components/ui'; import { useGetStartupConfig } from '~/data-provider'; import MCPPanelSkeleton from './MCPPanelSkeleton'; import { useToastContext } from '~/Providers'; import { useLocalize } from '~/hooks'; interface ServerConfigWithVars { serverName: string; config: { customUserVars: Record; }; } export default function MCPPanel() { const localize = useLocalize(); const { showToast } = useToastContext(); const { data: startupConfig, isLoading: startupConfigLoading } = useGetStartupConfig(); const [selectedServerNameForEditing, setSelectedServerNameForEditing] = useState( null, ); const mcpServerDefinitions = useMemo(() => { if (!startupConfig?.mcpServers) { return []; } return Object.entries(startupConfig.mcpServers) .filter( ([, serverConfig]) => serverConfig.customUserVars && Object.keys(serverConfig.customUserVars).length > 0, ) .map(([serverName, config]) => ({ serverName, iconPath: null, config: { ...config, customUserVars: config.customUserVars ?? {}, }, })); }, [startupConfig?.mcpServers]); const updateUserPluginsMutation = useUpdateUserPluginsMutation({ onSuccess: () => { showToast({ message: localize('com_nav_mcp_vars_updated'), status: 'success' }); }, onError: (error) => { console.error('Error updating MCP custom user variables:', error); showToast({ message: localize('com_nav_mcp_vars_update_error'), status: 'error', }); }, }); const handleSaveServerVars = useCallback( (serverName: string, updatedValues: Record) => { const payload: TUpdateUserPlugins = { pluginKey: `${Constants.mcp_prefix}${serverName}`, action: 'install', // 'install' action is used to set/update credentials/variables auth: updatedValues, }; updateUserPluginsMutation.mutate(payload); }, [updateUserPluginsMutation], ); const handleRevokeServerVars = useCallback( (serverName: string) => { const payload: TUpdateUserPlugins = { pluginKey: `${Constants.mcp_prefix}${serverName}`, action: 'uninstall', // 'uninstall' action clears the variables auth: {}, // Empty auth for uninstall }; updateUserPluginsMutation.mutate(payload); }, [updateUserPluginsMutation], ); const handleServerClickToEdit = (serverName: string) => { setSelectedServerNameForEditing(serverName); }; const handleGoBackToList = () => { setSelectedServerNameForEditing(null); }; if (startupConfigLoading) { return ; } if (mcpServerDefinitions.length === 0) { return (
{localize('com_sidepanel_mcp_no_servers_with_vars')}
); } if (selectedServerNameForEditing) { // Editing View const serverBeingEdited = mcpServerDefinitions.find( (s) => s.serverName === selectedServerNameForEditing, ); if (!serverBeingEdited) { // Fallback to list view if server not found setSelectedServerNameForEditing(null); return (
{localize('com_ui_error')}: {localize('com_ui_mcp_server_not_found')}
); } return (

{localize('com_sidepanel_mcp_variables_for', { '0': serverBeingEdited.serverName })}

); } else { // Server List View return (
{mcpServerDefinitions.map((server) => ( ))}
); } } // Inner component for the form - remains the same interface MCPVariableEditorProps { server: ServerConfigWithVars; onSave: (serverName: string, updatedValues: Record) => void; onRevoke: (serverName: string) => void; isSubmitting: boolean; } function MCPVariableEditor({ server, onSave, onRevoke, isSubmitting }: MCPVariableEditorProps) { const localize = useLocalize(); const { control, handleSubmit, reset, formState: { errors, isDirty }, } = useForm>({ defaultValues: {}, // Initialize empty, will be reset by useEffect }); useEffect(() => { // Always initialize with empty strings based on the schema const initialFormValues = Object.keys(server.config.customUserVars).reduce( (acc, key) => { acc[key] = ''; return acc; }, {} as Record, ); reset(initialFormValues); }, [reset, server.config.customUserVars]); const onFormSubmit = (data: Record) => { onSave(server.serverName, data); }; const handleRevokeClick = () => { onRevoke(server.serverName); }; return (
{Object.entries(server.config.customUserVars).map(([key, details]) => (
( )} /> {details.description && (

)} {errors[key] &&

{errors[key]?.message}

}
))}
{Object.keys(server.config.customUserVars).length > 0 && ( )}
); }