// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates // SPDX-License-Identifier: MIT import { Loader2 } from "lucide-react"; import { useCallback, useState } from "react"; import { Button } from "~/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/ui/dialog"; import { Textarea } from "~/components/ui/textarea"; import { queryMCPServerMetadata } from "~/core/api"; import { MCPConfigSchema, type MCPServerMetadata, type SimpleMCPServerMetadata, type SimpleSSEMCPServerMetadata, type SimpleStdioMCPServerMetadata, } from "~/core/mcp"; export function AddMCPServerDialog({ onAdd, }: { onAdd?: (servers: MCPServerMetadata[]) => void; }) { const [open, setOpen] = useState(false); const [input, setInput] = useState(""); const [validationError, setValidationError] = useState(""); const [error, setError] = useState(null); const [processing, setProcessing] = useState(false); const handleChange = useCallback((value: string) => { setInput(value); if (!value.trim()) { setValidationError(null); return; } setValidationError(null); try { const parsed = JSON.parse(value); if (!("mcpServers" in parsed)) { setValidationError("Missing `mcpServers` in JSON"); return; } } catch { setValidationError("Invalid JSON"); return; } const result = MCPConfigSchema.safeParse(JSON.parse(value)); if (!result.success) { if (result.error.errors[0]) { const error = result.error.errors[0]; if (error.code === "invalid_union") { if (error.unionErrors[0]?.errors[0]) { setValidationError(error.unionErrors[0].errors[0].message); return; } } } const errorMessage = result.error.errors[0]?.message ?? "Validation failed"; setValidationError(errorMessage); return; } const keys = Object.keys(result.data.mcpServers); if (keys.length === 0) { setValidationError("Missing server name in `mcpServers`"); return; } }, []); const handleAdd = useCallback(async () => { const config = MCPConfigSchema.parse(JSON.parse(input)); setInput(JSON.stringify(config, null, 2)); const addingServers: SimpleMCPServerMetadata[] = []; for (const [key, server] of Object.entries(config.mcpServers)) { if ("command" in server) { const metadata: SimpleStdioMCPServerMetadata = { transport: "stdio", name: key, command: server.command, args: server.args, env: server.env, }; addingServers.push(metadata); } else if ("url" in server) { const metadata: SimpleSSEMCPServerMetadata = { transport: "sse", name: key, url: server.url, }; addingServers.push(metadata); } } setProcessing(true); const results: MCPServerMetadata[] = []; let processingServer: string | null = null; try { setError(null); for (const server of addingServers) { processingServer = server.name; const metadata = await queryMCPServerMetadata(server); results.push({ ...metadata, name: server.name, enabled: true }); } if (results.length > 0) { onAdd?.(results); } setInput(""); setOpen(false); } catch (e) { console.error(e); setError(`Failed to add server: ${processingServer}`); } finally { setProcessing(false); } }, [input, onAdd]); return ( Add New MCP Servers DeerFlow uses the standard JSON MCP config to create a new server.
Paste your config below and click "Add" to add new servers.