| | <script lang="ts"> |
| | import { usePublicConfig } from "$lib/utils/PublicConfig.svelte"; |
| | import Modal from "$lib/components/Modal.svelte"; |
| | import ServerCard from "./ServerCard.svelte"; |
| | import AddServerForm from "./AddServerForm.svelte"; |
| | import { |
| | allMcpServers, |
| | selectedServerIds, |
| | enabledServersCount, |
| | addCustomServer, |
| | refreshMcpServers, |
| | healthCheckServer, |
| | } from "$lib/stores/mcpServers"; |
| | import type { KeyValuePair } from "$lib/types/Tool"; |
| | import IconAddLarge from "~icons/carbon/add-large"; |
| | import IconRefresh from "~icons/carbon/renew"; |
| | import LucideHammer from "~icons/lucide/hammer"; |
| | import IconMCP from "$lib/components/icons/IconMCP.svelte"; |
| | |
| | const publicConfig = usePublicConfig(); |
| | |
| | interface Props { |
| | onclose: () => void; |
| | } |
| | |
| | let { onclose }: Props = $props(); |
| | |
| | type View = "list" | "add"; |
| | let currentView = $state<View>("list"); |
| | let isRefreshing = $state(false); |
| | |
| | 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); |
| | currentView = "list"; |
| | } |
| | |
| | function handleCancel() { |
| | currentView = "list"; |
| | } |
| | |
| | async function handleRefresh() { |
| | if (isRefreshing) return; |
| | isRefreshing = true; |
| | try { |
| | await refreshMcpServers(); |
| | |
| | const servers = $allMcpServers; |
| | await Promise.allSettled(servers.map((s) => healthCheckServer(s))); |
| | } finally { |
| | isRefreshing = false; |
| | } |
| | } |
| | </script> |
| |
|
| | <Modal width={currentView === "list" ? "w-[800px]" : "w-[600px]"} {onclose} closeButton> |
| | <div class="p-6"> |
| | |
| | <div class="mb-6"> |
| | <h2 class="mb-1 text-xl font-semibold text-gray-900 dark:text-gray-200"> |
| | {#if currentView === "list"} |
| | MCP Servers |
| | {:else} |
| | Add MCP server |
| | {/if} |
| | </h2> |
| | <p class="text-sm text-gray-600 dark:text-gray-400"> |
| | {#if currentView === "list"} |
| | Manage MCP servers to extend {publicConfig.PUBLIC_APP_NAME} with external tools. |
| | {:else} |
| | Add a custom MCP server to {publicConfig.PUBLIC_APP_NAME}. |
| | {/if} |
| | </p> |
| | </div> |
| |
|
| | |
| | {#if currentView === "list"} |
| | <div |
| | class="mb-6 flex justify-between rounded-lg p-4 max-sm:flex-col max-sm:gap-4 sm:items-center {!enabledCount |
| | ? 'bg-gray-100 dark:bg-white/5' |
| | : 'bg-blue-50 dark:bg-blue-900/10'}" |
| | > |
| | <div class="flex items-center gap-3"> |
| | <div |
| | class="flex size-10 items-center justify-center rounded-xl bg-blue-500/10" |
| | class:grayscale={!enabledCount} |
| | > |
| | <IconMCP classNames="size-8 text-blue-600 dark:text-blue-500" /> |
| | </div> |
| | <div> |
| | <p class="text-sm font-semibold text-gray-900 dark:text-gray-100"> |
| | {$allMcpServers.length} |
| | {$allMcpServers.length === 1 ? "server" : "servers"} configured |
| | </p> |
| | <p class="text-xs text-gray-600 dark:text-gray-400"> |
| | {enabledCount} enabled |
| | </p> |
| | </div> |
| | </div> |
| |
|
| | <div class="flex gap-2"> |
| | <button |
| | onclick={handleRefresh} |
| | disabled={isRefreshing} |
| | class="btn gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700" |
| | > |
| | <IconRefresh class="size-4 {isRefreshing ? 'animate-spin' : ''}" /> |
| | {isRefreshing ? "Refreshing…" : "Refresh"} |
| | </button> |
| | <button |
| | onclick={() => (currentView = "add")} |
| | class="btn flex items-center gap-0.5 rounded-lg bg-blue-600 py-1.5 pl-2 pr-3 text-sm font-medium text-white hover:bg-blue-600" |
| | > |
| | <IconAddLarge class="size-4" /> |
| | Add Server |
| | </button> |
| | </div> |
| | </div> |
| | <div class="space-y-5"> |
| | |
| | {#if baseServers.length > 0} |
| | <div> |
| | <h3 class="mb-3 text-sm font-medium text-gray-700 dark:text-gray-300"> |
| | Base Servers ({baseServers.length}) |
| | </h3> |
| | <div class="grid grid-cols-1 gap-3 md:grid-cols-2"> |
| | {#each baseServers as server (server.id)} |
| | <ServerCard {server} isSelected={$selectedServerIds.has(server.id)} /> |
| | {/each} |
| | </div> |
| | </div> |
| | {/if} |
| |
|
| | |
| | <div> |
| | <h3 class="mb-3 text-sm font-medium text-gray-700 dark:text-gray-300"> |
| | Custom Servers ({customServers.length}) |
| | </h3> |
| | {#if customServers.length === 0} |
| | <div |
| | class="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 p-8 dark:border-gray-700" |
| | > |
| | <LucideHammer class="mb-3 size-12 text-gray-400" /> |
| | <p class="mb-1 text-sm font-medium text-gray-900 dark:text-gray-100"> |
| | No custom servers yet |
| | </p> |
| | <p class="mb-4 text-xs text-gray-600 dark:text-gray-400"> |
| | Add your own MCP servers with custom tools |
| | </p> |
| | <button |
| | onclick={() => (currentView = "add")} |
| | class="flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-600" |
| | > |
| | <IconAddLarge class="size-4" /> |
| | Add Your First Server |
| | </button> |
| | </div> |
| | {:else} |
| | <div class="grid grid-cols-1 gap-3 md:grid-cols-2"> |
| | {#each customServers as server (server.id)} |
| | <ServerCard {server} isSelected={$selectedServerIds.has(server.id)} /> |
| | {/each} |
| | </div> |
| | {/if} |
| | </div> |
| |
|
| | |
| | <div class="rounded-lg bg-gray-50 p-4 dark:bg-gray-700"> |
| | <h4 class="mb-2 text-sm font-medium text-gray-900 dark:text-gray-100">💡 Quick Tips</h4> |
| | <ul class="space-y-1 text-xs text-gray-600 dark:text-gray-400"> |
| | <li>• Only connect to servers you trust</li> |
| | <li>• Enable servers to make their tools available in chat</li> |
| | <li>• Use the Health Check button to verify server connectivity</li> |
| | <li>• You can add HTTP headers for authentication when required</li> |
| | </ul> |
| | </div> |
| | </div> |
| | {:else if currentView === "add"} |
| | <AddServerForm onsubmit={handleAddServer} oncancel={handleCancel} /> |
| | {/if} |
| | </div> |
| | </Modal> |
| |
|