diff --git a/web/src/app/settings/dialogs/add-mcp-server-dialog.tsx b/web/src/app/settings/dialogs/add-mcp-server-dialog.tsx index caa6d47..cc029cd 100644 --- a/web/src/app/settings/dialogs/add-mcp-server-dialog.tsx +++ b/web/src/app/settings/dialogs/add-mcp-server-dialog.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT import { Loader2 } from "lucide-react"; -import { useCallback, useState } from "react"; +import { useCallback, useRef, useState } from "react"; import { Button } from "~/components/ui/button"; import { @@ -34,6 +34,8 @@ export function AddMCPServerDialog({ const [validationError, setValidationError] = useState(""); const [error, setError] = useState(null); const [processing, setProcessing] = useState(false); + const abortControllerRef = useRef(null); + const handleChange = useCallback((value: string) => { setInput(value); if (!value.trim()) { @@ -74,7 +76,9 @@ export function AddMCPServerDialog({ return; } }, []); + const handleAdd = useCallback(async () => { + abortControllerRef.current = new AbortController(); const config = MCPConfigSchema.parse(JSON.parse(input)); setInput(JSON.stringify(config, null, 2)); const addingServers: SimpleMCPServerMetadata[] = []; @@ -105,7 +109,7 @@ export function AddMCPServerDialog({ setError(null); for (const server of addingServers) { processingServer = server.name; - const metadata = await queryMCPServerMetadata(server); + const metadata = await queryMCPServerMetadata(server, abortControllerRef.current.signal); results.push({ ...metadata, name: server.name, enabled: true }); } if (results.length > 0) { @@ -115,12 +119,23 @@ export function AddMCPServerDialog({ setOpen(false); } catch (e) { console.error(e); - setError(`Failed to add server: ${processingServer}`); + if (e instanceof Error && e.name === 'AbortError') { + setError(`Request was cancelled`); + } else { + setError(`Failed to add server: ${processingServer}`); + } } finally { setProcessing(false); + abortControllerRef.current = null; } }, [input, onAdd]); + const handleAbort = () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + return ( @@ -165,6 +180,11 @@ export function AddMCPServerDialog({ {processing && } Add + { + processing && ( + + ) + } diff --git a/web/src/core/api/mcp.ts b/web/src/core/api/mcp.ts index a50cf13..2f289e9 100644 --- a/web/src/core/api/mcp.ts +++ b/web/src/core/api/mcp.ts @@ -5,13 +5,14 @@ import type { SimpleMCPServerMetadata } from "../mcp"; import { resolveServiceURL } from "./resolve-service-url"; -export async function queryMCPServerMetadata(config: SimpleMCPServerMetadata) { +export async function queryMCPServerMetadata(config: SimpleMCPServerMetadata, signal?: AbortSignal) { const response = await fetch(resolveServiceURL("mcp/server/metadata"), { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(config), + signal, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`);