diff --git a/litellm/proxy/mcp_registry.json b/litellm/proxy/mcp_registry.json index f37fc39813e..b117f35600d 100644 --- a/litellm/proxy/mcp_registry.json +++ b/litellm/proxy/mcp_registry.json @@ -66,7 +66,7 @@ "name": "slack", "title": "Slack", "description": "Channel management, messaging, and Slack workspace integration", - "icon_url": "https://cdn.simpleicons.org/slack", + "icon_url": "/ui/assets/logos/slack.svg", "category": "Communication", "registry_url": null, "transport": "stdio", @@ -249,7 +249,7 @@ "name": "exa", "title": "Exa", "description": "Fast, intelligent web search and web crawling", - "icon_url": "https://cdn.simpleicons.org/exa", + "icon_url": "/ui/assets/logos/exa_ai.png", "category": "Search", "registry_url": "https://registry.modelcontextprotocol.io/servers/ai.exa%2Fexa", "transport": "http", @@ -262,7 +262,7 @@ "name": "tavily", "title": "Tavily", "description": "AI-optimized search engine for research and retrieval", - "icon_url": "https://cdn.simpleicons.org/tavily", + "icon_url": "/ui/assets/logos/tavily.png", "category": "Search", "registry_url": null, "transport": "stdio", @@ -288,7 +288,7 @@ "name": "playwright", "title": "Playwright", "description": "Browser automation and testing with Playwright", - "icon_url": "https://cdn.simpleicons.org/playwright", + "icon_url": "https://raw.githubusercontent.com/microsoft/playwright/2f6148bcd1a96ec687d55ce08645fc6315b1514e/packages/recorder/public/playwright-logo.svg", "category": "Web & Browser", "registry_url": null, "transport": "stdio", @@ -300,7 +300,7 @@ "name": "browserbase", "title": "Browserbase", "description": "Cloud browser automation and session management", - "icon_url": "https://cdn.simpleicons.org/browserbase", + "icon_url": "https://www.browserbase.com/favicon.svg", "category": "Web & Browser", "registry_url": null, "transport": "stdio", @@ -315,7 +315,7 @@ "name": "aws", "title": "AWS", "description": "Interact with Amazon Web Services resources and APIs", - "icon_url": "https://cdn.simpleicons.org/amazonaws", + "icon_url": "/ui/assets/logos/aws.svg", "category": "Cloud", "registry_url": null, "transport": "stdio", @@ -392,7 +392,7 @@ "name": "twilio", "title": "Twilio", "description": "Send SMS, make calls, and manage communication via Twilio", - "icon_url": "https://cdn.simpleicons.org/twilio", + "icon_url": "/ui/assets/logos/twilio.svg", "category": "Communication", "registry_url": null, "transport": "stdio", diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.integration.test.tsx similarity index 71% rename from ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.test.tsx rename to ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.integration.test.tsx index bdd937a8e6f..02e04a0aa97 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.integration.test.tsx @@ -4,6 +4,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import MCPDiscovery from "./mcp_discovery"; import { fetchDiscoverableMCPServers } from "@/components/networking"; import type { DiscoverableMCPServer } from "@/components/mcp_tools/types"; +import { renderWithProviders } from "../../../../../tests/test-utils"; +import { setServerRootPath } from "@/lib/serverRootPath"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; vi.mock("@/components/networking", () => ({ fetchDiscoverableMCPServers: vi.fn(), @@ -36,12 +41,53 @@ const defaultProps = { describe("MCPDiscovery", () => { beforeEach(() => { vi.clearAllMocks(); + setServerRootPath("/"); vi.mocked(fetchDiscoverableMCPServers).mockResolvedValue({ servers: [githubServer, slackServer], categories: ["Developer Tools", "Communication"], }); }); + it.each(["", "/litellm"])("should render available catalog logos under the %s server root", async (root) => { + const testDirectory = dirname(fileURLToPath(import.meta.url)); + const registry = JSON.parse( + readFileSync(resolve(testDirectory, "../../../../../../../litellm/proxy/mcp_registry.json"), "utf8"), + ) as { servers: DiscoverableMCPServer[] }; + const expectedLogos = [ + ["exa", "/ui/assets/logos/exa_ai.png"], + ["tavily", "/ui/assets/logos/tavily.png"], + ["slack", "/ui/assets/logos/slack.svg"], + ["twilio", "/ui/assets/logos/twilio.svg"], + [ + "playwright", + "https://raw.githubusercontent.com/microsoft/playwright/2f6148bcd1a96ec687d55ce08645fc6315b1514e/packages/recorder/public/playwright-logo.svg", + ], + ["browserbase", "https://www.browserbase.com/favicon.svg"], + ["aws", "/ui/assets/logos/aws.svg"], + ] as const; + setServerRootPath(root); + vi.mocked(fetchDiscoverableMCPServers).mockResolvedValue({ + servers: expectedLogos.map(([name]) => { + const server = registry.servers.find((entry) => entry.name === name)!; + return server; + }), + categories: [], + }); + + renderWithProviders(); + + for (const [name, source] of expectedLogos) { + const server = registry.servers.find((entry) => entry.name === name)!; + if (source.startsWith("/ui/")) { + expect(existsSync(resolve(testDirectory, "../../../../../public", source.slice(4)))).toBe(true); + } + expect(await screen.findByRole("img", { name: server.title })).toHaveAttribute( + "src", + source.startsWith("/ui/") ? `${root}${source}` : source, + ); + } + }); + // Each category name renders twice: once as a filter pill (a button) and once // as the heading of its group. Only the heading is not a button. const groupHeading = (category: string) => screen.getAllByText(category).filter((el) => el.tagName !== "BUTTON");