feat: support multiple folder selection in MCP scope selector

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
centdix
2026-03-27 12:28:59 +01:00
parent 0885d8c986
commit 9a4a8d20b6

View File

@@ -5,9 +5,9 @@
import Popover from '$lib/components/Popover.svelte'
import MultiSelect from '$lib/components/select/MultiSelect.svelte'
import { safeSelectItems } from '$lib/components/select/utils.svelte'
import FolderPicker from '$lib/components/FolderPicker.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import { FlowService, IntegrationService, ScriptService } from '$lib/gen'
import { FlowService, FolderService, IntegrationService, ScriptService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { mcpEndpointTools } from '$lib/mcpEndpointTools'
import InfoIcon from 'lucide-svelte/icons/info'
import { SvelteMap } from 'svelte/reactivity'
@@ -20,7 +20,9 @@
let { workspaceId, scope = $bindable() }: Props = $props()
let selectedMode = $state<'favorites' | 'all' | 'folder' | 'custom'>('favorites')
let selectedFolder = $state<string>('')
let selectedFolders = $state<string[]>([])
let allFolders = $state<string[]>([])
let loadingFolders = $state(false)
let selectedScripts = $state<string[]>([])
let selectedFlows = $state<string[]>([])
let selectedEndpoints = $state<string[]>([])
@@ -70,8 +72,10 @@
scopeParts.push(`mcp:endpoints:${selectedEndpoints.join(',')}`)
}
} else if (selectedMode === 'folder') {
const folderPath = `f/${selectedFolder}/*`
scopeParts = [`mcp:scripts:${folderPath}`, `mcp:flows:${folderPath}`, `mcp:endpoints:*`]
const folderPaths = selectedFolders.map((f) => `f/${f}/*`).join(',')
if (folderPaths.length > 0) {
scopeParts = [`mcp:scripts:${folderPaths}`, `mcp:flows:${folderPaths}`, `mcp:endpoints:*`]
}
} else {
scopeParts = [`mcp:${selectedMode}`]
}
@@ -91,13 +95,32 @@
}
})
// Clear folder when not in folder mode
// Clear folders when not in folder mode, load folder names when entering folder mode
$effect(() => {
if (selectedMode !== 'folder') {
selectedFolder = ''
if (selectedMode === 'folder') {
loadFolderNames()
} else {
selectedFolders = []
}
})
async function loadFolderNames() {
if (allFolders.length > 0) return
try {
loadingFolders = true
const excludedFolders = ['app_groups', 'app_custom', 'app_themes']
allFolders = (
await FolderService.listFolderNames({
workspace: $workspaceStore!
})
).filter((x) => !excludedFolders.includes(x))
} catch {
allFolders = []
} finally {
loadingFolders = false
}
}
// Load hub apps on mount
async function getAllApps() {
if (allApps.length > 0) return
@@ -192,11 +215,32 @@
// Load runnables based on mode
$effect(() => {
if (workspaceId) {
const folderParam = selectedFolder.length > 0 ? selectedFolder : undefined
getScriptsAndFlows(selectedMode === 'favorites', workspaceId, folderParam)
if (selectedMode === 'folder' && selectedFolders.length > 0) {
loadRunnablesForFolders(workspaceId, selectedFolders)
} else {
getScriptsAndFlows(selectedMode === 'favorites', workspaceId, undefined)
}
}
})
async function loadRunnablesForFolders(workspace: string, folders: string[]) {
try {
loadingRunnables = true
const results = await Promise.all(
folders.map(async (folder) => {
const [scripts, flows] = await Promise.all([
getScripts(false, workspace, folder),
getFlows(false, workspace, folder)
])
return [...scripts, ...flows]
})
)
includedRunnables = [...new Set(results.flat())]
} finally {
loadingRunnables = false
}
}
// Load all scripts/flows for custom mode
$effect(() => {
if (selectedMode === 'custom' && workspaceId) {
@@ -209,7 +253,7 @@
? 'Create your first scripts or flows to make them available via MCP.'
: selectedMode === 'favorites'
? `You do not have any favorite scripts or flows. You can favorite some scripts and flows to include them, or change the scope to "All scripts/flows" to include all your scripts and flows.`
: `You do not have any scripts or flows in the selected folder.`
: `You do not have any scripts or flows in the selected folder(s).`
)
function selectAllScripts() {
@@ -252,8 +296,8 @@
<ToggleButton
{item}
value="folder"
label="Folder"
tooltip="Make all scripts and flows in the selected folder available as tools"
label="Folders"
tooltip="Make all scripts and flows in the selected folders available as tools"
/>
<ToggleButton
{item}
@@ -267,8 +311,16 @@
{#if selectedMode === 'folder'}
<div>
<span class="block mb-1 text-emphasis text-xs font-semibold">Select Folder</span>
<FolderPicker bind:folderName={selectedFolder} />
<span class="block mb-1 text-emphasis text-xs font-semibold">Select Folders</span>
{#if loadingFolders}
<div class="text-xs text-primary">Loading folders...</div>
{:else}
<MultiSelect
items={safeSelectItems(allFolders)}
placeholder="Select folders"
bind:value={selectedFolders}
/>
{/if}
</div>
{/if}
@@ -389,7 +441,7 @@
</div>
</div>
{/if}
{:else if selectedMode !== 'folder' || selectedFolder.length > 0}
{:else if selectedMode !== 'folder' || selectedFolders.length > 0}
{#if loadingRunnables}
<div class="flex flex-col gap-2">
<span class="block text-xs text-primary"