{WELL_KNOWN_LOGOS.map((logo) => {
diff --git a/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.tsx b/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.tsx
new file mode 100644
index 00000000000..935b9bb349a
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.tsx
@@ -0,0 +1,429 @@
+import { useEffect, useState, type FC, type KeyboardEvent, type MouseEvent } from "react";
+import { Dropdown, Tooltip, Typography, Tag } from "antd";
+import type { MenuProps } from "antd";
+import {
+ CheckOutlined,
+ DeleteOutlined,
+ ExclamationCircleFilled,
+ MoreOutlined,
+ ThunderboltOutlined,
+} from "@ant-design/icons";
+import type { MCPServer } from "./types";
+import { getMaskedAndFullUrl } from "./utils";
+import {
+ getEnvVarDefinitions,
+ getMissingUserFields,
+ subscribeEnvVarsChanged,
+} from "./mock/mockMcpEnvVars";
+
+const { Text } = Typography;
+
+interface MCPServerCardProps {
+ server: MCPServer;
+ userID: string;
+ isLoadingHealth?: boolean;
+ isRechecking?: boolean;
+ onClick: () => void;
+ onRecheckHealth?: () => void;
+ onByokConnect?: () => void;
+ onOpenFillFields?: () => void;
+ onDelete?: () => void;
+}
+
+const HEALTH_TONE: Record
= {
+ healthy: { dot: "bg-green-500" },
+ unhealthy: { dot: "bg-red-500" },
+ unknown: { dot: "bg-gray-300" },
+};
+
+// Stop card-level click handler from firing when an interactive child is used.
+const stop = (e: MouseEvent | KeyboardEvent) => e.stopPropagation();
+
+const MCPServerCard: FC = ({
+ server,
+ userID,
+ isLoadingHealth,
+ isRechecking,
+ onClick,
+ onRecheckHealth,
+ onByokConnect,
+ onOpenFillFields,
+ onDelete,
+}) => {
+ // Re-render whenever the mock env var store changes, so the missing-fields
+ // badge updates as soon as the user fills in values in the modal.
+ const [, setEnvTick] = useState(0);
+ useEffect(
+ () => subscribeEnvVarsChanged(() => setEnvTick((t) => t + 1)),
+ [],
+ );
+
+ const alias = server.alias || server.server_name || "";
+ const name = server.server_name || alias || server.server_id;
+ // Logo is sourced exclusively from the admin-set `mcp_info.logo_url`.
+ // We deliberately do not guess from the server name/alias — that's brittle
+ // (renames break the icon, slug arithmetic 404s on unknown brands). The
+ // create/edit form auto-fills `logo_url` from discovery `icon_url` and
+ // from a host-keyed lookup so the common cases don't require manual work.
+ const candidateLogo = server.mcp_info?.logo_url ?? undefined;
+ const [failedLogoUrl, setFailedLogoUrl] = useState(null);
+ const logoUrl =
+ candidateLogo && failedLogoUrl !== candidateLogo ? candidateLogo : undefined;
+ const transport = server.transport || "http";
+ const displayTransport =
+ server.spec_path && transport !== "stdio" ? "openapi" : transport;
+ const authType = server.auth_type || "none";
+ const status = server.status || "unknown";
+ const healthTone = HEALTH_TONE[status] ?? HEALTH_TONE.unknown;
+ const isPublic = server.available_on_public_internet;
+ const accessGroups = (server.mcp_access_groups ?? []).filter(
+ (g): g is string => typeof g === "string",
+ );
+
+ const envDefs = alias ? getEnvVarDefinitions(alias) : [];
+ const perUserCount = envDefs.filter((d) => d.scope === "per_user").length;
+ const missingUserFields = alias ? getMissingUserFields(alias, userID) : [];
+ const needsAttention = perUserCount > 0 && missingUserFields.length > 0;
+
+ const cardClass = needsAttention
+ ? "border-2 border-red-300 bg-red-50/40 hover:border-red-400 hover:shadow-md"
+ : "border border-gray-200 bg-white hover:border-gray-300 hover:shadow-md";
+
+ const url = server.url || "";
+ const { maskedUrl } = url ? getMaskedAndFullUrl(url) : { maskedUrl: "" };
+
+ // Transport-adapted identifier shown under the title. Every transport has
+ // something useful here, which keeps the tag row vertically aligned across
+ // cards in the grid (stdio cards no longer "snap up" because they lack a URL).
+ let subtitle = "";
+ let subtitleTooltip = "";
+ if (transport === "stdio") {
+ const parts = [server.command, ...(server.args ?? [])]
+ .filter((p): p is string => typeof p === "string" && p.length > 0);
+ subtitle = parts.join(" ");
+ subtitleTooltip = subtitle;
+ } else if (server.spec_path) {
+ subtitle = server.spec_path;
+ subtitleTooltip = server.spec_path;
+ } else if (url) {
+ subtitle = maskedUrl;
+ subtitleTooltip = url;
+ }
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ onClick();
+ }
+ };
+
+ const menuItems: MenuProps["items"] = [];
+ if (onRecheckHealth) {
+ menuItems.push({
+ key: "test-connection",
+ label: "Test Connection",
+ icon: ,
+ disabled: isRechecking,
+ onClick: ({ domEvent }) => {
+ domEvent.stopPropagation();
+ onRecheckHealth();
+ },
+ });
+ }
+ if (onDelete) {
+ if (menuItems.length > 0) {
+ menuItems.push({ key: "divider", type: "divider" });
+ }
+ menuItems.push({
+ key: "delete",
+ label: "Delete",
+ icon: ,
+ danger: true,
+ onClick: ({ domEvent }) => {
+ domEvent.stopPropagation();
+ onDelete();
+ },
+ });
+ }
+
+ // Card uses role="button" + nested
+ );
+};
+
+interface HealthChipProps {
+ status: string;
+ isLoadingHealth?: boolean;
+ isRechecking?: boolean;
+ onRecheck?: () => void;
+ lastCheck?: string | null;
+ error?: string | null;
+ dotClass: string;
+}
+
+const HealthChip: FC