From 44c7feac9d9e1e54dc4024f8b76f13c66c137304 Mon Sep 17 00:00:00 2001 From: vvnsrzn Date: Sat, 21 Mar 2026 22:39:00 +0100 Subject: [PATCH 1/2] feat: implement MCP server search functionality and registry integration --- src/lib/components/mcp/AddServerForm.svelte | 114 +++++++---- .../components/mcp/MCPServerManager.svelte | 139 +++++++++++-- src/lib/components/mcp/MCPServerSearch.svelte | 95 +++++++++ .../components/mcp/RegistryResultCard.svelte | 66 +++++++ src/lib/components/mcp/ServerCard.svelte | 11 +- src/lib/types/Tool.ts | 23 +++ src/routes/api/mcp/registry/+server.ts | 121 ++++++++++++ src/routes/api/mcp/registry/registry.spec.ts | 182 ++++++++++++++++++ 8 files changed, 690 insertions(+), 61 deletions(-) create mode 100644 src/lib/components/mcp/MCPServerSearch.svelte create mode 100644 src/lib/components/mcp/RegistryResultCard.svelte create mode 100644 src/routes/api/mcp/registry/+server.ts create mode 100644 src/routes/api/mcp/registry/registry.spec.ts diff --git a/src/lib/components/mcp/AddServerForm.svelte b/src/lib/components/mcp/AddServerForm.svelte index a5bc035d5bf..0b80663aca0 100644 --- a/src/lib/components/mcp/AddServerForm.svelte +++ b/src/lib/components/mcp/AddServerForm.svelte @@ -16,6 +16,7 @@ oncancel: () => void; initialName?: string; initialUrl?: string; + initialDescription?: string; initialHeaders?: KeyValuePair[]; submitLabel?: string; } @@ -25,6 +26,7 @@ oncancel, initialName = "", initialUrl = "", + initialDescription = "", initialHeaders = [], submitLabel = "Add Server", }: Props = $props(); @@ -32,11 +34,13 @@ let name = $state(""); let url = $state(""); let headers = $state([]); + let headersOpen = $state(false); $effect.pre(() => { name = initialName; url = initialUrl; headers = initialHeaders.length > 0 ? [...initialHeaders] : []; + headersOpen = initialHeaders.length > 0; }); let showHeaderValues = $state>({}); let error = $state(null); @@ -80,7 +84,11 @@ if (header.key.trim() || header.value.trim()) { const headerError = validateHeader(header.key, header.value); if (headerError) { - error = `Header ${i + 1}: ${headerError}`; + const key = header.key.trim(); + error = + key && headerError === "Header value is required" + ? `"${key}" value is required` + : headerError; return false; } } @@ -99,12 +107,20 @@ onsubmit({ name: name.trim(), url: url.trim(), - headers: filteredHeaders.length > 0 ? filteredHeaders : undefined, + headers: + filteredHeaders.length > 0 + ? filteredHeaders.map(({ key, value }) => ({ key, value })) + : undefined, }); }
+ + {#if initialDescription} +

{initialDescription}

+ {/if} +
diff --git a/src/lib/components/mcp/MCPServerManager.svelte b/src/lib/components/mcp/MCPServerManager.svelte index 68d7dd69fa3..e0db5819731 100644 --- a/src/lib/components/mcp/MCPServerManager.svelte +++ b/src/lib/components/mcp/MCPServerManager.svelte @@ -3,15 +3,17 @@ import Modal from "$lib/components/Modal.svelte"; import ServerCard from "./ServerCard.svelte"; import AddServerForm from "./AddServerForm.svelte"; + import MCPServerSearch from "./MCPServerSearch.svelte"; import { allMcpServers, selectedServerIds, enabledServersCount, addCustomServer, + updateCustomServer, refreshMcpServers, healthCheckServer, } from "$lib/stores/mcpServers"; - import type { KeyValuePair } from "$lib/types/Tool"; + import type { KeyValuePair, MCPRegistryEntry, MCPServer } from "$lib/types/Tool"; import IconAddLarge from "~icons/carbon/add-large"; import IconRefresh from "~icons/carbon/renew"; import LucideHammer from "~icons/lucide/hammer"; @@ -26,22 +28,73 @@ let { onclose }: Props = $props(); type View = "list" | "add"; + type AddTab = "search" | "manual"; + let currentView = $state("list"); + let addTab = $state("search"); let isRefreshing = $state(false); + let prefillName = $state(""); + let prefillUrl = $state(""); + let prefillDescription = $state(""); + let prefillHeaders = $state([]); + let editingServer = $state(null); + const baseServers = $derived($allMcpServers.filter((s) => s.type === "base")); const customServers = $derived($allMcpServers.filter((s) => s.type === "custom")); const enabledCount = $derived($enabledServersCount); function handleAddServer(serverData: { name: string; url: string; headers?: KeyValuePair[] }) { - addCustomServer(serverData); + if (editingServer) { + updateCustomServer(editingServer.id, serverData); + } else { + addCustomServer(serverData); + } + resetAddState(); currentView = "list"; } + function resetAddState() { + prefillName = ""; + prefillUrl = ""; + prefillDescription = ""; + prefillHeaders = []; + editingServer = null; + addTab = "search"; + } + function handleCancel() { + resetAddState(); currentView = "list"; } + function handleEditServer(server: MCPServer) { + editingServer = server; + prefillName = server.name; + prefillUrl = server.url; + prefillDescription = ""; + prefillHeaders = server.headers ?? []; + addTab = "manual"; + currentView = "add"; + } + + function handleRegistryAdd(entry: MCPRegistryEntry) { + prefillName = entry.title ?? entry.name; + prefillUrl = entry.url; + prefillDescription = entry.description; + prefillHeaders = (entry.requiredHeaders ?? []).map((h) => ({ + key: h.name, + value: "", + description: h.description, + })); + addTab = "manual"; + } + + function switchToAdd() { + resetAddState(); + currentView = "add"; + } + async function handleRefresh() { if (isRefreshing) return; isRefreshing = true; @@ -63,6 +116,8 @@

{#if currentView === "list"} MCP Servers + {:else if editingServer} + Edit MCP server {:else} Add MCP server {/if} @@ -70,8 +125,10 @@

{#if currentView === "list"} Manage MCP servers to extend {publicConfig.PUBLIC_APP_NAME} with external tools. + {:else if editingServer} + Update the configuration for {editingServer.name}. {:else} - Add a custom MCP server to {publicConfig.PUBLIC_APP_NAME}. + Search the MCP Registry or add a custom server URL. {/if}

@@ -93,7 +150,7 @@

{$allMcpServers.length} - {$allMcpServers.length === 1 ? "server" : "servers"} configured + {$allMcpServers.length > 1 ? "servers" : "server"} configured

{enabledCount} enabled @@ -102,17 +159,19 @@

+ {#if $allMcpServers.length > 0} + + {/if} -
{:else if currentView === "add"} - + +
+ + +
+ + {#if addTab === "search"} + +
+ +
+ {:else} + + {/if} {/if} diff --git a/src/lib/components/mcp/MCPServerSearch.svelte b/src/lib/components/mcp/MCPServerSearch.svelte new file mode 100644 index 00000000000..923c560d82f --- /dev/null +++ b/src/lib/components/mcp/MCPServerSearch.svelte @@ -0,0 +1,95 @@ + + +
+ +
+ + +
+ + + {#if loading} +
+ Searching registry… +
+ {:else if error} +
+

+ Registry unavailable: {error} +

+
+ {:else if results.length === 0 && query} +
+ No servers found for "{query}". Try the Manual tab to add a custom URL. +
+ {:else} +
+ {#each results as entry (entry.name)} + + {/each} +
+ {/if} +
diff --git a/src/lib/components/mcp/RegistryResultCard.svelte b/src/lib/components/mcp/RegistryResultCard.svelte new file mode 100644 index 00000000000..74a96e30120 --- /dev/null +++ b/src/lib/components/mcp/RegistryResultCard.svelte @@ -0,0 +1,66 @@ + + +
+ + {#if entry.icons?.[0]?.src} + {entry.name} + {:else} +
+ +
+ {/if} + +
+ + + {entry.title ?? entry.name} + + + +

+ {entry.description} +

+
+ + +
+ {#if alreadyAdded} + + + Added + + {:else} + + {/if} +
+
diff --git a/src/lib/components/mcp/ServerCard.svelte b/src/lib/components/mcp/ServerCard.svelte index 694cd1ee803..19bb6643333 100644 --- a/src/lib/components/mcp/ServerCard.svelte +++ b/src/lib/components/mcp/ServerCard.svelte @@ -1,6 +1,7 @@