From c2971654fb8bcf703d4c6a9b18afb01ef2015699 Mon Sep 17 00:00:00 2001 From: Ace Data Cloud Dev Date: Sun, 18 Oct 2026 02:45:21 +0800 Subject: [PATCH] feat(mcp): discover Ace media services by name --- litellm/proxy/mcp_registry.json | 60 +++++++++++++++++++ .../CreateMCPServer.integration.test.tsx | 27 +++++++++ .../_components/CreateMCPServer.tsx | 1 + .../mcp_discovery.integration.test.tsx | 28 +++++++++ .../src/components/mcp_tools/types.tsx | 1 + 5 files changed, 117 insertions(+) diff --git a/litellm/proxy/mcp_registry.json b/litellm/proxy/mcp_registry.json index 70e81f127c9..ed8c5a28ecf 100644 --- a/litellm/proxy/mcp_registry.json +++ b/litellm/proxy/mcp_registry.json @@ -428,6 +428,66 @@ {"name": "SUPABASE_URL", "description": "Supabase Project URL", "secret": false}, {"name": "SUPABASE_SERVICE_ROLE_KEY", "description": "Supabase Service Role Key", "secret": true} ] + }, + { + "name": "suno_acedatacloud", + "title": "Suno by Ace Data Cloud", + "description": "Generate Suno music, lyrics and covers through Ace Data Cloud. Retrieve the same task until final audio is ready. Requires your own Ace API token and service access.", + "icon_url": "https://raw.githubusercontent.com/AceDataCloud/MCPs/4a2abb23a6f19ea8460c4883dabb5ad378a428b7/suno/vscode/icon.png", + "category": "Media Generation", + "registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.AceDataCloud%2Fmcp-suno", + "transport": "http", + "url": "https://suno.mcp.acedata.cloud/mcp", + "auth_type": "bearer_token", + "env_vars": [] + }, + { + "name": "seedance_acedatacloud", + "title": "Seedance by Ace Data Cloud", + "description": "Generate Seedance videos from text or images through Ace Data Cloud. Query the same task for terminal video results. Requires your own Ace API token and service access.", + "icon_url": "https://raw.githubusercontent.com/AceDataCloud/MCPs/4a2abb23a6f19ea8460c4883dabb5ad378a428b7/seedance/vscode/icon.png", + "category": "Media Generation", + "registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.AceDataCloud%2Fmcp-seedance", + "transport": "http", + "url": "https://seedance.mcp.acedata.cloud/mcp", + "auth_type": "bearer_token", + "env_vars": [] + }, + { + "name": "happyhorse_acedatacloud", + "title": "HappyHorse by Ace Data Cloud", + "description": "Generate and edit Happy Horse videos through Ace Data Cloud. Query the same task for final video results. Requires your own Ace API token and service access.", + "icon_url": "https://cdn.acedata.cloud/e58fce2811.jpg", + "category": "Media Generation", + "registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.AceDataCloud%2Fmcp-happyhorse", + "transport": "http", + "url": "https://happyhorse.mcp.acedata.cloud/mcp", + "auth_type": "bearer_token", + "env_vars": [] + }, + { + "name": "gpt_image_acedatacloud", + "title": "GPT Image by Ace Data Cloud", + "description": "Generate and edit GPT Image images through the Ace Data Cloud OpenAI MCP server, which also exposes chat, embeddings and audio tools. Requires your own Ace API token and service access.", + "icon_url": "https://raw.githubusercontent.com/AceDataCloud/MCPs/4a2abb23a6f19ea8460c4883dabb5ad378a428b7/openai/vscode/icon.png", + "category": "Media Generation", + "registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.AceDataCloud%2Fmcp-openai", + "transport": "http", + "url": "https://openai.mcp.acedata.cloud/mcp", + "auth_type": "bearer_token", + "env_vars": [] + }, + { + "name": "nano_banana_acedatacloud", + "title": "Nano Banana by Ace Data Cloud", + "description": "Generate and edit NanoBanana images through Ace Data Cloud. Retrieve the same task for final images. Requires your own Ace API token and service access.", + "icon_url": "https://raw.githubusercontent.com/AceDataCloud/MCPs/4a2abb23a6f19ea8460c4883dabb5ad378a428b7/nanobanana/vscode/icon.png", + "category": "Media Generation", + "registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.AceDataCloud%2Fmcp-nanobanana-pro", + "transport": "http", + "url": "https://nanobanana.mcp.acedata.cloud/mcp", + "auth_type": "bearer_token", + "env_vars": [] } ] } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.integration.test.tsx index 65656a053ce..fb8cbfaf2ab 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.integration.test.tsx @@ -1932,6 +1932,33 @@ describe("CreateMCPServer", () => { }); describe("when prefillData is provided", () => { + it("opens the credential field for an authenticated discovery entry without supplying a token", async () => { + render( + , + ); + + expect(await screen.findByText("Authentication Value")).toBeInTheDocument(); + expect(screen.getByRole("combobox", { name: "Authentication", exact: true })).toHaveTextContent("Bearer Token"); + expect(screen.getByPlaceholderText("https://your-mcp-server.com")).toHaveValue( + "https://suno.mcp.acedata.cloud/mcp", + ); + expect(getServerNameInput()).toHaveValue("suno_acedatacloud"); + expect(screen.getByPlaceholderText("Enter token or secret")).toHaveValue(""); + expect(screen.getByPlaceholderText("Enter token or secret")).toHaveAttribute("type", "password"); + expect(networking.createMCPServer).not.toHaveBeenCalled(); + }); + it("should populate form fields from discovery data", async () => { const prefillData = { name: "github-mcp", diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx index 07618abb9a4..ea3782552eb 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx @@ -384,6 +384,7 @@ const CreateMCPServer: React.FC = ({ alias: sanitizedName, description: prefillData.description || "", transport: transport, + ...(prefillData.auth_type === AUTH_TYPE.BEARER_TOKEN ? { auth_type: AUTH_TYPE.BEARER_TOKEN } : {}), }; if (transport === "stdio") { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.integration.test.tsx index 02e04a0aa97..4eb3cee4a6f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_discovery.integration.test.tsx @@ -131,6 +131,34 @@ describe("MCPDiscovery", () => { expect(onSelectServer).toHaveBeenCalledWith(githubServer); }); + it.each([ + ["suno", "Suno by Ace Data Cloud"], + ["seedance", "Seedance by Ace Data Cloud"], + ["happy horse", "HappyHorse by Ace Data Cloud"], + ["gpt image", "GPT Image by Ace Data Cloud"], + ["nanobanana", "Nano Banana by Ace Data Cloud"], + ])("finds %s from the bundled catalog and selects its authenticated connection", async (query, title) => { + const testDirectory = dirname(fileURLToPath(import.meta.url)); + const registry = JSON.parse( + readFileSync(resolve(testDirectory, "../../../../../../../litellm/proxy/mcp_registry.json"), "utf8"), + ) as { servers: DiscoverableMCPServer[] }; + vi.mocked(fetchDiscoverableMCPServers).mockResolvedValue({ + servers: registry.servers, + categories: [...new Set(registry.servers.map((server) => server.category))], + }); + const onSelectServer = vi.fn(); + renderWithProviders(); + await screen.findByText(title); + + fireEvent.change(screen.getByPlaceholderText("Search servers..."), { target: { value: query } }); + await userEvent.click(await screen.findByText(title)); + + const server = registry.servers.find((entry) => entry.title === title)!; + expect(onSelectServer).toHaveBeenCalledWith(server); + expect(server.auth_type).toBe("bearer_token"); + expect(screen.queryByText("GitHub")).not.toBeInTheDocument(); + }); + it("offers a custom-server escape hatch", async () => { const onCustomServer = vi.fn(); render(); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx index 22fd64f10d8..ea109c7a883 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx @@ -562,6 +562,7 @@ export interface DiscoverableMCPServer { category: string; registry_url?: string | null; transport: string; + auth_type?: "bearer_token" | null; url?: string | null; command?: string | null; args?: string[] | null;