diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte index 40819d6f1a..893f8077dd 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte @@ -23,7 +23,8 @@ FileExtensionText, KeyboardKey, MimeTypeText, - SpecialFileType + SpecialFileType, + ToolSource } from '$lib/enums'; import { useChatFormPickers } from '$lib/hooks/use-chat-form-pickers.svelte'; import { @@ -73,7 +74,6 @@ disabled?: boolean; isLoading?: boolean; placeholder?: string; - showMcpPromptButton?: boolean; showAddButton?: boolean; showModelSelector?: boolean; @@ -103,7 +103,6 @@ onValueChange, placeholder = 'Type a message...', showAddButton = true, - showMcpPromptButton = false, showModelSelector = true, uploadedFiles = $bindable([]), value = $bindable('') @@ -152,9 +151,18 @@ getServerHome: () => toolsStore.serverHome ?? null, getShowModelSelector: () => showModelSelector, getValue: () => value, - hasCwdTools: () => toolsStore.hasEnabledCwdTools, - hasPrompts: () => - mcpStore.hasPromptsCapability(conversationsStore.preferences.getAllMcpServerOverrides()), + hasCwdTools: () => conversationsStore.preferences.hasEnabledCwdTools(), + // policy-aware, same rule as the agentic flow: MCP category on and at + // least one globally-enabled server whose group key is not disabled + hasPrompts: () => { + const prefs = conversationsStore.preferences; + + if (!prefs.isCategoryEnabled(ToolSource.MCP)) return false; + + return mcpStore + .getServers() + .some((s) => s.enabled && prefs.isServerToolsEnabled(s.id) && s.url.trim()); + }, openModelSelector: () => chatFormActionsRef?.openModelSelector(), setCaretOffset: (offset) => inputRef?.setCaretOffset(offset), setValue: (v) => { @@ -620,8 +628,6 @@ isReasoning={chatStore.isReasoning} {isRecording} onFileUpload={handleFileUpload} - onMcpPromptClick={showMcpPromptButton ? () => pickers.openPromptPicker() : undefined} - onMcpResourcesClick={() => (isResourceDialogOpen = true)} onMcpSettingsClick={() => (isMcpServersDialogOpen = true)} onMicClick={handleMicClick} {onStop} @@ -635,7 +641,7 @@ - {#if toolsStore.hasEnabledCwdTools} + {#if conversationsStore.preferences.hasEnabledCwdTools()} ({ hasAudioModality: chatFormActions.hasAudioModality, - hasMcpPromptsSupport: chatFormActions.hasMcpPromptsSupport, - hasMcpResourcesSupport: chatFormActions.hasMcpResourcesSupport, hasVideoModality: chatFormActions.hasVideoModality, hasVisionModality: chatFormActions.hasVisionModality }), () => ({ onFileUpload: chatFormActions.onFileUpload, - onMcpPromptClick: chatFormActions.onMcpPromptClick, - onMcpResourcesClick: chatFormActions.onMcpResourcesClick, onSystemPromptClick: chatFormActions.onSystemPromptClick }), () => { diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpSubmenu.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpSubmenu.svelte deleted file mode 100644 index 07439afd61..0000000000 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpSubmenu.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - MCP - - - - - - - Servers - - - {#if chatFormActions.hasMcpPromptsSupport} - - - - Prompts - - {/if} - - {#if chatFormActions.hasMcpResourcesSupport} - - - - Resources - - {/if} - - 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 2f69dc96de..acd0f4d211 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 @@ -1,18 +1,18 @@
@@ -194,80 +186,15 @@ - (mcpExpanded = open)} open={mcpExpanded}> - - {#if mcpExpanded} - - {:else} - - {/if} + - {/each} - - {#if mcpServers.length === 0} -
- No MCP servers configured -
- {/if} -
- - + System Message + {#if toolsPanel.totalToolCount > 0} (toolsExpanded = open)} open={toolsExpanded}> @@ -289,40 +216,12 @@
- {#each toolsPanel.activeGroups as group (group.key)} - {@const checked = toolsPanel.isGroupChecked(group)} - {@const enabledCount = toolsPanel.getEnabledToolCount(group)} - {@const favicon = toolsPanel.getFavicon(group)} + {#each toolsPanel.categoryGroups as group (group.key)} + {@render sheetGroupRow(group)} + {/each} - + {#each toolsPanel.mcpGroups as group (group.key)} + {@render sheetGroupRow(group)} {/each}
@@ -331,38 +230,55 @@ - - {#if chatFormActions.hasMcpPromptsSupport} - - {/if} - - {#if chatFormActions.hasMcpResourcesSupport} - - {/if} + +{#snippet sheetGroupRow(group: ToolGroup)} + {@const checkState = toolsPanel.getGroupCheckState(group)} + {@const enabledCount = toolsPanel.getEnabledToolCount(group)} + {@const favicon = toolsPanel.getFavicon(group)} + {@const groupDisabled = toolsPanel.isGroupDisabled(group)} + + +{/snippet} 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 40fed27c70..f495441714 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 @@ -7,6 +7,7 @@ import { CLI_FLAGS, ICON_CLASS_DEFAULT } from '$lib/constants'; import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte'; import { mcpStore, toolsStore } from '$lib/stores'; + import type { ToolGroup } from '$lib/types'; const toolsPanel = useToolsPanel(); const hasMcpServersAvailable = $derived(mcpStore.getServers().length > 0); @@ -62,95 +63,108 @@ {/if} {:else}
- {#each toolsPanel.activeGroups as group (group.key)} - {@const isExpanded = toolsPanel.expandedGroups.has(group.key)} - {@const checked = toolsPanel.isGroupChecked(group)} - {@const favicon = toolsPanel.getFavicon(group)} + {#each toolsPanel.categoryGroups as group (group.key)} + {@render groupRow(group)} + {/each} - toolsPanel.toggleGroupExpanded(group.key)} - open={isExpanded} - > -
- - {#if isExpanded} - - {:else} - - {/if} - - - {#if favicon} - { - (e.currentTarget as HTMLImageElement).style.display = 'none'; - }} - src={favicon} - /> - {/if} - - {group.label} - - - - {toolsPanel.getEnabledToolCount(group)}/{group.tools.length} - - - - - - {#snippet child({ props })} - toolsPanel.toggleGroupByKey(group.key)} - /> - {/snippet} - - - -

- {checked ? 'Disable' : 'Enable'} - {group.tools.length} tool{group.tools.length !== 1 ? 's' : ''} -

-
-
-
- - -
- {#each group.tools as entry (entry.key)} - {@const enabled = toolsStore.isToolEnabled(entry.key)} - - {/each} -
-
-
+ {#each toolsPanel.mcpGroups as group (group.key)} + {@render groupRow(group)} {/each}
{/if} + +{#snippet groupRow(group: ToolGroup)} + {@const isExpanded = toolsPanel.expandedGroups.has(group.key)} + {@const checkState = toolsPanel.getGroupCheckState(group)} + {@const favicon = toolsPanel.getFavicon(group)} + {@const groupDisabled = toolsPanel.isGroupDisabled(group)} + + toolsPanel.toggleGroupExpanded(group.key)} + open={isExpanded} + > +
+ + {#if isExpanded} + + {:else} + + {/if} + + + {#if favicon} + { + (e.currentTarget as HTMLImageElement).style.display = 'none'; + }} + src={favicon} + /> + {/if} + + {group.label} + + + + {toolsPanel.getEnabledToolCount(group)}/{group.tools.length} + + + + + + {#snippet child({ props })} + toolsPanel.toggleGroupByKey(group.key)} + /> + {/snippet} + + + +

+ {checkState.checked ? 'Disable' : 'Enable'} + {group.tools.length} tool{group.tools.length !== 1 ? 's' : ''} +

+
+
+
+ + +
+ {#each group.tools as entry (entry.key)} + {@const enabled = toolsPanel.isToolEnabled(entry)} + {@const parentDisabled = toolsPanel.isToolParentDisabled(entry)} + + {/each} +
+
+
+{/snippet} diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte index f1aa743693..395f2cfbe1 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte @@ -13,7 +13,7 @@ import { setChatFormActionsContext } from '$lib/contexts'; import { FileTypeCategory, MessageRole } from '$lib/enums'; import { ChatService } from '$lib/services'; - import { chatStore, conversationsStore, mcpStore, settingsStore } from '$lib/stores'; + import { chatStore, conversationsStore, settingsStore } from '$lib/stores'; import { getFileTypeCategory } from '$lib/utils'; interface Props { @@ -31,8 +31,6 @@ onMicClick?: () => void; onStop?: () => void; onSystemPromptClick?: () => void; - onMcpPromptClick?: () => void; - onMcpResourcesClick?: () => void; onMcpSettingsClick?: () => void; } @@ -45,8 +43,6 @@ isReasoning = false, isRecording = false, onFileUpload, - onMcpPromptClick, - onMcpResourcesClick, onMcpSettingsClick, onMicClick, onStop, @@ -58,18 +54,6 @@ let currentConfig = $derived(settingsStore.config); - let hasMcpPromptsSupport = $derived.by(() => { - const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides(); - - return mcpStore.hasPromptsCapability(perChatOverrides); - }); - - let hasMcpResourcesSupport = $derived.by(() => { - const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides(); - - return mcpStore.hasResourcesCapability(perChatOverrides); - }); - let hasAudioModality = $state(false); let hasVideoModality = $state(false); let hasVisionModality = $state(false); @@ -142,12 +126,6 @@ get hasAudioModality() { return hasAudioModality; }, - get hasMcpPromptsSupport() { - return hasMcpPromptsSupport; - }, - get hasMcpResourcesSupport() { - return hasMcpResourcesSupport; - }, get hasVideoModality() { return hasVideoModality; }, @@ -157,12 +135,6 @@ get onFileUpload() { return onFileUpload; }, - get onMcpPromptClick() { - return onMcpPromptClick; - }, - get onMcpResourcesClick() { - return onMcpResourcesClick; - }, get onMcpSettingsClick() { return onMcpSettingsClick; }, diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormCurrentWorkingDirectory/ChatFormCurrentWorkingDirectory.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormCurrentWorkingDirectory/ChatFormCurrentWorkingDirectory.svelte index 307d0e702e..ecd1698467 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormCurrentWorkingDirectory/ChatFormCurrentWorkingDirectory.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormCurrentWorkingDirectory/ChatFormCurrentWorkingDirectory.svelte @@ -5,12 +5,12 @@ import SearchInput from '$lib/components/app/forms/SearchInput.svelte'; import * as Popover from '$lib/components/ui/popover'; import { DEFAULT_MOBILE_BREAKPOINT, HOME_TILDE, SEARCH, UI_DATA_ATTRS } from '$lib/constants'; - import { BuiltInTool, GlobSearchType, KeyboardKey } from '$lib/enums'; + import { BuiltInTool, GlobSearchType, KeyboardKey, ToolSource } from '$lib/enums'; import { useDebouncedSearch } from '$lib/hooks/use-debounced-search.svelte'; import { usePickerNavigation } from '$lib/hooks/use-picker-navigation.svelte'; import { useScrollActiveRow } from '$lib/hooks/use-scroll-active-row.svelte'; import { ToolsService } from '$lib/services/tools.service'; - import { toolsStore } from '$lib/stores'; + import { conversationsStore, toolsStore } from '$lib/stores'; import type { GlobEntry } from '$lib/types'; import { abbreviateHome, @@ -63,8 +63,11 @@ // unavailable instead of firing searches that would only fail. Browse is // hidden too: it resolves the picked folder name through the same tool. const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.SERVER_FILE_GLOB_SEARCH)); + // effective policy: the active conversation's tool policy, or global defaults const fileSearchEnabled = $derived( - fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey) + fileSearchKey !== null && + conversationsStore.preferences.isToolEnabled(fileSearchKey) && + conversationsStore.preferences.isCategoryEnabled(ToolSource.SERVER) ); const searchUnavailableMessage = $derived( fileSearchKey === null diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte index 353d6e7baf..f6a3ee1347 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte @@ -9,7 +9,7 @@ } from '$lib/components/app/chat'; import Badge from '$lib/components/ui/badge/badge.svelte'; import { KeyboardKey } from '$lib/enums'; - import { conversationsStore, mcpStore } from '$lib/stores'; + import { mcpStore } from '$lib/stores'; import type { GetPromptResult, MCPPromptInfo, MCPServerSettingsEntry } from '$lib/types'; import { debounce, uuid } from '$lib/utils'; import { SvelteMap } from 'svelte/reactivity'; @@ -87,8 +87,7 @@ isLoading = true; try { - const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides(); - const initialized = await mcpStore.ensureInitialized(perChatOverrides); + const initialized = await mcpStore.ensureInitialized(); if (!initialized) { prompts = []; diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMention.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMention.svelte index 5a5c8320f9..d09a3d3cb8 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMention.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMention.svelte @@ -5,10 +5,16 @@ import * as Popover from '$lib/components/ui/popover'; import * as Tooltip from '$lib/components/ui/tooltip'; import { FILE_GLOB_SEARCH_PICKERS, HOME_TILDE, SEARCH } from '$lib/constants'; - import { BuiltInTool, FileMentionEntryType, GlobSearchType, KeyboardKey } from '$lib/enums'; + import { + BuiltInTool, + FileMentionEntryType, + GlobSearchType, + KeyboardKey, + ToolSource + } from '$lib/enums'; import { useDebouncedSearch } from '$lib/hooks/use-debounced-search.svelte'; import { usePickerNavigation } from '$lib/hooks/use-picker-navigation.svelte'; - import { deviceStore, settingsStore, toolsStore } from '$lib/stores'; + import { conversationsStore, deviceStore, settingsStore, toolsStore } from '$lib/stores'; import type { FileMentionEntry, GlobEntryResult } from '$lib/types'; import { abbreviateHome, runGlobSearchWithChildren } from '$lib/utils'; @@ -52,8 +58,11 @@ // --tools) or the user disabled it, the picker still opens but explains // why instead of firing searches that would only fail. const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.SERVER_FILE_GLOB_SEARCH)); + // effective policy: the active conversation's tool policy, or global defaults const fileSearchEnabled = $derived( - fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey) + fileSearchKey !== null && + conversationsStore.preferences.isToolEnabled(fileSearchKey) && + conversationsStore.preferences.isCategoryEnabled(ToolSource.SERVER) ); let searchResults = $state([]); diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte index 41d79387b6..6e30cebec6 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte @@ -111,7 +111,6 @@ onValueChange={editCtx.setContent} placeholder="Edit your message..." showAddButton={editCtx.messageRole === MessageRole.USER} - showMcpPromptButton showModelSelector={editCtx.messageRole === MessageRole.USER} value={editCtx.editedContent} /> diff --git a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte index 9825b4b90b..962b6774fc 100644 --- a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenForm.svelte @@ -160,6 +160,5 @@ onSubmit={handleSubmit} onSystemPromptClick={handleSystemPromptClick} onUploadedFileRemove={handleUploadedFileRemove} - showMcpPromptButton /> diff --git a/tools/ui/src/lib/components/app/chat/index.ts b/tools/ui/src/lib/components/app/chat/index.ts index 61ec242e90..d7d7745df1 100644 --- a/tools/ui/src/lib/components/app/chat/index.ts +++ b/tools/ui/src/lib/components/app/chat/index.ts @@ -220,19 +220,6 @@ export { default as ChatFormActionModels } from './ChatForm/ChatFormActions/Chat */ export { default as ChatFormActionAddToolsSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte'; -/** - * Dropdown submenu for MCP prompts and resources in the chat form. - * - * Shows an "MCP" sub-menu item with entries for MCP Prompts and MCP - * Resources. Only visible when the server supports them. - * - * @example - * ```svelte - * - * ``` - */ -export { default as ChatFormActionAddMcpSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpSubmenu.svelte'; - /** * Dropdown submenu for selecting reasoning effort level. * diff --git a/tools/ui/src/lib/components/app/dialogs/DialogMcpResourcesBrowser.svelte b/tools/ui/src/lib/components/app/dialogs/DialogMcpResourcesBrowser.svelte index 6dfcdb856c..82a07477d1 100644 --- a/tools/ui/src/lib/components/app/dialogs/DialogMcpResourcesBrowser.svelte +++ b/tools/ui/src/lib/components/app/dialogs/DialogMcpResourcesBrowser.svelte @@ -8,7 +8,7 @@ import { Button } from '$lib/components/ui/button'; import * as Dialog from '$lib/components/ui/dialog'; import { ICON_CLASS_DEFAULT } from '$lib/constants'; - import { conversationsStore, mcpStore } from '$lib/stores'; + import { mcpStore } from '$lib/stores'; import type { MCPResourceContent, MCPResourceInfo, MCPResourceTemplateInfo } from '$lib/types'; import { getResourceDisplayName } from '$lib/utils'; import { SvelteSet } from 'svelte/reactivity'; @@ -48,8 +48,7 @@ }); async function loadResources() { - const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides(); - const initialized = await mcpStore.ensureInitialized(perChatOverrides); + const initialized = await mcpStore.ensureInitialized(); if (initialized) { await mcpStore.fetchAllResources(); diff --git a/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte b/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte index fab45aa970..bc28a754c6 100644 --- a/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte +++ b/tools/ui/src/lib/components/app/dialogs/DialogMcpServerAddNew.svelte @@ -10,7 +10,7 @@ RECOMMENDED_MCP_SERVERS } from '$lib/constants'; import { BooleanString, HealthCheckStatus } from '$lib/enums'; - import { conversationsStore, mcpStore } from '$lib/stores'; + import { mcpStore } from '$lib/stores'; import { canonicalizeServerUrl, parseHeadersToArray, uuid } from '$lib/utils'; interface Props { @@ -234,8 +234,6 @@ useProxy: newServerUseProxy }); - conversationsStore.preferences.setMcpServerOverride(newServerId, true); - handleOpenChange(false); } diff --git a/tools/ui/src/lib/components/app/dialogs/DialogModelInformation.svelte b/tools/ui/src/lib/components/app/dialogs/DialogModelInformation.svelte index 811c24d6b7..e200c004e5 100644 --- a/tools/ui/src/lib/components/app/dialogs/DialogModelInformation.svelte +++ b/tools/ui/src/lib/components/app/dialogs/DialogModelInformation.svelte @@ -76,22 +76,19 @@ - - + + + - - Model Information +
+
+ Model Information - Current model details and capabilities - + Current model details and capabilities +
-
{#if isLoadingModels || isLoadingRouterProps}
Loading model information...
@@ -100,17 +97,15 @@ {@const modelMeta = firstModel.meta} {#if serverProps} - + +