import { createFileRoute } from '@tanstack/react-router' import { route } from '@/constants/routes' import HeaderPage from '@/containers/HeaderPage' import SettingsMenu from '@/containers/SettingsMenu' import { Card, CardItem } from '@/containers/Card' import { IconPencil, IconPlus, IconTrash, IconCodeCircle, } from '@tabler/icons-react' import { useMCPServers, MCPServerConfig } from '@/hooks/useMCPServers' import { useEffect, useState } from 'react' import AddEditMCPServer from '@/containers/dialogs/AddEditMCPServer' import DeleteMCPServerConfirm from '@/containers/dialogs/DeleteMCPServerConfirm' import EditJsonMCPserver from '@/containers/dialogs/EditJsonMCPserver' import { Switch } from '@/components/ui/switch' import { twMerge } from 'tailwind-merge' import { getConnectedServers } from '@/services/mcp' import { useToolApproval } from '@/hooks/useToolApproval' import { toast } from 'sonner' import { invoke } from '@tauri-apps/api/core' import { useTranslation } from '@/i18n/react-i18next-compat' // Function to mask sensitive values const maskSensitiveValue = (value: string) => { if (!value) return value if (value.length <= 8) return '*'.repeat(value.length) return value.slice(0, 4) + '*'.repeat(value.length - 8) + value.slice(-4) } // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.settings.mcp_servers as any)({ component: MCPServers, }) function MCPServers() { const { t } = useTranslation() const { mcpServers, addServer, editServer, deleteServer, syncServers, syncServersAndRestart, getServerConfig, } = useMCPServers() const { allowAllMCPPermissions, setAllowAllMCPPermissions } = useToolApproval() const [open, setOpen] = useState(false) const [editingKey, setEditingKey] = useState(null) const [currentConfig, setCurrentConfig] = useState< MCPServerConfig | undefined >(undefined) // Delete confirmation dialog state const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [serverToDelete, setServerToDelete] = useState(null) // JSON editor dialog state const [jsonEditorOpen, setJsonEditorOpen] = useState(false) const [jsonServerName, setJsonServerName] = useState(null) const [jsonEditorData, setJsonEditorData] = useState< MCPServerConfig | Record | undefined >(undefined) const [connectedServers, setConnectedServers] = useState([]) const [loadingServers, setLoadingServers] = useState<{ [key: string]: boolean }>({}) const handleOpenDialog = (serverKey?: string) => { if (serverKey) { // Edit mode setCurrentConfig(mcpServers[serverKey]) setEditingKey(serverKey) } else { // Add mode setCurrentConfig(undefined) setEditingKey(null) } setOpen(true) } const handleSaveServer = async (name: string, config: MCPServerConfig) => { try { await toggleServer(name, false) } catch (error) { console.error('Error deactivating server:', error) } if (editingKey) { // If server name changed, delete old one and add new one if (editingKey !== name) { deleteServer(editingKey) addServer(name, config) } else { editServer(editingKey, config) } } else { // Add new server addServer(name, config) } syncServers() await toggleServer(name, true) } const handleEdit = (serverKey: string) => { handleOpenDialog(serverKey) } const handleDeleteClick = (serverKey: string) => { setServerToDelete(serverKey) setDeleteDialogOpen(true) } const handleConfirmDelete = () => { if (serverToDelete) { deleteServer(serverToDelete) setServerToDelete(null) syncServersAndRestart() } } const handleOpenJsonEditor = async (serverKey?: string) => { if (serverKey) { // Edit single server JSON setJsonServerName(serverKey) setJsonEditorData(mcpServers[serverKey]) } else { // Edit all servers JSON setJsonServerName(null) setJsonEditorData(mcpServers) } setJsonEditorOpen(true) } const handleSaveJson = async ( data: MCPServerConfig | Record ) => { if (jsonServerName) { try { await toggleServer(jsonServerName, false) } catch (error) { console.error('Error deactivating server:', error) } // Save single server editServer(jsonServerName, data as MCPServerConfig) syncServers() toggleServer(jsonServerName, true) } else { // Save all servers // Clear existing servers first Object.keys(mcpServers).forEach((key) => { deleteServer(key) }) // Add all servers from the JSON Object.entries(data as Record).forEach( ([key, config]) => { addServer(key, config) } ) } } const toggleServer = (serverKey: string, active: boolean) => { if (serverKey) { setLoadingServers((prev) => ({ ...prev, [serverKey]: true })) const config = getServerConfig(serverKey) if (active && config) { invoke('activate_mcp_server', { name: serverKey, config: { ...(config ?? (mcpServers[serverKey] as MCPServerConfig)), active, }, }) .then(() => { // Save single server editServer(serverKey, { ...(config ?? (mcpServers[serverKey] as MCPServerConfig)), active, }) syncServers() toast.success( active ? t('mcp-servers:serverStatusActive', { serverKey }) : t('mcp-servers:serverStatusInactive', { serverKey }) ) getConnectedServers().then(setConnectedServers) }) .catch((error) => { editServer(serverKey, { ...(config ?? (mcpServers[serverKey] as MCPServerConfig)), active: false, }) toast.error(error, { description: t('mcp-servers:checkParams'), }) }) .finally(() => { setLoadingServers((prev) => ({ ...prev, [serverKey]: false })) }) } else { editServer(serverKey, { ...(config ?? (mcpServers[serverKey] as MCPServerConfig)), active, }) syncServers() invoke('deactivate_mcp_server', { name: serverKey }).finally(() => { getConnectedServers().then(setConnectedServers) setLoadingServers((prev) => ({ ...prev, [serverKey]: false })) }) } } } useEffect(() => { getConnectedServers().then(setConnectedServers) const intervalId = setInterval(() => { getConnectedServers().then(setConnectedServers) }, 3000) return () => clearInterval(intervalId) }, [setConnectedServers]) return (

{t('common:settings')}

{t('mcp-servers:title')}

{t('mcp-servers:experimental')}
handleOpenJsonEditor()} title={t('mcp-servers:editAllJson')} >
handleOpenDialog()} title={t('mcp-servers:addServer')} >

{t('mcp-servers:findMore')}{' '} mcp.so

} >
} /> {Object.keys(mcpServers).length === 0 ? (
{t('mcp-servers:noServers')}
) : ( Object.entries(mcpServers).map(([key, config], index) => (

{key}

} descriptionOutside={
{t('mcp-servers:command')}: {config.command}
{t('mcp-servers:args')}: {config?.args?.join(', ')}
{config.env && Object.keys(config.env).length > 0 && (
{t('mcp-servers:env')}:{' '} {Object.entries(config.env) .map( ([key, value]) => `${key}=${maskSensitiveValue(value)}` ) .join(', ')}
)}
} actions={
handleOpenJsonEditor(key)} title={t('mcp-servers:editJson')} >
handleEdit(key)} title={t('mcp-servers:editServer')} >
handleDeleteClick(key)} title={t('mcp-servers:deleteServer')} >
toggleServer(key, checked) } />
} />
)) )}
{/* Use the AddEditMCPServer component */} {/* Delete confirmation dialog */} {/* JSON editor dialog */} } onSave={handleSaveJson} /> ) }