diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte index 021e0e453..1c6bb0c1c 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte @@ -299,7 +299,7 @@
- {#each toolsPanel.activeGroups as group (group.label)} + {#each toolsPanel.activeGroups as group (group.key)} {@const checked = toolsPanel.isGroupChecked(group)} {@const enabledCount = toolsPanel.getEnabledToolCount(group)} {@const favicon = toolsPanel.getFavicon(group)} @@ -307,7 +307,7 @@ {/each} diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte index 9ca9360c5..4473c29a3 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte @@ -64,14 +64,14 @@ {/if} {:else}
- {#each toolsPanel.activeGroups as group (group.label)} - {@const isExpanded = toolsPanel.expandedGroups.has(group.label)} + {#each toolsPanel.activeGroups as group (group.key)} + {@const isExpanded = toolsPanel.expandedGroups.has(group.key)} {@const checked = toolsPanel.isGroupChecked(group)} {@const favicon = toolsPanel.getFavicon(group)} toolsPanel.toggleGroupExpanded(group.label)} + onOpenChange={() => toolsPanel.toggleGroupExpanded(group.key)} >
toolsPanel.toggleGroupByLabel(group.label)} + onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)} class="mr-2 {ICON_CLASS_DEFAULT} shrink-0" /> {/snippet} diff --git a/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte b/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte index fa10d5d79..9ec57a558 100644 --- a/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte +++ b/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte @@ -15,6 +15,7 @@ REDACTED_HEADERS } from '$lib/constants'; import { browser } from '$app/environment'; + import { HealthCheckStatus } from '$lib/enums'; interface Props { open: boolean; @@ -24,6 +25,16 @@ let { open = $bindable(), onOpenChange }: Props = $props(); let newServerUrl = $state(''); + let newServerName = $state(''); + let nameAutoFilled = $state(''); + let nameTouched = $state(false); + + let previewRun = 0; + + function handleNameChange(value: string) { + newServerName = value; + nameTouched = true; + } let newServerHeaders = $state(''); let newServerUseProxy = $state(false); @@ -115,6 +126,48 @@ } }); + // Debounced preview handshake: once the URL is valid and stable, fetch the + // server-reported name to prefill the display name field. A manual edit + // freezes the autofill for good, and failures stay silent. + $effect(() => { + const url = newServerUrl.trim(); + const headers = newServerHeaders.trim(); + const useProxy = newServerUseProxy; + + if (!open || newServerUrlError || !url) return; + + const run = ++previewRun; + // One throwaway id per run: concurrent previews (URL typed, then the + // bearer token pasted) would poison each other's shared health state. + const previewId = `${MCP_SERVER_ID_PREFIX}-preview-${run}`; + const timer = setTimeout(async () => { + await mcpStore.runHealthCheck({ + id: previewId, + enabled: false, + url, + headers: headers || undefined, + useProxy + }); + + const state = mcpStore.getHealthCheckState(previewId); + + mcpStore.clearHealthCheck(previewId); + + if (run !== previewRun) return; + + if (state.status !== HealthCheckStatus.SUCCESS) return; + + const autoName = state.serverInfo?.title || state.serverInfo?.name || ''; + + if (autoName && !nameTouched) { + newServerName = autoName; + nameAutoFilled = autoName; + } + }, 600); + + return () => clearTimeout(timer); + }); + let hasSelection = $derived(selectedRecommendationId !== null); let unconfiguredRecommendations = $derived.by(() => { @@ -146,6 +199,10 @@ function handleOpenChange(value: boolean) { if (!value) { newServerUrl = ''; + newServerName = ''; + nameAutoFilled = ''; + nameTouched = false; + previewRun++; newServerHeaders = ''; newServerUseProxy = false; newServerWantsAuthorization = false; @@ -163,6 +220,12 @@ id: newServerId, enabled: true, url: newServerUrl.trim(), + // A name equal to the autofilled server-reported one is not a + // customization: keep following the automatic label. + displayName: + newServerName.trim() && newServerName.trim() !== nameAutoFilled.trim() + ? newServerName.trim() + : undefined, headers: newServerHeaders.trim() || undefined, useProxy: newServerUseProxy }); @@ -210,6 +273,8 @@
(newServerUrl = v)} diff --git a/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte b/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte index cf171dcc9..5d4c89209 100644 --- a/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte +++ b/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte @@ -70,7 +70,12 @@ async function startEditing() { isEditing = true; await tick(); - editFormRef?.setInitialValues(server.url, server.headers || '', server.useProxy || false); + editFormRef?.setInitialValues( + server.url, + server.headers || '', + server.useProxy || false, + displayName + ); } function cancelEditing() { @@ -81,9 +86,12 @@ } } - function saveEditing(url: string, headers: string, useProxy: boolean) { + function saveEditing(url: string, headers: string, useProxy: boolean, name?: string) { onUpdate({ url: url, + // undefined = prefill untouched, keep any existing custom name; + // empty string = field cleared, back to the automatic label + displayName: name === undefined ? server.displayName : name.trim() || undefined, headers: headers || undefined, useProxy: useProxy }); @@ -106,6 +114,7 @@ serverId={server.id} serverUrl={server.url} serverUseProxy={server.useProxy} + serverLabel={displayName} onSave={saveEditing} onCancel={cancelEditing} /> diff --git a/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCardEditForm.svelte b/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCardEditForm.svelte index 8ed4ee8b8..19778f95b 100644 --- a/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCardEditForm.svelte +++ b/tools/ui/src/lib/components/app/mcp/McpServerCard/McpServerCardEditForm.svelte @@ -7,13 +7,23 @@ serverId: string; serverUrl: string; serverUseProxy?: boolean; - onSave: (url: string, headers: string, useProxy: boolean) => void; + /** Current automatic label, prefilled so the user can customize it. */ + serverLabel?: string; + onSave: (url: string, headers: string, useProxy: boolean, name?: string) => void; onCancel: () => void; } - let { serverId, serverUrl, serverUseProxy = false, onSave, onCancel }: Props = $props(); + let { + serverId, + serverUrl, + serverUseProxy = false, + serverLabel = '', + onSave, + onCancel + }: Props = $props(); let editUrl = $derived(serverUrl); + let editName = $derived(serverLabel); let editHeaders = $state(''); let editUseProxy = $derived(serverUseProxy); @@ -34,7 +44,12 @@ function handleSave() { if (!canSave) return; - onSave(editUrl.trim(), editHeaders.trim(), editUseProxy); + + // An unchanged prefill keeps following the automatic label; only an + // actual edit becomes a persisted custom display name. + const name = editName.trim() !== serverLabel.trim() ? editName.trim() : undefined; + + onSave(editUrl.trim(), editHeaders.trim(), editUseProxy, name); } function handleSubmit(event: SubmitEvent) { @@ -42,10 +57,11 @@ handleSave(); } - export function setInitialValues(url: string, headers: string, useProxy: boolean) { + export function setInitialValues(url: string, headers: string, useProxy: boolean, name = '') { editUrl = url; editHeaders = headers; editUseProxy = useProxy; + editName = name; } @@ -55,6 +71,8 @@ (editName = v)} headers={editHeaders} useProxy={editUseProxy} onUrlChange={(v) => (editUrl = v)} diff --git a/tools/ui/src/lib/components/app/mcp/McpServerForm.svelte b/tools/ui/src/lib/components/app/mcp/McpServerForm.svelte index a7472add3..2b8e1226b 100644 --- a/tools/ui/src/lib/components/app/mcp/McpServerForm.svelte +++ b/tools/ui/src/lib/components/app/mcp/McpServerForm.svelte @@ -17,6 +17,10 @@ interface Props { url: string; headers: string; + name?: string; + onNameChange?: (name: string) => void; + /** Shown in the empty display name field, e.g. the current automatic label. */ + namePlaceholder?: string; useProxy?: boolean; onUrlChange: (url: string) => void; onHeadersChange: (headers: string) => void; @@ -44,6 +48,9 @@ let { url, headers, + name = '', + onNameChange, + namePlaceholder = 'Name reported by the server', useProxy = false, onUrlChange, onHeadersChange, @@ -156,6 +163,20 @@ {/if}
+
+ + + onNameChange?.(e.currentTarget.value)} + /> +
+