mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-23 19:04:10 +01:00
make MCPFormPanel agnostic to Agent / Chat context
This commit is contained in:
parent
cf91dc3aad
commit
389ab1db77
6 changed files with 233 additions and 152 deletions
112
client/src/components/SidePanel/Agents/AgentMCPFormPanel.tsx
Normal file
112
client/src/components/SidePanel/Agents/AgentMCPFormPanel.tsx
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
|
||||||
|
import { useToastContext } from '~/Providers';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
import { Panel } from '~/common';
|
||||||
|
import type { MCP } from '~/common';
|
||||||
|
import MCPFormPanel from '../MCP/MCPFormPanel';
|
||||||
|
|
||||||
|
// TODO: Add MCP delete (for now mocked for ui)
|
||||||
|
// import { useDeleteAgentMCP } from '~/data-provider';
|
||||||
|
|
||||||
|
function useDeleteAgentMCP({
|
||||||
|
onSuccess,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
onSuccess: () => void;
|
||||||
|
onError: (error: Error) => void;
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
mutate: async ({ mcp_id, agent_id }: { mcp_id: string; agent_id: string }) => {
|
||||||
|
try {
|
||||||
|
console.log('Mock delete MCP:', { mcp_id, agent_id });
|
||||||
|
onSuccess();
|
||||||
|
} catch (error) {
|
||||||
|
onError(error as Error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function useUpdateAgentMCP({
|
||||||
|
onSuccess,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
onSuccess: (mcp: MCP) => void;
|
||||||
|
onError: (error: Error) => void;
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
mutate: async (mcp: MCP) => {
|
||||||
|
try {
|
||||||
|
// TODO: Implement MCP endpoint
|
||||||
|
onSuccess(mcp);
|
||||||
|
} catch (error) {
|
||||||
|
onError(error as Error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AgentMCPFormPanel() {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { showToast } = useToastContext();
|
||||||
|
const { mcp, setMcp, agent_id, setActivePanel } = useAgentPanelContext();
|
||||||
|
|
||||||
|
const updateAgentMCP = useUpdateAgentMCP({
|
||||||
|
onSuccess(mcp) {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_update_mcp_success'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
setMcp(mcp);
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
showToast({
|
||||||
|
message: (error as Error).message || localize('com_ui_update_mcp_error'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteAgentMCP = useDeleteAgentMCP({
|
||||||
|
onSuccess: () => {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_delete_mcp_success'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
setActivePanel(Panel.builder);
|
||||||
|
setMcp(undefined);
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
showToast({
|
||||||
|
message: (error as Error).message ?? localize('com_ui_delete_mcp_error'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
setActivePanel(Panel.builder);
|
||||||
|
setMcp(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = (mcp: MCP) => {
|
||||||
|
updateAgentMCP.mutate(mcp);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (mcp_id: string, contextId: string) => {
|
||||||
|
deleteAgentMCP.mutate({ mcp_id, agent_id: contextId });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MCPFormPanel
|
||||||
|
mcp={mcp}
|
||||||
|
contextId={agent_id}
|
||||||
|
onBack={handleBack}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
onSave={handleSave}
|
||||||
|
showDeleteButton={!!mcp}
|
||||||
|
isDeleteDisabled={!agent_id || !mcp?.mcp_id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import VersionPanel from './Version/VersionPanel';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import ActionsPanel from './ActionsPanel';
|
import ActionsPanel from './ActionsPanel';
|
||||||
import AgentPanel from './AgentPanel';
|
import AgentPanel from './AgentPanel';
|
||||||
import MCPPanel from './MCPPanel';
|
import AgentMCPFormPanel from './AgentMCPFormPanel';
|
||||||
import { Panel } from '~/common';
|
import { Panel } from '~/common';
|
||||||
|
|
||||||
export default function AgentPanelSwitch() {
|
export default function AgentPanelSwitch() {
|
||||||
|
|
@ -55,7 +55,7 @@ function AgentPanelSwitchWithContext() {
|
||||||
return <VersionPanel />;
|
return <VersionPanel />;
|
||||||
}
|
}
|
||||||
if (activePanel === Panel.mcp) {
|
if (activePanel === Panel.mcp) {
|
||||||
return <MCPPanel />;
|
return <AgentMCPFormPanel />;
|
||||||
}
|
}
|
||||||
return <AgentPanel agentsConfig={agentsConfig} endpointsConfig={endpointsConfig} />;
|
return <AgentPanel agentsConfig={agentsConfig} endpointsConfig={endpointsConfig} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,57 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { ChevronLeft } from 'lucide-react';
|
import { ChevronLeft } from 'lucide-react';
|
||||||
import { useForm, FormProvider } from 'react-hook-form';
|
import { useForm, FormProvider } from 'react-hook-form';
|
||||||
import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
|
|
||||||
import { OGDialog, OGDialogTrigger, Label } from '~/components/ui';
|
import { OGDialog, OGDialogTrigger, Label } from '~/components/ui';
|
||||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { defaultMCPFormValues } from '~/common/mcp';
|
import { defaultMCPFormValues } from '~/common/mcp';
|
||||||
import useLocalize from '~/hooks/useLocalize';
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
import { useToastContext } from '~/Providers';
|
|
||||||
import { TrashIcon } from '~/components/svg';
|
import { TrashIcon } from '~/components/svg';
|
||||||
import type { MCPForm } from '~/common';
|
import type { MCPForm, MCP } from '~/common';
|
||||||
import MCPInput from './MCPInput';
|
import MCPInput from './MCPInput';
|
||||||
import { Panel } from '~/common';
|
|
||||||
import {
|
import {
|
||||||
AuthTypeEnum,
|
AuthTypeEnum,
|
||||||
AuthorizationTypeEnum,
|
AuthorizationTypeEnum,
|
||||||
TokenExchangeMethodEnum,
|
TokenExchangeMethodEnum,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
// TODO: Add MCP delete (for now mocked for ui)
|
|
||||||
// import { useDeleteAgentMCP } from '~/data-provider';
|
|
||||||
|
|
||||||
function useDeleteAgentMCP({
|
interface MCPFormPanelProps {
|
||||||
onSuccess,
|
// Data
|
||||||
onError,
|
mcp?: MCP;
|
||||||
}: {
|
agent_id?: string; // agent_id, conversation_id, etc.
|
||||||
onSuccess: () => void;
|
|
||||||
onError: (error: Error) => void;
|
// Actions
|
||||||
}) {
|
onBack: () => void;
|
||||||
return {
|
onDelete?: (mcp_id: string, agent_id: string) => void;
|
||||||
mutate: async ({ mcp_id, agent_id }: { mcp_id: string; agent_id: string }) => {
|
onSave: (mcp: MCP) => void;
|
||||||
try {
|
|
||||||
console.log('Mock delete MCP:', { mcp_id, agent_id });
|
// UI customization
|
||||||
onSuccess();
|
title?: string;
|
||||||
} catch (error) {
|
subtitle?: string;
|
||||||
onError(error as Error);
|
showDeleteButton?: boolean;
|
||||||
}
|
isDeleteDisabled?: boolean;
|
||||||
},
|
deleteConfirmMessage?: string;
|
||||||
};
|
|
||||||
|
// Form customization
|
||||||
|
defaultValues?: Partial<MCPForm>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MCPPanel() {
|
export default function MCPFormPanel({
|
||||||
|
mcp,
|
||||||
|
agent_id,
|
||||||
|
onBack,
|
||||||
|
onDelete,
|
||||||
|
onSave,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
showDeleteButton = true,
|
||||||
|
isDeleteDisabled = false,
|
||||||
|
deleteConfirmMessage,
|
||||||
|
defaultValues = defaultMCPFormValues,
|
||||||
|
}: MCPFormPanelProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
|
||||||
const { mcp, setMcp, agent_id, setActivePanel } = useAgentPanelContext();
|
|
||||||
const deleteAgentMCP = useDeleteAgentMCP({
|
|
||||||
onSuccess: () => {
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_delete_mcp_success'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
setActivePanel(Panel.builder);
|
|
||||||
setMcp(undefined);
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
showToast({
|
|
||||||
message: (error as Error).message ?? localize('com_ui_delete_mcp_error'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const methods = useForm<MCPForm>({
|
const methods = useForm<MCPForm>({
|
||||||
defaultValues: defaultMCPFormValues,
|
defaultValues: defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { reset } = methods;
|
const { reset } = methods;
|
||||||
|
|
@ -96,33 +87,32 @@ export default function MCPPanel() {
|
||||||
}
|
}
|
||||||
}, [mcp, reset]);
|
}, [mcp, reset]);
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (onDelete && mcp?.mcp_id && agent_id) {
|
||||||
|
onDelete(mcp.mcp_id, agent_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form className="h-full grow overflow-hidden">
|
<form className="h-full grow overflow-hidden">
|
||||||
<div className="h-full overflow-auto px-2 pb-12 text-sm">
|
<div className="h-full overflow-auto px-2 pb-12 text-sm">
|
||||||
<div className="relative flex flex-col items-center px-16 py-6 text-center">
|
<div className="relative flex flex-col items-center px-16 py-6 text-center">
|
||||||
<div className="absolute left-0 top-6">
|
<div className="absolute left-0 top-6">
|
||||||
<button
|
<button type="button" className="btn btn-neutral relative" onClick={onBack}>
|
||||||
type="button"
|
|
||||||
className="btn btn-neutral relative"
|
|
||||||
onClick={() => {
|
|
||||||
setActivePanel(Panel.builder);
|
|
||||||
setMcp(undefined);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex w-full items-center justify-center gap-2">
|
<div className="flex w-full items-center justify-center gap-2">
|
||||||
<ChevronLeft />
|
<ChevronLeft />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!!mcp && (
|
{!!mcp && showDeleteButton && onDelete && (
|
||||||
<OGDialog>
|
<OGDialog>
|
||||||
<OGDialogTrigger asChild>
|
<OGDialogTrigger asChild>
|
||||||
<div className="absolute right-0 top-6">
|
<div className="absolute right-0 top-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!agent_id || !mcp.mcp_id}
|
disabled={isDeleteDisabled || !mcp.mcp_id || !agent_id}
|
||||||
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
|
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
|
||||||
>
|
>
|
||||||
<TrashIcon className="text-red-500" />
|
<TrashIcon className="text-red-500" />
|
||||||
|
|
@ -135,22 +125,11 @@ export default function MCPPanel() {
|
||||||
className="max-w-[450px]"
|
className="max-w-[450px]"
|
||||||
main={
|
main={
|
||||||
<Label className="text-left text-sm font-medium">
|
<Label className="text-left text-sm font-medium">
|
||||||
{localize('com_ui_delete_mcp_confirm')}
|
{deleteConfirmMessage || localize('com_ui_delete_mcp_confirm')}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
selection={{
|
selection={{
|
||||||
selectHandler: () => {
|
selectHandler: handleDelete,
|
||||||
if (!agent_id) {
|
|
||||||
return showToast({
|
|
||||||
message: localize('com_agents_no_agent_id_error'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
deleteAgentMCP.mutate({
|
|
||||||
mcp_id: mcp.mcp_id,
|
|
||||||
agent_id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
selectClasses:
|
selectClasses:
|
||||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||||
selectText: localize('com_ui_delete'),
|
selectText: localize('com_ui_delete'),
|
||||||
|
|
@ -160,11 +139,14 @@ export default function MCPPanel() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="text-xl font-medium">
|
<div className="text-xl font-medium">
|
||||||
{mcp ? localize('com_ui_edit_mcp_server') : localize('com_ui_add_mcp_server')}
|
{title ||
|
||||||
|
(mcp ? localize('com_ui_edit_mcp_server') : localize('com_ui_add_mcp_server'))}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-text-secondary">
|
||||||
|
{subtitle || localize('com_agents_mcp_info')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-text-secondary">{localize('com_agents_mcp_info')}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<MCPInput mcp={mcp} agent_id={agent_id} setMCP={setMcp} />
|
<MCPInput mcp={mcp} agent_id={agent_id} onSave={onSave} />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
@ -5,54 +5,24 @@ import MCPAuth from '~/components/SidePanel/Builder/MCPAuth';
|
||||||
import MCPIcon from '~/components/SidePanel/Agents/MCPIcon';
|
import MCPIcon from '~/components/SidePanel/Agents/MCPIcon';
|
||||||
import { Label, Checkbox } from '~/components/ui';
|
import { Label, Checkbox } from '~/components/ui';
|
||||||
import useLocalize from '~/hooks/useLocalize';
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
import { useToastContext } from '~/Providers';
|
|
||||||
import { Spinner } from '~/components/svg';
|
import { Spinner } from '~/components/svg';
|
||||||
import { MCPForm } from '~/common/types';
|
import { MCPForm } from '~/common/types';
|
||||||
|
|
||||||
function useUpdateAgentMCP({
|
|
||||||
onSuccess,
|
|
||||||
onError,
|
|
||||||
}: {
|
|
||||||
onSuccess: (data: [string, MCP]) => void;
|
|
||||||
onError: (error: Error) => void;
|
|
||||||
}) {
|
|
||||||
return {
|
|
||||||
mutate: async ({
|
|
||||||
mcp_id,
|
|
||||||
metadata,
|
|
||||||
agent_id,
|
|
||||||
}: {
|
|
||||||
mcp_id?: string;
|
|
||||||
metadata: MCP['metadata'];
|
|
||||||
agent_id: string;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
// TODO: Implement MCP endpoint
|
|
||||||
onSuccess(['success', { mcp_id, metadata, agent_id } as MCP]);
|
|
||||||
} catch (error) {
|
|
||||||
onError(error as Error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isLoading: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MCPInputProps {
|
interface MCPInputProps {
|
||||||
mcp?: MCP;
|
mcp?: MCP;
|
||||||
agent_id?: string;
|
agent_id?: string;
|
||||||
setMCP: React.Dispatch<React.SetStateAction<MCP | undefined>>;
|
onSave: (mcp: MCP) => void;
|
||||||
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MCPInput({ mcp, agent_id, setMCP }: MCPInputProps) {
|
export default function MCPInput({ mcp, a, onSave, isLoading = false }: MCPInputProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
register,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
control,
|
control,
|
||||||
} = useFormContext<MCPForm>();
|
} = useFormContext<MCPForm>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [showTools, setShowTools] = useState(false);
|
const [showTools, setShowTools] = useState(false);
|
||||||
const [selectedTools, setSelectedTools] = useState<string[]>([]);
|
const [selectedTools, setSelectedTools] = useState<string[]>([]);
|
||||||
|
|
||||||
|
|
@ -64,50 +34,16 @@ export default function MCPInput({ mcp, agent_id, setMCP }: MCPInputProps) {
|
||||||
}
|
}
|
||||||
}, [mcp]);
|
}, [mcp]);
|
||||||
|
|
||||||
const updateAgentMCP = useUpdateAgentMCP({
|
|
||||||
onSuccess(data) {
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_update_mcp_success'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
setMCP(data[1]);
|
|
||||||
setShowTools(true);
|
|
||||||
setSelectedTools(data[1].metadata.tools ?? []);
|
|
||||||
setIsLoading(false);
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
showToast({
|
|
||||||
message: (error as Error).message || localize('com_ui_update_mcp_error'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
setIsLoading(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const saveMCP = handleSubmit(async (data: MCPForm) => {
|
const saveMCP = handleSubmit(async (data: MCPForm) => {
|
||||||
setIsLoading(true);
|
const updatedMCP: MCP = {
|
||||||
try {
|
mcp_id: mcp?.mcp_id ?? '',
|
||||||
const response = await updateAgentMCP.mutate({
|
agent_id: a ?? '', // This will be agent_id, conversation_id, etc.
|
||||||
agent_id: agent_id ?? '',
|
metadata: {
|
||||||
mcp_id: mcp?.mcp_id,
|
...data,
|
||||||
metadata: {
|
tools: selectedTools,
|
||||||
...data,
|
},
|
||||||
tools: selectedTools,
|
};
|
||||||
},
|
onSave(updatedMCP);
|
||||||
});
|
|
||||||
setMCP(response[1]);
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_update_mcp_success'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
showToast({
|
|
||||||
message: localize('com_ui_update_mcp_error'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
const handleSelectAll = () => {
|
||||||
|
|
@ -140,14 +76,15 @@ export default function MCPInput({ mcp, agent_id, setMCP }: MCPInputProps) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
const base64String = reader.result as string;
|
const base64String = reader.result as string;
|
||||||
setMCP({
|
const updatedMCP: MCP = {
|
||||||
mcp_id: mcp?.mcp_id ?? '',
|
mcp_id: mcp?.mcp_id ?? '',
|
||||||
agent_id: agent_id ?? '',
|
agent_id: a ?? '',
|
||||||
metadata: {
|
metadata: {
|
||||||
...mcp?.metadata,
|
...mcp?.metadata,
|
||||||
icon: base64String,
|
icon: base64String,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
onSave(updatedMCP);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,8 @@ import { useGetStartupConfig } from '~/data-provider';
|
||||||
import MCPPanelSkeleton from './MCPPanelSkeleton';
|
import MCPPanelSkeleton from './MCPPanelSkeleton';
|
||||||
import { useToastContext } from '~/Providers';
|
import { useToastContext } from '~/Providers';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
import MCPFormPanel from './MCPFormPanel';
|
||||||
|
import type { MCP } from '~/common';
|
||||||
|
|
||||||
interface ServerConfigWithVars {
|
interface ServerConfigWithVars {
|
||||||
serverName: string;
|
serverName: string;
|
||||||
|
|
@ -24,6 +26,7 @@ export default function MCPPanel() {
|
||||||
const [selectedServerNameForEditing, setSelectedServerNameForEditing] = useState<string | null>(
|
const [selectedServerNameForEditing, setSelectedServerNameForEditing] = useState<string | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const [showMCPForm, setShowMCPForm] = useState(false);
|
||||||
|
|
||||||
const mcpServerDefinitions = useMemo(() => {
|
const mcpServerDefinitions = useMemo(() => {
|
||||||
if (!startupConfig?.mcpServers) {
|
if (!startupConfig?.mcpServers) {
|
||||||
|
|
@ -89,14 +92,54 @@ export default function MCPPanel() {
|
||||||
setSelectedServerNameForEditing(null);
|
setSelectedServerNameForEditing(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddMCP = () => {
|
||||||
|
setShowMCPForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackFromForm = () => {
|
||||||
|
setShowMCPForm(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveMCP = (mcp: MCP) => {
|
||||||
|
// TODO: Implement MCP save logic for conversation context
|
||||||
|
console.log('Saving MCP:', mcp);
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_update_mcp_success'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
setShowMCPForm(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (showMCPForm) {
|
||||||
|
return (
|
||||||
|
<MCPFormPanel
|
||||||
|
onBack={handleBackFromForm}
|
||||||
|
onSave={handleSaveMCP}
|
||||||
|
showDeleteButton={false}
|
||||||
|
title={localize('com_ui_add_mcp_server')}
|
||||||
|
subtitle={localize('com_agents_mcp_info_chat')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (startupConfigLoading) {
|
if (startupConfigLoading) {
|
||||||
return <MCPPanelSkeleton />;
|
return <MCPPanelSkeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mcpServerDefinitions.length === 0) {
|
if (mcpServerDefinitions.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-center text-sm text-gray-500">
|
<div className="h-auto max-w-full overflow-x-hidden p-3">
|
||||||
{localize('com_sidepanel_mcp_no_servers_with_vars')}
|
<div className="p-4 text-center text-sm text-gray-500">
|
||||||
|
{localize('com_sidepanel_mcp_no_servers_with_vars')}
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<Button
|
||||||
|
onClick={handleAddMCP}
|
||||||
|
className="w-full bg-green-500 text-white hover:bg-green-600"
|
||||||
|
>
|
||||||
|
{localize('com_ui_add_mcp')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -153,6 +196,12 @@ export default function MCPPanel() {
|
||||||
{server.serverName}
|
{server.serverName}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
<Button
|
||||||
|
onClick={handleAddMCP}
|
||||||
|
className="w-full bg-green-500 text-white hover:bg-green-600"
|
||||||
|
>
|
||||||
|
{localize('com_ui_add_mcp')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"com_agents_mcp_description_placeholder": "Explain what it does in a few words",
|
"com_agents_mcp_description_placeholder": "Explain what it does in a few words",
|
||||||
"com_agents_mcp_icon_size": "Minimum size 128 x 128 px",
|
"com_agents_mcp_icon_size": "Minimum size 128 x 128 px",
|
||||||
"com_agents_mcp_info": "Add MCP servers to your agent to enable it to perform tasks and interact with external services",
|
"com_agents_mcp_info": "Add MCP servers to your agent to enable it to perform tasks and interact with external services",
|
||||||
|
"com_agents_mcp_info_chat": "Add MCP servers to enable chat to perform tasks and interact with external services",
|
||||||
"com_agents_mcp_name_placeholder": "Custom Tool",
|
"com_agents_mcp_name_placeholder": "Custom Tool",
|
||||||
"com_agents_mcp_trust_subtext": "Custom connectors are not verified by LibreChat",
|
"com_agents_mcp_trust_subtext": "Custom connectors are not verified by LibreChat",
|
||||||
"com_agents_mcps_disabled": "You need to create an agent before adding MCPs.",
|
"com_agents_mcps_disabled": "You need to create an agent before adding MCPs.",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue