From af3302768b4fe13a90baa2413b7881c6fba43359 Mon Sep 17 00:00:00 2001 From: Kobi Kadosh Date: Sun, 16 Aug 2026 01:06:54 -0700 Subject: [PATCH 1/3] feat(mcp): add Nimble to MCP registry Adds Nimble (web search, SERP, and site extraction) to the curated MCP server discovery list, alongside the other Search category entries (Brave Search, Exa, Tavily). Uses the documented streamable HTTP MCP endpoint (https://mcp.nimbleway.com/mcp); the legacy /sse endpoint referenced in older docs now returns 410 Gone. --- litellm/proxy/mcp_registry.json | 13 ++++++++++ .../mcp_server/test_mcp_discovery.py | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/litellm/proxy/mcp_registry.json b/litellm/proxy/mcp_registry.json index f37fc39813e..b618961262a 100644 --- a/litellm/proxy/mcp_registry.json +++ b/litellm/proxy/mcp_registry.json @@ -258,6 +258,19 @@ {"name": "EXA_API_KEY", "description": "Exa API Key", "secret": true} ] }, + { + "name": "nimble", + "title": "Nimble", + "description": "Web search, SERP, and site extraction data for AI agents", + "icon_url": "https://avatars.githubusercontent.com/u/94446617?v=4", + "category": "Search", + "registry_url": null, + "transport": "http", + "url": "https://mcp.nimbleway.com/mcp", + "env_vars": [ + {"name": "NIMBLE_API_KEY", "description": "Nimble API Key", "secret": true} + ] + }, { "name": "tavily", "title": "Tavily", diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py index 9a741a3f861..3a14d0d881d 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py @@ -94,6 +94,30 @@ class TestMCPRegistryFile: assert linear["url"] == "https://mcp.linear.app/mcp" assert "/sse" not in linear["url"] + def test_nimble_uses_streamable_http_with_api_key(self, registry_path): + """Nimble should expose its authenticated streamable HTTP endpoint.""" + with open(registry_path, "r") as f: + data = json.load(f) + + nimble = next(s for s in data["servers"] if s["name"] == "nimble") + assert nimble == { + "name": "nimble", + "title": "Nimble", + "description": "Web search, SERP, and site extraction data for AI agents", + "icon_url": "https://avatars.githubusercontent.com/u/94446617?v=4", + "category": "Search", + "registry_url": None, + "transport": "http", + "url": "https://mcp.nimbleway.com/mcp", + "env_vars": [ + { + "name": "NIMBLE_API_KEY", + "description": "Nimble API Key", + "secret": True, + } + ], + } + def test_well_known_servers_present(self, registry_path): """Ensure key well-known MCPs are in the registry.""" with open(registry_path, "r") as f: From 83b0efa8a15fd2bbb1231f493a334d5026497a06 Mon Sep 17 00:00:00 2001 From: Kobi Kadosh Date: Sun, 16 Aug 2026 01:35:03 -0700 Subject: [PATCH 2/3] fix(mcp): prefill discovery authentication --- litellm/proxy/mcp_registry.json | 1 + .../mcp_server/test_mcp_discovery.py | 1 + .../CreateMCPServer.integration.test.tsx | 17 ++++++++++------- .../mcp-servers/_components/CreateMCPServer.tsx | 1 + .../src/components/mcp_tools/types.tsx | 1 + 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/mcp_registry.json b/litellm/proxy/mcp_registry.json index b618961262a..ae3bfb26295 100644 --- a/litellm/proxy/mcp_registry.json +++ b/litellm/proxy/mcp_registry.json @@ -267,6 +267,7 @@ "registry_url": null, "transport": "http", "url": "https://mcp.nimbleway.com/mcp", + "auth_type": "bearer_token", "env_vars": [ {"name": "NIMBLE_API_KEY", "description": "Nimble API Key", "secret": true} ] diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py index 3a14d0d881d..f4ba1e52939 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_discovery.py @@ -109,6 +109,7 @@ class TestMCPRegistryFile: "registry_url": None, "transport": "http", "url": "https://mcp.nimbleway.com/mcp", + "auth_type": "bearer_token", "env_vars": [ { "name": "NIMBLE_API_KEY", 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 c9007e29c3e..85921f02b31 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 @@ -1897,20 +1897,23 @@ describe("CreateMCPServer", () => { describe("when prefillData is provided", () => { it("should populate form fields from discovery data", async () => { const prefillData = { - name: "github-mcp", - title: "GitHub MCP", - description: "GitHub integration server", - category: "Development", + name: "nimble", + title: "Nimble", + description: "Nimble search server", + category: "Search", transport: "http", - url: "https://github-mcp.example.com", + url: "https://mcp.nimbleway.com/mcp", + auth_type: "bearer_token", }; render(); await waitFor(() => { - // Server name should be sanitized (hyphens replaced with underscores) const nameInput = getServerNameInput(); - expect(nameInput).toHaveValue("github_mcp"); + expect(nameInput).toHaveValue("nimble"); + expect(screen.getByPlaceholderText("https://your-mcp-server.com")).toHaveValue("https://mcp.nimbleway.com/mcp"); + expect(screen.getByText("Bearer Token")).toBeInTheDocument(); + expect(screen.getByText("Authentication Value")).toBeInTheDocument(); }); }); }); 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 476e43e49aa..8f52da93615 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 @@ -361,6 +361,7 @@ const CreateMCPServer: React.FC = ({ alias: sanitizedName, description: prefillData.description || "", transport: transport, + auth_type: prefillData.auth_type || undefined, }; if (transport === "stdio") { diff --git a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx index 8d0d6dd0b89..1b2e72d7e83 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx @@ -519,6 +519,7 @@ export interface DiscoverableMCPServer { category: string; registry_url?: string | null; transport: string; + auth_type?: string | null; url?: string | null; command?: string | null; args?: string[] | null; From 26c66fc2dbafcff2c03d472bbf2c556aabd9ee6e Mon Sep 17 00:00:00 2001 From: Kobi Kadosh Date: Sun, 16 Aug 2026 01:37:56 -0700 Subject: [PATCH 3/3] test(mcp): preserve discovery sanitization coverage --- .../CreateMCPServer.integration.test.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) 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 85921f02b31..e6b22e761a0 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 @@ -1895,7 +1895,24 @@ describe("CreateMCPServer", () => { }); describe("when prefillData is provided", () => { - it("should populate form fields from discovery data", async () => { + it("should sanitize the server name from discovery data", async () => { + const prefillData = { + name: "github-mcp", + title: "GitHub MCP", + description: "GitHub integration server", + category: "Development", + transport: "http", + url: "https://github-mcp.example.com", + }; + + render(); + + await waitFor(() => { + expect(getServerNameInput()).toHaveValue("github_mcp"); + }); + }); + + it("should populate URL and auth type from discovery data", async () => { const prefillData = { name: "nimble", title: "Nimble", @@ -1909,8 +1926,7 @@ describe("CreateMCPServer", () => { render(); await waitFor(() => { - const nameInput = getServerNameInput(); - expect(nameInput).toHaveValue("nimble"); + expect(getServerNameInput()).toHaveValue("nimble"); expect(screen.getByPlaceholderText("https://your-mcp-server.com")).toHaveValue("https://mcp.nimbleway.com/mcp"); expect(screen.getByText("Bearer Token")).toBeInTheDocument(); expect(screen.getByText("Authentication Value")).toBeInTheDocument();