Spaces:
Runtime error
Runtime error
File size: 3,341 Bytes
52c6f5c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
<script lang="ts">
import { mcpServers, type MCPServerEntity, type MCPFormData } from "$lib/state/mcps.svelte.js";
import { projects } from "$lib/state/projects.svelte.js";
import { extractDomain } from "$lib/utils/url.js";
import IconEdit from "~icons/carbon/edit";
import IconDelete from "~icons/carbon/trash-can";
import Switch from "../switch.svelte";
import McpForm from "./mcp-form.svelte";
interface Props {
server: MCPServerEntity;
}
let { server }: Props = $props();
let editing = $state(false);
async function deleteServer() {
await mcpServers.delete(server.id);
// Remove from project's enabled MCPs if it was enabled
const currentProject = projects.current;
if (!currentProject?.enabledMCPs?.includes(server.id)) return;
await projects.update({
...currentProject,
enabledMCPs: currentProject.enabledMCPs.filter(mcpId => mcpId !== server.id),
});
}
async function setEnabled(enabled: boolean) {
const currentProject = projects.current;
if (!currentProject) return;
const enabledMCPs = currentProject.enabledMCPs || [];
const newEnabledMCPs = enabled ? [...enabledMCPs, server.id] : enabledMCPs.filter(id => id !== server.id);
await projects.update({
...currentProject,
enabledMCPs: newEnabledMCPs,
});
}
const isEnabled = $derived(projects.current?.enabledMCPs?.includes(server.id) || false);
async function saveServer(formData: MCPFormData) {
await mcpServers.update({
...server,
...formData,
});
editing = false;
}
function getFaviconUrl(url: string): string {
const domain = extractDomain(url);
return `https://www.google.com/s2/favicons?domain=https://${domain}&sz=64`;
}
function urlWithoutSubpaths(url: string): string {
const urlObj = new URL(url);
return urlObj.origin;
}
</script>
<div class="rounded-lg border border-gray-200 p-3 dark:border-gray-700">
<div class="flex justify-between">
<div>
<div class="flex items-center gap-1">
<img src={getFaviconUrl(server.url)} alt="Server Icon" class="size-4 rounded p-0.5 dark:bg-gray-500" />
<span class="font-bold">{server.name}</span>
</div>
<p class="mt-1 truncate text-sm dark:text-neutral-300">
<span class="rounded bg-blue-900 px-0.75 py-0.25 uppercase">
{server.protocol}
</span>
<span>
{urlWithoutSubpaths(server.url)}
</span>
</p>
{#if server.headers && Object.keys(server.headers).length > 0}
<p class="mt-1 text-xs dark:text-neutral-400">
Headers: {Object.keys(server.headers).length} configured
</p>
{/if}
</div>
<div class="flex flex-col items-end justify-between gap-2">
<Switch bind:value={() => isEnabled, v => setEnabled(v)} />
<div class="flex items-center gap-2">
{#if !editing}
<button class="btn-mini" onclick={() => (editing = true)}>
<IconEdit class="h-4 w-4" />
<span>Edit</span>
</button>
<button
class="btn-mini text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
onclick={() => deleteServer()}
>
<IconDelete class="h-4 w-4" />
<span>Delete</span>
</button>
{/if}
</div>
</div>
</div>
{#if editing}
<div class="mt-2 border-t border-neutral-500 pt-2 dark:border-neutral-700">
<McpForm {server} onSubmit={saveServer} onCancel={() => (editing = false)} />
</div>
{/if}
</div>
|