From 38264051b7d28c9ce285037741b95e8532e09ee9 Mon Sep 17 00:00:00 2001 From: Marek Dano Date: Wed, 2 Sep 2026 12:36:01 +0100 Subject: [PATCH 1/2] feat: migrate mcp-server and virtual-server endpoints to v1 API paths Signed-off-by: Marek Dano --- e2e/global-search.spec.ts | 2 +- e2e/servers.spec.ts | 72 +++++----- e2e/virtual-servers.spec.ts | 128 +++++++++--------- src/api/servers.test.ts | 26 ++-- src/api/servers.ts | 16 +-- src/api/virtualServers.test.ts | 14 +- src/api/virtualServers.ts | 14 +- src/components/dashboard/McpHealthCard.tsx | 4 +- src/components/dashboard/SystemView.test.tsx | 11 +- src/components/dashboard/SystemView.tsx | 4 +- .../gateways/SourceSelection.test.tsx | 6 +- src/components/gateways/SourceSelection.tsx | 2 +- .../VirtualServerDetailsPanel.test.tsx | 32 ++--- .../gateways/VirtualServerDetailsPanel.tsx | 8 +- .../mcp-servers/MCPServerForm.test.tsx | 6 +- src/hooks/useMCPServerForm.test.ts | 56 ++++---- src/hooks/useMCPServerForm.ts | 6 +- src/hooks/useMcpServers.test.ts | 8 +- src/hooks/useMcpServers.ts | 6 +- src/hooks/useMiniCardStatuses.test.tsx | 2 +- src/hooks/useMiniCardStatuses.ts | 4 +- src/pages/CreateServer.test.tsx | 50 +++---- src/pages/CreateServer.tsx | 6 +- src/pages/Dashboard.test.tsx | 6 +- src/pages/Dashboard.tsx | 5 +- src/pages/Gateways.test.tsx | 49 ++++--- src/pages/Gateways.tsx | 4 +- src/pages/Resources.test.tsx | 6 +- src/pages/Resources.tsx | 2 +- src/pages/Servers.test.tsx | 18 ++- src/pages/Servers.tsx | 8 +- src/pages/Tools.test.tsx | 2 +- src/pages/Tools.tsx | 2 +- src/test/mocks/handlers.ts | 12 +- 34 files changed, 312 insertions(+), 285 deletions(-) diff --git a/e2e/global-search.spec.ts b/e2e/global-search.spec.ts index 80e4dfae..4d7a7b36 100644 --- a/e2e/global-search.spec.ts +++ b/e2e/global-search.spec.ts @@ -11,7 +11,7 @@ test.describe("Global search", () => { }) => { let searchRequestUrl = ""; - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/servers.spec.ts b/e2e/servers.spec.ts index 33b1383a..f3798f31 100644 --- a/e2e/servers.spec.ts +++ b/e2e/servers.spec.ts @@ -47,7 +47,7 @@ test.describe("MCP Servers page", () => { }); test("shows empty state panel when no servers exist", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -67,7 +67,7 @@ test.describe("MCP Servers page", () => { test("hides connect card when the caller lacks gateways.create", async ({ page, apiMock }) => { await apiMock.mockPermissions({ permissions: ["gateways.read"] }); - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -87,7 +87,7 @@ test.describe("MCP Servers page", () => { apiMock, }) => { await apiMock.mockPermissions({ permissions: ["gateways.read"] }); - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -103,7 +103,7 @@ test.describe("MCP Servers page", () => { }); test("shows servers list with title and Connect button when servers exist", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -120,7 +120,7 @@ test.describe("MCP Servers page", () => { }); test("shows error alert when API fails", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 500, contentType: "application/json", @@ -136,7 +136,7 @@ test.describe("MCP Servers page", () => { }); test("shows both servers when multiple exist", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -152,7 +152,7 @@ test.describe("MCP Servers page", () => { }); test("shows Load More button when nextCursor is present", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { const url = new URL(route.request().url()); const hasCursor = url.searchParams.has("cursor"); @@ -183,7 +183,7 @@ test.describe("MCP Servers page", () => { }); test("hides Load More button when there is no nextCursor", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -198,7 +198,7 @@ test.describe("MCP Servers page", () => { }); test("opens server actions dropdown menu", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -224,14 +224,14 @@ test.describe("MCP Servers page", () => { }) => { let deleteRequestCount = 0; - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ gateways: [MOCK_SERVER, MOCK_SERVER_2], nextCursor: null }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; await route.fulfill({ status: 204 }); @@ -266,7 +266,7 @@ test.describe("MCP Servers page", () => { }); test("cancels delete and keeps server in list", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -292,14 +292,14 @@ test.describe("MCP Servers page", () => { test("rolls back optimistic delete and shows error toast when delete fails", async ({ page }) => { let deleteRequestCount = 0; - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ gateways: [MOCK_SERVER, MOCK_SERVER_2], nextCursor: null }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; await route.fulfill({ @@ -330,14 +330,14 @@ test.describe("MCP Servers page", () => { test("closes details drawer optimistically when the viewed server is deleted", async ({ page, }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ gateways: [MOCK_SERVER, MOCK_SERVER_2], nextCursor: null }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}`, async (route) => { if (route.request().method() === "GET") { await route.fulfill({ status: 200, @@ -348,21 +348,21 @@ test.describe("MCP Servers page", () => { } await route.fulfill({ status: 204 }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}/tools*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/tools*`, (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ tools: [] }), }), ); - await page.route(`**/gateways/${MOCK_SERVER.id}/resources*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/resources*`, (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ resources: [] }), }), ); - await page.route(`**/gateways/${MOCK_SERVER.id}/prompts*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/prompts*`, (route) => route.fulfill({ status: 200, contentType: "application/json", @@ -391,14 +391,14 @@ test.describe("MCP Servers page", () => { }); test("restores details drawer when delete is rolled back after API failure", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ gateways: [MOCK_SERVER, MOCK_SERVER_2], nextCursor: null }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}`, async (route) => { if (route.request().method() === "GET") { await route.fulfill({ status: 200, @@ -414,21 +414,21 @@ test.describe("MCP Servers page", () => { body: JSON.stringify({ detail: "Forbidden" }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}/tools*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/tools*`, (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ tools: [] }), }), ); - await page.route(`**/gateways/${MOCK_SERVER.id}/resources*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/resources*`, (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ resources: [] }), }), ); - await page.route(`**/gateways/${MOCK_SERVER.id}/prompts*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/prompts*`, (route) => route.fulfill({ status: 200, contentType: "application/json", @@ -462,14 +462,14 @@ test.describe("MCP Servers page", () => { }); test("does not close details drawer when a different server is deleted", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ gateways: [MOCK_SERVER, MOCK_SERVER_2], nextCursor: null }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}`, async (route) => { if (route.request().method() === "GET") { await route.fulfill({ status: 200, @@ -478,24 +478,24 @@ test.describe("MCP Servers page", () => { }); } }); - await page.route(`**/gateways/${MOCK_SERVER_2.id}`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER_2.id}`, async (route) => { await route.fulfill({ status: 204 }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}/tools*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/tools*`, (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ tools: [] }), }), ); - await page.route(`**/gateways/${MOCK_SERVER.id}/resources*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/resources*`, (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ resources: [] }), }), ); - await page.route(`**/gateways/${MOCK_SERVER.id}/prompts*`, (route) => + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/prompts*`, (route) => route.fulfill({ status: 200, contentType: "application/json", @@ -527,14 +527,14 @@ test.describe("MCP Servers page", () => { }); test("opens server details panel from actions menu", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ gateways: [MOCK_SERVER], nextCursor: null }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}`, async (route) => { if (route.request().method() === "GET") { await route.fulfill({ status: 200, @@ -543,21 +543,21 @@ test.describe("MCP Servers page", () => { }); } }); - await page.route(`**/gateways/${MOCK_SERVER.id}/tools*`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/tools*`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ tools: [] }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}/resources*`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/resources*`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ resources: [] }), }); }); - await page.route(`**/gateways/${MOCK_SERVER.id}/prompts*`, async (route) => { + await page.route(`**/v1/mcp-servers/${MOCK_SERVER.id}/prompts*`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -577,7 +577,7 @@ test.describe("MCP Servers page", () => { }); test("shows per-page selector in the servers footer", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", diff --git a/e2e/virtual-servers.spec.ts b/e2e/virtual-servers.spec.ts index 5a220651..4fe7b317 100644 --- a/e2e/virtual-servers.spec.ts +++ b/e2e/virtual-servers.spec.ts @@ -86,7 +86,7 @@ test.describe("Virtual Servers page", () => { test("shows connect source card when no virtual servers exist", async ({ page }) => { // Mock empty servers response - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -113,7 +113,7 @@ test.describe("Virtual Servers page", () => { apiMock, }) => { await apiMock.mockPermissions({ permissions: ["servers.read"] }); - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -131,7 +131,7 @@ test.describe("Virtual Servers page", () => { }); test("navigates to create server UI when connect source card is clicked", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -161,7 +161,7 @@ test.describe("Virtual Servers page", () => { test("creates a virtual server when source selection is skipped", async ({ page }) => { let createPayload: unknown = null; - await page.route("**/servers", async (route) => { + await page.route("**/v1/virtual-servers", async (route) => { expect(route.request().method()).toBe("POST"); createPayload = route.request().postDataJSON(); await route.fulfill({ @@ -214,7 +214,7 @@ test.describe("Virtual Servers page", () => { test("shows create API errors and allows retry from source selection", async ({ page }) => { let requestCount = 0; - await page.route("**/servers", async (route) => { + await page.route("**/v1/virtual-servers", async (route) => { expect(route.request().method()).toBe("POST"); requestCount += 1; if (requestCount === 1) { @@ -246,7 +246,7 @@ test.describe("Virtual Servers page", () => { }); test("shows empty and failed MCP source states during source selection", async ({ page }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -263,8 +263,8 @@ test.describe("Virtual Servers page", () => { await expect(page.getByText("No MCP servers found.")).toBeVisible(); - await page.unroute("**/gateways?*"); - await page.route("**/gateways?*", async (route) => { + await page.unroute("**/v1/mcp-servers?*"); + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 500, contentType: "application/json", @@ -285,7 +285,7 @@ test.describe("Virtual Servers page", () => { test("shows component fetch errors before creating from selected MCP sources", async ({ page, }) => { - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -329,7 +329,7 @@ test.describe("Virtual Servers page", () => { test("shows virtual servers list when servers exist", async ({ page }) => { // Mock servers response with data - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -353,7 +353,7 @@ test.describe("Virtual Servers page", () => { }); test("displays server details correctly", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -381,7 +381,7 @@ test.describe("Virtual Servers page", () => { }); test("opens server actions dropdown menu with state action", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -413,14 +413,14 @@ test.describe("Virtual Servers page", () => { const disabledServer = { ...MOCK_VIRTUAL_SERVER, enabled: false }; let stateRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [disabledServer] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}/state?*`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}/state?*`, async (route) => { expect(route.request().method()).toBe("POST"); expect(new URL(route.request().url()).searchParams.get("activate")).toBe("true"); stateRequestCount += 1; @@ -453,14 +453,14 @@ test.describe("Virtual Servers page", () => { test("requires confirmation to deactivate and supports cancellation", async ({ page }) => { let stateRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}/state?*`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}/state?*`, async (route) => { expect(route.request().method()).toBe("POST"); expect(new URL(route.request().url()).searchParams.get("activate")).toBe("false"); stateRequestCount += 1; @@ -510,14 +510,14 @@ test.describe("Virtual Servers page", () => { }); let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; await deleteCanFinish; @@ -554,14 +554,14 @@ test.describe("Virtual Servers page", () => { releaseDelete = resolve; }); - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { await deleteCanFinish; await route.fulfill({ status: 204 }); }); @@ -587,7 +587,7 @@ test.describe("Virtual Servers page", () => { let listRequestCount = 0; let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { listRequestCount += 1; await route.fulfill({ status: 200, @@ -595,7 +595,7 @@ test.describe("Virtual Servers page", () => { body: JSON.stringify({ servers: isDeleted ? [] : [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; isDeleted = true; @@ -633,14 +633,14 @@ test.describe("Virtual Servers page", () => { releaseDelete = resolve; }); - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; await deleteCanFinish; @@ -680,14 +680,14 @@ test.describe("Virtual Servers page", () => { }); let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; await deleteCanFinish; @@ -726,14 +726,14 @@ test.describe("Virtual Servers page", () => { test("shows delete failures in a toast instead of the page content", async ({ page }) => { let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; await route.fulfill({ @@ -762,7 +762,7 @@ test.describe("Virtual Servers page", () => { }); test("cancels delete dialog and keeps the virtual server card visible", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -799,14 +799,14 @@ test.describe("Virtual Servers page", () => { }; let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER, SIBLING] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { expect(route.request().method()).toBe("DELETE"); deleteRequestCount += 1; await route.fulfill({ status: 204 }); @@ -851,14 +851,14 @@ test.describe("Virtual Servers page", () => { }); let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { if (route.request().method() === "DELETE") { deleteRequestCount += 1; await deleteCanFinish; // hold the response @@ -910,14 +910,14 @@ test.describe("Virtual Servers page", () => { }); let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { if (route.request().method() === "DELETE") { deleteRequestCount += 1; await deleteCanFinish; @@ -971,14 +971,14 @@ test.describe("Virtual Servers page", () => { }) => { let deleteRequestCount = 0; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { if (route.request().method() === "DELETE") { deleteRequestCount += 1; await route.fulfill({ status: 204 }); @@ -1023,14 +1023,14 @@ test.describe("Virtual Servers page", () => { }); test("opens virtual server details panel from row actions", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1090,14 +1090,14 @@ test.describe("Virtual Servers page", () => { test("shows a tooltip with the full endpoint when it's truncated in the Try it tab", async ({ page, }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1144,14 +1144,14 @@ test.describe("Virtual Servers page", () => { }); test("details panel add source button navigates to edit the virtual server", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ servers: [MOCK_VIRTUAL_SERVER] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1177,7 +1177,7 @@ test.describe("Virtual Servers page", () => { }); test("shows only the actions menu in the virtual server card header", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1193,7 +1193,7 @@ test.describe("Virtual Servers page", () => { }); test("navigates to create server UI from the create server card", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1212,7 +1212,7 @@ test.describe("Virtual Servers page", () => { test("shows error state when API fails", async ({ page }) => { // Mock API error - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 500, contentType: "application/json", @@ -1231,7 +1231,7 @@ test.describe("Virtual Servers page", () => { test("handles disabled server correctly", async ({ page }) => { const disabledServer = { ...MOCK_VIRTUAL_SERVER, enabled: false, tags: ["public", "disabled"] }; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1260,7 +1260,7 @@ test.describe("Virtual Servers page", () => { tags: ["private", "disabled"], }; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1294,7 +1294,7 @@ test.describe("Virtual Servers page", () => { tags: [], }; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1318,7 +1318,7 @@ test.describe("Virtual Servers page", () => { }); test("connect source card is keyboard accessible", async ({ page }) => { - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1348,7 +1348,7 @@ test.describe("Virtual Servers page", () => { updatedAt: "", }; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1372,7 +1372,7 @@ test.describe("Virtual Servers page", () => { associatedPrompts: [], }; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1402,7 +1402,7 @@ test.describe("Virtual Servers page", () => { associatedPrompts: [], }; - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1427,7 +1427,7 @@ test.describe("Virtual Servers page", () => { test("creates a virtual server with components from a selected MCP server", async ({ page }) => { let createPayload: unknown = null; - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1466,7 +1466,7 @@ test.describe("Virtual Servers page", () => { body: JSON.stringify({ prompts: [{ id: "prompt-from-mcp", name: "Prompt from MCP" }] }), }); }); - await page.route("**/servers", async (route) => { + await page.route("**/v1/virtual-servers", async (route) => { expect(route.request().method()).toBe("POST"); createPayload = route.request().postDataJSON(); await route.fulfill({ @@ -1482,7 +1482,7 @@ test.describe("Virtual Servers page", () => { }), }); }); - await page.route("**/servers?*", async (route) => { + await page.route("**/v1/virtual-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1522,7 +1522,7 @@ test.describe("Virtual Servers page", () => { }) => { let createPayload: unknown = null; - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1571,7 +1571,7 @@ test.describe("Virtual Servers page", () => { body: JSON.stringify({ prompts: [{ id: "shared-prompt", name: "Shared Prompt" }] }), }); }); - await page.route("**/servers", async (route) => { + await page.route("**/v1/virtual-servers", async (route) => { expect(route.request().method()).toBe("POST"); createPayload = route.request().postDataJSON(); await route.fulfill({ @@ -1618,7 +1618,7 @@ test.describe("Virtual Servers page", () => { teamId: "team-1", }; - await page.route("**/gateways?*", async (route) => { + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", @@ -1653,7 +1653,7 @@ test.describe("Virtual Servers page", () => { body: JSON.stringify({ prompts: [{ id: "existing-prompt-id", name: "Existing Prompt" }] }), }); }); - await page.route(`**/servers/${editServer.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${editServer.id}`, async (route) => { if (route.request().method() === "PUT") { updatePayload = route.request().postDataJSON(); await route.fulfill({ @@ -1698,7 +1698,7 @@ test.describe("Virtual Servers page", () => { }); test("shows edit load and update failures", async ({ page }) => { - await page.route("**/servers/missing-server", async (route) => { + await page.route("**/v1/virtual-servers/missing-server", async (route) => { await route.fulfill({ status: 404, contentType: "application/json", @@ -1709,15 +1709,15 @@ test.describe("Virtual Servers page", () => { await page.goto("/app/gateways/create-server?editServerId=missing-server"); await expect(page.getByRole("alert")).toHaveText("HTTP 404"); - await page.unroute("**/servers/missing-server"); - await page.route("**/gateways?*", async (route) => { + await page.unroute("**/v1/virtual-servers/missing-server"); + await page.route("**/v1/mcp-servers?*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ gateways: [] }), }); }); - await page.route(`**/servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { + await page.route(`**/v1/virtual-servers/${MOCK_VIRTUAL_SERVER.id}`, async (route) => { if (route.request().method() === "PUT") { await route.fulfill({ status: 500, diff --git a/src/api/servers.test.ts b/src/api/servers.test.ts index 3d99b937..079809d5 100644 --- a/src/api/servers.test.ts +++ b/src/api/servers.test.ts @@ -31,7 +31,7 @@ describe("serversApi", () => { expect(result).toEqual({ status: "success", message: "Server activated" }); expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/gateways/server-123/state?activate=true"), + expect.stringContaining("/v1/mcp-servers/server-123/state?activate=true"), expect.objectContaining({ method: "POST", headers: expect.objectContaining({ @@ -55,7 +55,7 @@ describe("serversApi", () => { expect(result).toEqual({ status: "success", message: "Server deactivated" }); expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/gateways/server-123/state?activate=false"), + expect.stringContaining("/v1/mcp-servers/server-123/state?activate=false"), expect.anything(), ); }); @@ -85,7 +85,7 @@ describe("serversApi", () => { await serversApi.toggleEnabled("server-123", true); expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/gateways/server-123/state?activate=true"), + expect.stringContaining("/v1/mcp-servers/server-123/state?activate=true"), expect.objectContaining({ headers: expect.objectContaining({ "Content-Type": "application/json", @@ -296,7 +296,7 @@ describe("serversApi", () => { }); describe("updateTags", () => { - it("PUTs /gateways/:id with a tags-only body and returns the updated server", async () => { + it("PUTs /v1/mcp-servers/:id with a tags-only body and returns the updated server", async () => { const updated = { id: "server-123", tags: [{ id: "prod", label: "prod" }] }; mockFetch.mockResolvedValueOnce( new Response(JSON.stringify(updated), { @@ -308,7 +308,7 @@ describe("serversApi", () => { const result = await serversApi.updateTags("server-123", ["prod"]); expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/gateways/server-123"), + expect.stringContaining("/v1/mcp-servers/server-123"), expect.objectContaining({ method: "PUT", body: JSON.stringify({ tags: ["prod"] }), @@ -341,7 +341,7 @@ describe("serversApi", () => { headers: { "Content-Type": "application/json" }, }); - it("requests /gateways with only include_pagination by default", async () => { + it("requests /v1/mcp-servers with only include_pagination by default", async () => { const body = { servers: [], pagination: { nextCursor: null } }; mockFetch.mockResolvedValueOnce(jsonResponse(body)); @@ -349,7 +349,7 @@ describe("serversApi", () => { expect(result).toEqual(body); const url = mockFetch.mock.calls[0][0] as string; - expect(url).toContain("/gateways?"); + expect(url).toContain("/v1/mcp-servers?"); expect(url).toContain("include_pagination=true"); expect(url).not.toContain("cursor="); expect(url).not.toContain("limit="); @@ -400,7 +400,7 @@ describe("serversApi", () => { headers: { "Content-Type": "application/json" }, }); - it("fetches /gateways/:id and returns the server", async () => { + it("fetches /v1/mcp-servers/:id and returns the server", async () => { const server = { id: "get-basic", name: "Basic" }; mockFetch.mockResolvedValueOnce(jsonResponse(server)); @@ -408,7 +408,7 @@ describe("serversApi", () => { expect(result).toEqual(server); expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/gateways/get-basic"), + expect.stringContaining("/v1/mcp-servers/get-basic"), expect.objectContaining({ method: "GET" }), ); }); @@ -444,7 +444,7 @@ describe("serversApi", () => { }); describe("testConnection", () => { - it("POSTs /gateways/:id/test and returns the result", async () => { + it("POSTs /v1/mcp-servers/:id/test and returns the result", async () => { mockFetch.mockResolvedValueOnce( new Response(JSON.stringify({ success: true, message: "Reachable" }), { status: 200, @@ -456,7 +456,7 @@ describe("serversApi", () => { expect(result).toEqual({ success: true, message: "Reachable" }); expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/gateways/server-123/test"), + expect.stringContaining("/v1/mcp-servers/server-123/test"), expect.objectContaining({ method: "POST" }), ); }); @@ -467,7 +467,7 @@ describe("serversApi", () => { }); describe("delete", () => { - it("DELETEs /gateways/:id", async () => { + it("DELETEs /v1/mcp-servers/:id", async () => { mockFetch.mockResolvedValueOnce( new Response(JSON.stringify({}), { status: 200, @@ -478,7 +478,7 @@ describe("serversApi", () => { await serversApi.delete("server-123"); expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining("/gateways/server-123"), + expect.stringContaining("/v1/mcp-servers/server-123"), expect.objectContaining({ method: "DELETE" }), ); }); diff --git a/src/api/servers.ts b/src/api/servers.ts index 4f7b6b91..12aff50c 100644 --- a/src/api/servers.ts +++ b/src/api/servers.ts @@ -1,7 +1,7 @@ /** * MCP Servers API service * - * Wraps /gateways backend endpoint but exposes as "servers" API + * Wraps /v1/mcp-servers backend endpoint but exposes as "servers" API * for frontend consistency. */ @@ -68,7 +68,7 @@ export const serversApi = { searchParams.set("include_pagination", "true"); const query = searchParams.toString(); - return api.get(`/gateways${query ? `?${query}` : ""}`, undefined, params?.signal); + return api.get(`/v1/mcp-servers${query ? `?${query}` : ""}`, undefined, params?.signal); }, /** @@ -82,7 +82,7 @@ export const serversApi = { return cachedRequest; } - const request = api.get(`/gateways/${validId}`); + const request = api.get(`/v1/mcp-servers/${validId}`); serverByIdRequestCache.set(validId, request); request.catch(() => { @@ -97,19 +97,19 @@ export const serversApi = { */ delete: (id: string): Promise => { const validId = validateServerId(id); - return api.delete(`/gateways/${validId}`); + return api.delete(`/v1/mcp-servers/${validId}`); }, /** * Replace an MCP server's (gateway's) tags. * - * Sends a partial `PUT /gateways/{id}` carrying only `tags`; the gateway + * Sends a partial `PUT /v1/mcp-servers/{id}` carrying only `tags`; the gateway * update service preserves every other field when it is omitted. Returns the * updated server so callers can patch their cache with the normalized tags. */ updateTags: (id: string, tags: string[]): Promise => { const validId = validateServerId(id); - return api.put(`/gateways/${validId}`, { tags }); + return api.put(`/v1/mcp-servers/${validId}`, { tags }); }, /** @@ -117,7 +117,7 @@ export const serversApi = { */ testConnection: (id: string): Promise<{ success: boolean; message: string }> => { const validId = validateServerId(id); - return api.post(`/gateways/${validId}/test`, {}); + return api.post(`/v1/mcp-servers/${validId}/test`, {}); }, /** @@ -156,7 +156,7 @@ export const serversApi = { */ toggleEnabled: (id: string, enabled: boolean): Promise<{ status: string; message: string }> => { const validId = validateServerId(id); - return api.post(`/gateways/${validId}/state?activate=${enabled}`); + return api.post(`/v1/mcp-servers/${validId}/state?activate=${enabled}`); }, /** diff --git a/src/api/virtualServers.test.ts b/src/api/virtualServers.test.ts index 5b22ab62..da603f30 100644 --- a/src/api/virtualServers.test.ts +++ b/src/api/virtualServers.test.ts @@ -24,7 +24,7 @@ describe("virtualServers API", () => { vi.mocked(api.put).mockReset(); }); - it("builds the create payload expected by POST /servers", () => { + it("builds the create payload expected by POST /v1/virtual-servers", () => { expect( buildCreateVirtualServerPayload({ name: "Research server", @@ -241,7 +241,7 @@ describe("virtualServers API", () => { await deleteVirtualServer("gateway/1?mode=delete"); - expect(api.delete).toHaveBeenCalledWith("/servers/gateway%2F1%3Fmode%3Ddelete"); + expect(api.delete).toHaveBeenCalledWith("/v1/virtual-servers/gateway%2F1%3Fmode%3Ddelete"); }); it("sets a virtual server state using an encoded id and explicit target state", async () => { @@ -250,11 +250,11 @@ describe("virtualServers API", () => { const result = await setVirtualServerState("gateway/1", false); - expect(api.post).toHaveBeenCalledWith("/servers/gateway%2F1/state?activate=false"); + expect(api.post).toHaveBeenCalledWith("/v1/virtual-servers/gateway%2F1/state?activate=false"); expect(result).toBe(updated); }); - it("builds the update payload expected by PUT /servers/{id}", () => { + it("builds the update payload expected by PUT /v1/virtual-servers/{id}", () => { const payload = buildUpdateVirtualServerPayload({ name: "Updated research server", description: "", @@ -298,13 +298,13 @@ describe("virtualServers API", () => { expect(payload).not.toHaveProperty("oauth_config"); }); - it("PUTs /servers/:id with a tags-only body via updateVirtualServerTags", async () => { + it("PUTs /v1/virtual-servers/:id with a tags-only body via updateVirtualServerTags", async () => { const updated = { id: "server-1", tags: [{ id: "prod", label: "prod" }] }; vi.mocked(api.put).mockResolvedValue(updated); const result = await updateVirtualServerTags("server-1", ["prod"]); - expect(api.put).toHaveBeenCalledWith("/servers/server-1", { tags: ["prod"] }); + expect(api.put).toHaveBeenCalledWith("/v1/virtual-servers/server-1", { tags: ["prod"] }); expect(result).toBe(updated); }); @@ -313,7 +313,7 @@ describe("virtualServers API", () => { await updateVirtualServerTags("team/1", ["x"]); - expect(api.put).toHaveBeenCalledWith("/servers/team%2F1", { tags: ["x"] }); + expect(api.put).toHaveBeenCalledWith("/v1/virtual-servers/team%2F1", { tags: ["x"] }); }); describe("testVirtualServerHandshake", () => { diff --git a/src/api/virtualServers.ts b/src/api/virtualServers.ts index bab82638..47ec9e3c 100644 --- a/src/api/virtualServers.ts +++ b/src/api/virtualServers.ts @@ -61,15 +61,17 @@ export function buildCreateVirtualServerPayload( } export function createVirtualServer(details: CreateServerDetails): Promise { - return api.post("/servers", buildCreateVirtualServerPayload(details)); + return api.post("/v1/virtual-servers", buildCreateVirtualServerPayload(details)); } export function deleteVirtualServer(id: string): Promise { - return api.delete(`/servers/${encodeURIComponent(id)}`); + return api.delete(`/v1/virtual-servers/${encodeURIComponent(id)}`); } export function setVirtualServerState(id: string, activate: boolean): Promise { - return api.post(`/servers/${encodeURIComponent(id)}/state?activate=${activate}`); + return api.post( + `/v1/virtual-servers/${encodeURIComponent(id)}/state?activate=${activate}`, + ); } export function buildUpdateVirtualServerPayload( @@ -104,7 +106,7 @@ export function updateVirtualServer( details: CreateServerDetails, ): Promise { return api.put( - `/servers/${encodeURIComponent(serverId)}`, + `/v1/virtual-servers/${encodeURIComponent(serverId)}`, buildUpdateVirtualServerPayload(details), ); } @@ -112,13 +114,13 @@ export function updateVirtualServer( /** * Replace a virtual server's tags. * - * Sends a partial `PUT /servers/{id}` carrying only `tags`; the server update + * Sends a partial `PUT /v1/virtual-servers/{id}` carrying only `tags`; the server update * service preserves every other field (name, visibility, associated * tools/resources/prompts, ...) when it is omitted. Returns the updated server * so callers can patch their cache with the backend-normalized tags. */ export function updateVirtualServerTags(serverId: string, tags: string[]): Promise { - return api.put(`/servers/${encodeURIComponent(serverId)}`, { tags }); + return api.put(`/v1/virtual-servers/${encodeURIComponent(serverId)}`, { tags }); } /** diff --git a/src/components/dashboard/McpHealthCard.tsx b/src/components/dashboard/McpHealthCard.tsx index 43aacf21..50aca09c 100644 --- a/src/components/dashboard/McpHealthCard.tsx +++ b/src/components/dashboard/McpHealthCard.tsx @@ -1,7 +1,7 @@ /** * McpHealthCard (#5842). * - * Per-server MCP reachability roster over `GET /gateways` (React "MCP servers"): + * Per-server MCP reachability roster over `GET /v1/mcp-servers` (React "MCP servers"): * a "Servers" title with a "Refreshed X ago" hint, a top-right fleet status * ("Reachable" / "Reduced coverage" / "Unreachable" / "Disabled"), an inline * "X of N" summary with component totals, one row per server, and Postgres/Redis @@ -11,7 +11,7 @@ * live in the Activity feed, not here. See `mcpServerRoster.ts` for the pure * classification logic. * - * Permissions: `/gateways` is RBAC-scoped server-side (`gateways.read` + token + * Permissions: `/v1/mcp-servers` is RBAC-scoped server-side (`gateways.read` + token * teams), so the card self-gates — a caller without `gateways.read` gets a 403 * we surface as PermissionDenied, and a scoped caller only ever sees (and counts) * their own servers. The footer chips come from the admin-only `/version` diff --git a/src/components/dashboard/SystemView.test.tsx b/src/components/dashboard/SystemView.test.tsx index 22ea2adf..375c32ff 100644 --- a/src/components/dashboard/SystemView.test.tsx +++ b/src/components/dashboard/SystemView.test.tsx @@ -67,8 +67,8 @@ const METRICS = { function mockByPath(overrides: Record = {}) { const byPath: Record = { "/metrics": METRICS, - "/servers?limit=0&include_inactive=true": [{ enabled: true }, { enabled: false }], - "/gateways?limit=0&include_inactive=true": [ + "/v1/virtual-servers?limit=0&include_inactive=true": [{ enabled: true }, { enabled: false }], + "/v1/mcp-servers?limit=0&include_inactive=true": [ { enabled: true }, { enabled: true }, { enabled: true, reachable: false }, @@ -137,8 +137,11 @@ describe("SystemView", () => { return result(undefined, false, { message: "disabled" }); const byPath: Record = { "/metrics": METRICS, - "/servers?limit=0&include_inactive=true": [{ enabled: true }, { enabled: false }], - "/gateways?limit=0&include_inactive=true": [ + "/v1/virtual-servers?limit=0&include_inactive=true": [ + { enabled: true }, + { enabled: false }, + ], + "/v1/mcp-servers?limit=0&include_inactive=true": [ { enabled: true }, { enabled: true }, { enabled: true, reachable: false }, diff --git a/src/components/dashboard/SystemView.tsx b/src/components/dashboard/SystemView.tsx index f0b558e6..b823207a 100644 --- a/src/components/dashboard/SystemView.tsx +++ b/src/components/dashboard/SystemView.tsx @@ -26,8 +26,8 @@ import { import { VirtualServersCard } from "./VirtualServersCard"; const METRICS_PATH = "/metrics"; -const SERVERS_PATH = "/servers?limit=0&include_inactive=true"; -const GATEWAYS_PATH = "/gateways?limit=0&include_inactive=true"; +const SERVERS_PATH = "/v1/virtual-servers?limit=0&include_inactive=true"; +const GATEWAYS_PATH = "/v1/mcp-servers?limit=0&include_inactive=true"; const A2A_PATH = "/a2a?limit=0&include_inactive=true"; const TOOLS_PATH = "/tools?limit=0&include_inactive=true"; const RESOURCES_PATH = "/resources?limit=0&include_inactive=true"; diff --git a/src/components/gateways/SourceSelection.test.tsx b/src/components/gateways/SourceSelection.test.tsx index ef833624..7bda16bd 100644 --- a/src/components/gateways/SourceSelection.test.tsx +++ b/src/components/gateways/SourceSelection.test.tsx @@ -65,7 +65,7 @@ describe("SourceSelection", () => { const user = userEvent.setup(); let gatewaysRequestCount = 0; server.use( - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { gatewaysRequestCount += 1; return HttpResponse.json({ gateways: [ @@ -143,7 +143,7 @@ describe("SourceSelection", () => { it("shows an empty message when no MCP servers are available", async () => { const user = userEvent.setup(); server.use( - http.get("*/gateways", () => + http.get("*/v1/mcp-servers", () => HttpResponse.json({ gateways: [], }), @@ -172,7 +172,7 @@ describe("SourceSelection", () => { it("shows an alert when MCP server loading fails", async () => { const user = userEvent.setup(); server.use( - http.get("*/gateways", () => + http.get("*/v1/mcp-servers", () => HttpResponse.json({ detail: "Gateway list failed" }, { status: 500 }), ), ); diff --git a/src/components/gateways/SourceSelection.tsx b/src/components/gateways/SourceSelection.tsx index 406b2864..9efa0f73 100644 --- a/src/components/gateways/SourceSelection.tsx +++ b/src/components/gateways/SourceSelection.tsx @@ -26,7 +26,7 @@ import { cn } from "@/lib/utils"; import { STATUS_ICON } from "@/lib/status"; import type { MCPServer, ServerStatus } from "@/types/server"; -const MCP_SERVERS_QUERY_PATH = "/gateways?limit=100&include_inactive=true"; +const MCP_SERVERS_QUERY_PATH = "/v1/mcp-servers?limit=100&include_inactive=true"; type ListedMCPServer = MCPServer & { tool_count?: number; diff --git a/src/components/gateways/VirtualServerDetailsPanel.test.tsx b/src/components/gateways/VirtualServerDetailsPanel.test.tsx index 0993320d..91078b77 100644 --- a/src/components/gateways/VirtualServerDetailsPanel.test.tsx +++ b/src/components/gateways/VirtualServerDetailsPanel.test.tsx @@ -91,9 +91,9 @@ describe("VirtualServerDetailsPanel components list", () => { // The panel fetches tools/resources/prompts when open; return empty so it // falls back to the server's associated* arrays for rendering. mswServer.use( - http.get("*/servers/:id/tools", () => HttpResponse.json({ tools: [] })), - http.get("*/servers/:id/resources", () => HttpResponse.json({ resources: [] })), - http.get("*/servers/:id/prompts", () => HttpResponse.json({ prompts: [] })), + http.get("*/v1/virtual-servers/:id/tools", () => HttpResponse.json({ tools: [] })), + http.get("*/v1/virtual-servers/:id/resources", () => HttpResponse.json({ resources: [] })), + http.get("*/v1/virtual-servers/:id/prompts", () => HttpResponse.json({ prompts: [] })), ); vi.mocked(copyToClipboard).mockClear(); }); @@ -178,7 +178,7 @@ describe("VirtualServerDetailsPanel components list", () => { const user = userEvent.setup(); // Fetched components carry a gateway_id, which drives the source tabs. mswServer.use( - http.get("*/servers/:id/tools", () => + http.get("*/v1/virtual-servers/:id/tools", () => HttpResponse.json({ tools: [ { id: "t1", name: "Tool One", originalName: "tool_one", gateway_id: "gwA" }, @@ -186,7 +186,7 @@ describe("VirtualServerDetailsPanel components list", () => { ], }), ), - http.get("*/gateways", () => + http.get("*/v1/mcp-servers", () => HttpResponse.json({ gateways: [ { id: "gwA", name: "Gateway A" }, @@ -258,9 +258,9 @@ describe("VirtualServerDetailsPanel components list", () => { describe("VirtualServerDetailsPanel render variants", () => { beforeEach(() => { mswServer.use( - http.get("*/servers/:id/tools", () => HttpResponse.json({ tools: [] })), - http.get("*/servers/:id/resources", () => HttpResponse.json({ resources: [] })), - http.get("*/servers/:id/prompts", () => HttpResponse.json({ prompts: [] })), + http.get("*/v1/virtual-servers/:id/tools", () => HttpResponse.json({ tools: [] })), + http.get("*/v1/virtual-servers/:id/resources", () => HttpResponse.json({ resources: [] })), + http.get("*/v1/virtual-servers/:id/prompts", () => HttpResponse.json({ prompts: [] })), ); }); @@ -374,11 +374,11 @@ describe("VirtualServerDetailsPanel render variants", () => { it("handles component responses returned as bare arrays", async () => { const user = userEvent.setup(); mswServer.use( - http.get("*/servers/:id/tools", () => + http.get("*/v1/virtual-servers/:id/tools", () => HttpResponse.json([{ id: "t1", name: "arr_tool", originalName: "arr_tool" }]), ), - http.get("*/servers/:id/resources", () => HttpResponse.json([])), - http.get("*/servers/:id/prompts", () => HttpResponse.json([])), + http.get("*/v1/virtual-servers/:id/resources", () => HttpResponse.json([])), + http.get("*/v1/virtual-servers/:id/prompts", () => HttpResponse.json([])), ); render( { beforeEach(() => { mswServer.use( - http.get("*/servers/:id/tools", () => HttpResponse.json({ tools: [] })), - http.get("*/servers/:id/resources", () => HttpResponse.json({ resources: [] })), - http.get("*/servers/:id/prompts", () => HttpResponse.json({ prompts: [] })), + http.get("*/v1/virtual-servers/:id/tools", () => HttpResponse.json({ tools: [] })), + http.get("*/v1/virtual-servers/:id/resources", () => HttpResponse.json({ resources: [] })), + http.get("*/v1/virtual-servers/:id/prompts", () => HttpResponse.json({ prompts: [] })), ); }); @@ -492,7 +492,7 @@ describe("VirtualServerDetailsPanel test connection tab", () => { it("flags a component-count mismatch using the panel's own aggregated counts", async () => { const user = userEvent.setup(); mswServer.use( - http.get("*/servers/:id/tools", () => + http.get("*/v1/virtual-servers/:id/tools", () => HttpResponse.json({ tools: [{ id: "t1", name: "tool-1", originalName: "tool-1" }] }), ), http.post(HANDSHAKE_ENDPOINT, () => @@ -531,7 +531,7 @@ describe("VirtualServerDetailsPanel test connection tab", () => { // must not count toward the aggregate or it would permanently mismatch. const user = userEvent.setup(); mswServer.use( - http.get("*/servers/:id/tools", () => + http.get("*/v1/virtual-servers/:id/tools", () => HttpResponse.json({ tools: [ { id: "t1", name: "tool-1", originalName: "tool-1", enabled: true }, diff --git a/src/components/gateways/VirtualServerDetailsPanel.tsx b/src/components/gateways/VirtualServerDetailsPanel.tsx index b1ea4438..26746d9b 100644 --- a/src/components/gateways/VirtualServerDetailsPanel.tsx +++ b/src/components/gateways/VirtualServerDetailsPanel.tsx @@ -214,19 +214,19 @@ export function VirtualServerDetailsPanel({ const toolsPath = useMemo(() => { if (!server?.id) return ""; const params = new URLSearchParams({ include_inactive: "true" }); - return `/servers/${encodeURIComponent(server.id)}/tools?${params}`; + return `/v1/virtual-servers/${encodeURIComponent(server.id)}/tools?${params}`; }, [server?.id]); const resourcesPath = useMemo(() => { if (!server?.id) return ""; const params = new URLSearchParams({ include_inactive: "true" }); - return `/servers/${encodeURIComponent(server.id)}/resources?${params}`; + return `/v1/virtual-servers/${encodeURIComponent(server.id)}/resources?${params}`; }, [server?.id]); const promptsPath = useMemo(() => { if (!server?.id) return ""; const params = new URLSearchParams({ include_inactive: "true" }); - return `/servers/${encodeURIComponent(server.id)}/prompts?${params}`; + return `/v1/virtual-servers/${encodeURIComponent(server.id)}/prompts?${params}`; }, [server?.id]); // Fetch components data - only when panel is open and server exists @@ -315,7 +315,7 @@ export function VirtualServerDetailsPanel({ const sourcesPath = useMemo(() => { const params = new URLSearchParams({ limit: "100", include_inactive: "true" }); - return `/gateways?${params}`; + return `/v1/mcp-servers?${params}`; }, []); const { data: sourcesData, isLoading: sourcesLoading } = useQuery< diff --git a/src/components/mcp-servers/MCPServerForm.test.tsx b/src/components/mcp-servers/MCPServerForm.test.tsx index 4ed00174..3a09a462 100644 --- a/src/components/mcp-servers/MCPServerForm.test.tsx +++ b/src/components/mcp-servers/MCPServerForm.test.tsx @@ -35,11 +35,11 @@ const server = setupServer( return HttpResponse.json({ authenticated: false }); }), // Mock gateway creation - http.post("/api/gateways", () => { + http.post("/api/v1/mcp-servers", () => { return HttpResponse.json({ id: "test-gateway-123", name: "Test Server" }); }), // Mock single gateway fetch (used in edit mode) - http.get("/api/gateways/:id", ({ params }) => { + http.get("/api/v1/mcp-servers/:id", ({ params }) => { return HttpResponse.json({ id: params.id, name: "Test Server", @@ -327,7 +327,7 @@ describe("MCPServerForm", () => { ], }), ), - http.get("/api/gateways/:id", ({ params }) => + http.get("/api/v1/mcp-servers/:id", ({ params }) => HttpResponse.json({ id: params.id, name: "Test Server", diff --git a/src/hooks/useMCPServerForm.test.ts b/src/hooks/useMCPServerForm.test.ts index 81c1731b..459da9e7 100644 --- a/src/hooks/useMCPServerForm.test.ts +++ b/src/hooks/useMCPServerForm.test.ts @@ -881,7 +881,7 @@ describe("useMCPServerForm", () => { describe("Edit Mode - Form Population from API", () => { it("maps 'authheaders' from API response to 'custom' auth type", async () => { server.use( - http.get("/api/gateways/gw-1", () => + http.get("/api/v1/mcp-servers/gw-1", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -898,7 +898,7 @@ describe("useMCPServerForm", () => { it("maps 'query_param' from API response to 'query' auth type", async () => { server.use( - http.get("/api/gateways/gw-2", () => + http.get("/api/v1/mcp-servers/gw-2", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -918,7 +918,7 @@ describe("useMCPServerForm", () => { it("populates advanced settings and OAuth config from the API", async () => { server.use( - http.get("/api/gateways/gw-adv", () => + http.get("/api/v1/mcp-servers/gw-adv", () => HttpResponse.json({ name: "Advanced Server", url: "http://localhost:3000", @@ -959,7 +959,7 @@ describe("useMCPServerForm", () => { it("accepts OAuth scopes provided as a plain string", async () => { server.use( - http.get("/api/gateways/gw-scopes", () => + http.get("/api/v1/mcp-servers/gw-scopes", () => HttpResponse.json({ name: "Scopes Server", url: "http://localhost:3000", @@ -975,7 +975,7 @@ describe("useMCPServerForm", () => { it("populates basic auth username and masked password from API", async () => { server.use( - http.get("/api/gateways/gw-3", () => + http.get("/api/v1/mcp-servers/gw-3", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -995,7 +995,7 @@ describe("useMCPServerForm", () => { it("populates bearer token from API response", async () => { server.use( - http.get("/api/gateways/gw-4", () => + http.get("/api/v1/mcp-servers/gw-4", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -1013,7 +1013,7 @@ describe("useMCPServerForm", () => { it("populates multiple custom headers from API response", async () => { server.use( - http.get("/api/gateways/gw-5", () => + http.get("/api/v1/mcp-servers/gw-5", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -1035,7 +1035,7 @@ describe("useMCPServerForm", () => { it("populates oauth store_tokens and auto_refresh as true from API", async () => { server.use( - http.get("/api/gateways/gw-6", () => + http.get("/api/v1/mcp-servers/gw-6", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -1058,7 +1058,7 @@ describe("useMCPServerForm", () => { it("populates oauth store_tokens and auto_refresh as false from API", async () => { server.use( - http.get("/api/gateways/gw-7", () => + http.get("/api/v1/mcp-servers/gw-7", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -1081,7 +1081,7 @@ describe("useMCPServerForm", () => { it("defaults oauth store_tokens and auto_refresh to false when absent from API response", async () => { server.use( - http.get("/api/gateways/gw-8", () => + http.get("/api/v1/mcp-servers/gw-8", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -1100,7 +1100,7 @@ describe("useMCPServerForm", () => { it("opens the advanced panel when server has auth configured", async () => { server.use( - http.get("/api/gateways/gw-9", () => + http.get("/api/v1/mcp-servers/gw-9", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000", @@ -1131,7 +1131,7 @@ describe("useMCPServerForm", () => { it("should trigger OAuth authorization after successful gateway creation with OAuth auth type", async () => { server.use( - http.post("/api/gateways", () => { + http.post("/api/v1/mcp-servers", () => { return HttpResponse.json({ id: "new-gateway-123", name: "Test OAuth Gateway", @@ -1184,7 +1184,7 @@ describe("useMCPServerForm", () => { it("should trigger OAuth authorization after successful gateway update with OAuth auth type", async () => { server.use( - http.get("/api/gateways/existing-gateway", () => { + http.get("/api/v1/mcp-servers/existing-gateway", () => { return HttpResponse.json({ id: "existing-gateway", name: "Existing Gateway", @@ -1199,7 +1199,7 @@ describe("useMCPServerForm", () => { authType: "oauth", }); }), - http.put("/api/gateways/existing-gateway", () => { + http.put("/api/v1/mcp-servers/existing-gateway", () => { return HttpResponse.json({ id: "existing-gateway", name: "Updated OAuth Gateway", @@ -1254,7 +1254,7 @@ describe("useMCPServerForm", () => { it("should not trigger OAuth authorization for non-OAuth auth types", async () => { server.use( - http.post("/api/gateways", () => { + http.post("/api/v1/mcp-servers", () => { return HttpResponse.json({ id: "new-gateway-456", name: "Test Basic Auth Gateway", @@ -1295,7 +1295,7 @@ describe("useMCPServerForm", () => { it("reuses the created gateway ID on OAuth retry to prevent duplicate gateway creation", async () => { let createCallCount = 0; server.use( - http.post("/api/gateways", () => { + http.post("/api/v1/mcp-servers", () => { createCallCount++; return HttpResponse.json({ id: "gateway-retry-test" }); }), @@ -1352,7 +1352,7 @@ describe("useMCPServerForm", () => { vi.useFakeTimers(); server.use( - http.post("/api/gateways", () => HttpResponse.json({ id: "gateway-delay-test" })), + http.post("/api/v1/mcp-servers", () => HttpResponse.json({ id: "gateway-delay-test" })), ); const triggerOAuthMock = vi @@ -1413,7 +1413,9 @@ describe("useMCPServerForm", () => { }); it("sets fetchToolsNotification to success after OAuth + successful tool fetch", async () => { - server.use(http.post("/api/gateways", () => HttpResponse.json({ id: "gw-ft-success" }))); + server.use( + http.post("/api/v1/mcp-servers", () => HttpResponse.json({ id: "gw-ft-success" })), + ); vi.spyOn(serversApi, "triggerOAuthAuthorization").mockResolvedValueOnce({ type: "oauth_callback", @@ -1451,7 +1453,7 @@ describe("useMCPServerForm", () => { }); it("sets fetchToolsNotification to error with nested detail.message when fetch-tools fails", async () => { - server.use(http.post("/api/gateways", () => HttpResponse.json({ id: "gw-ft-err" }))); + server.use(http.post("/api/v1/mcp-servers", () => HttpResponse.json({ id: "gw-ft-err" }))); vi.spyOn(serversApi, "triggerOAuthAuthorization").mockResolvedValueOnce({ type: "oauth_callback", @@ -1493,7 +1495,9 @@ describe("useMCPServerForm", () => { }); it("sets fetchToolsNotification to error with plain detail string when fetch-tools fails", async () => { - server.use(http.post("/api/gateways", () => HttpResponse.json({ id: "gw-ft-str-err" }))); + server.use( + http.post("/api/v1/mcp-servers", () => HttpResponse.json({ id: "gw-ft-str-err" })), + ); vi.spyOn(serversApi, "triggerOAuthAuthorization").mockResolvedValueOnce({ type: "oauth_callback", @@ -1534,7 +1538,9 @@ describe("useMCPServerForm", () => { }); it("clearFetchToolsNotification resets fetchToolsNotification to null", async () => { - server.use(http.post("/api/gateways", () => HttpResponse.json({ id: "gw-clear-test" }))); + server.use( + http.post("/api/v1/mcp-servers", () => HttpResponse.json({ id: "gw-clear-test" })), + ); vi.spyOn(serversApi, "triggerOAuthAuthorization").mockResolvedValueOnce({ type: "oauth_callback", @@ -1570,7 +1576,7 @@ describe("useMCPServerForm", () => { describe("handleSubmit error parsing", () => { it("extracts nested detail.message from API error on create failure", async () => { server.use( - http.post("/api/gateways", () => + http.post("/api/v1/mcp-servers", () => HttpResponse.json( { detail: { message: "A server with this name already exists", success: false } }, { status: 400 }, @@ -1597,10 +1603,10 @@ describe("useMCPServerForm", () => { it("extracts plain detail string from API error on update failure", async () => { server.use( - http.get("/api/gateways/edit-gw", () => + http.get("/api/v1/mcp-servers/edit-gw", () => HttpResponse.json({ name: "My Server", url: "http://localhost:3000" }), ), - http.put("/api/gateways/edit-gw", () => + http.put("/api/v1/mcp-servers/edit-gw", () => HttpResponse.json({ detail: "Gateway not found" }, { status: 404 }), ), ); @@ -1621,7 +1627,7 @@ describe("useMCPServerForm", () => { it("logs error and continues if activation fails during OAuth", async () => { server.use( - http.post("/api/gateways", () => HttpResponse.json({ id: "gw-oauth-activate-fail" })), + http.post("/api/v1/mcp-servers", () => HttpResponse.json({ id: "gw-oauth-activate-fail" })), ); vi.spyOn(serversApi, "triggerOAuthAuthorization").mockResolvedValueOnce({ diff --git a/src/hooks/useMCPServerForm.ts b/src/hooks/useMCPServerForm.ts index a95a1538..1f1cb1ef 100644 --- a/src/hooks/useMCPServerForm.ts +++ b/src/hooks/useMCPServerForm.ts @@ -385,7 +385,7 @@ export function useMCPServerForm(gatewayId?: string): UseMCPServerFormReturn { store_tokens?: boolean; auto_refresh?: boolean; }; - }>(`/gateways/${gatewayId}`, { + }>(`/v1/mcp-servers/${gatewayId}`, { enabled: Boolean(gatewayId), }); @@ -457,7 +457,7 @@ export function useMCPServerForm(gatewayId?: string): UseMCPServerFormReturn { // Use useQuery for POST request to create MCP gateway const { execute: createGateway, isLoading: isCreating } = useQuery( - "/gateways", + "/v1/mcp-servers", { method: "POST", enabled: false, // Don't execute immediately @@ -467,7 +467,7 @@ export function useMCPServerForm(gatewayId?: string): UseMCPServerFormReturn { // handleSubmit guards against a missing gatewayId before calling updateGateway, // so this URL is only ever used when gatewayId is defined. const { execute: updateGateway, isLoading: isUpdating } = useQuery( - `/gateways/${gatewayId}`, + `/v1/mcp-servers/${gatewayId}`, { method: "PUT", enabled: false, // Don't execute immediately diff --git a/src/hooks/useMcpServers.test.ts b/src/hooks/useMcpServers.test.ts index fa116df5..d21f1a80 100644 --- a/src/hooks/useMcpServers.test.ts +++ b/src/hooks/useMcpServers.test.ts @@ -18,8 +18,8 @@ describe("useMcpServers", () => { setTabHidden(false); }); - it("fetches /gateways and exposes servers plus a lastUpdated timestamp", async () => { - server.use(http.get("*/gateways", () => HttpResponse.json(GATEWAYS))); + it("fetches /v1/mcp-servers and exposes servers plus a lastUpdated timestamp", async () => { + server.use(http.get("*/v1/mcp-servers", () => HttpResponse.json(GATEWAYS))); const { result } = renderHook(() => useMcpServers(0)); // polling disabled @@ -31,7 +31,7 @@ describe("useMcpServers", () => { it("polls on the interval and pauses while the tab is hidden", async () => { let calls = 0; server.use( - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { calls += 1; return HttpResponse.json(GATEWAYS); }), @@ -50,7 +50,7 @@ describe("useMcpServers", () => { it("fetches again when the tab becomes visible", async () => { let calls = 0; server.use( - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { calls += 1; return HttpResponse.json(GATEWAYS); }), diff --git a/src/hooks/useMcpServers.ts b/src/hooks/useMcpServers.ts index f6152dac..a70e2c84 100644 --- a/src/hooks/useMcpServers.ts +++ b/src/hooks/useMcpServers.ts @@ -1,14 +1,14 @@ /** * useMcpServers (#5842). * - * Polls `GET /gateways` (React "MCP servers") for the home MCP reachability + * Polls `GET /v1/mcp-servers` (React "MCP servers") for the home MCP reachability * roster. Includes inactive servers so the roster can show disabled rows, and * requests pagination metadata for the structured `{ gateways: [...] }` shape. * * Polls modestly and pauses while the tab is hidden (mirrors `useSystemHealth`), * and tracks `lastUpdated` so the card can render a "Refreshed Xs ago" hint. * - * The backend RBAC-scopes `/gateways` (`gateways.read` + token-team scoping), so + * The backend RBAC-scopes `/v1/mcp-servers` (`gateways.read` + token-team scoping), so * a scoped caller only ever sees their own servers and a caller without * `gateways.read` gets a 403 the card surfaces as PermissionDenied. */ @@ -20,7 +20,7 @@ import type { MCPServer, ServersResponse } from "@/types/server"; // limit is clamped to 100 server-side (see api/servers.ts); fleets larger than // 100 undercount until pagination is added. Adequate for the home summary today. -const MCP_SERVERS_PATH = "/gateways?limit=100&include_inactive=true&include_pagination=true"; +const MCP_SERVERS_PATH = "/v1/mcp-servers?limit=100&include_inactive=true&include_pagination=true"; export const MCP_SERVERS_POLL_INTERVAL_MS = 60_000; diff --git a/src/hooks/useMiniCardStatuses.test.tsx b/src/hooks/useMiniCardStatuses.test.tsx index a4d6130b..b83380ba 100644 --- a/src/hooks/useMiniCardStatuses.test.tsx +++ b/src/hooks/useMiniCardStatuses.test.tsx @@ -193,7 +193,7 @@ describe("useMiniCardStatuses — reachability dots", () => { it("is Online for a source with a reachable instance, Offline for one without", () => { mockUseQuery.mockImplementation((path: string | null) => - typeof path === "string" && path.includes("/gateways") + typeof path === "string" && path.includes("/v1/mcp-servers") ? query({ gateways: [{ enabled: true, reachable: true }] }) : query([]), ); diff --git a/src/hooks/useMiniCardStatuses.ts b/src/hooks/useMiniCardStatuses.ts index f69ac536..8d820131 100644 --- a/src/hooks/useMiniCardStatuses.ts +++ b/src/hooks/useMiniCardStatuses.ts @@ -4,7 +4,7 @@ * pure `computeMiniCardStatuses` model and the `resolveHeadline` condition. * * Sources: `/version` (backend health, via useSystemHealth), reachability - * probes for MCP servers (`/gateways`) and A2A agents (`/a2a`), and recent + * probes for MCP servers (`/v1/mcp-servers`) and A2A agents (`/a2a`), and recent * activity for error/warning counts. Activity is fetched once (no polling) to * keep the resting home quiet; it is empty until the activity backend (#5944) * lands. @@ -40,7 +40,7 @@ import type { ServersResponse } from "@/types/server"; // in the paginated `{ gateways: [...] }` shape (matching useMcpServers); A2A is a // bare list. Capped at 100 like the roster; a reachable instance past position // 100 is not counted. -const MCP_REACH_PATH = "/gateways?limit=100&include_pagination=true"; +const MCP_REACH_PATH = "/v1/mcp-servers?limit=100&include_pagination=true"; const A2A_REACH_PATH = "/a2a?limit=100"; export interface HomeStatus { diff --git a/src/pages/CreateServer.test.tsx b/src/pages/CreateServer.test.tsx index 570db675..ae80be0a 100644 --- a/src/pages/CreateServer.test.tsx +++ b/src/pages/CreateServer.test.tsx @@ -122,7 +122,7 @@ describe("CreateServer", () => { componentMockState.mockForm = true; routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => + http.get("*/v1/virtual-servers/gateway-1", () => HttpResponse.json({ id: "gateway-1", name: "GH repo tasks", @@ -215,7 +215,7 @@ describe("CreateServer", () => { it("shows an error when edit server details cannot be loaded", async () => { routerMock.path = "/app/gateways/create-server?editServerId=missing-server"; server.use( - http.get("*/servers/missing-server", () => + http.get("*/v1/virtual-servers/missing-server", () => HttpResponse.json({ detail: "Virtual server not found" }, { status: 404 }), ), ); @@ -231,7 +231,7 @@ describe("CreateServer", () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; mockUpdateVirtualServer.mockRejectedValueOnce(new Error("Update failed")); server.use( - http.get("*/servers/gateway-1", () => + http.get("*/v1/virtual-servers/gateway-1", () => HttpResponse.json({ id: "gateway-1", name: "GH repo tasks", @@ -337,7 +337,7 @@ describe("CreateServer", () => { let gatewaysRequestCount = 0; const toolCursors: Array = []; server.use( - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { gatewaysRequestCount += 1; return HttpResponse.json({ gateways: [ @@ -552,7 +552,7 @@ describe("CreateServer", () => { it("lists connected MCP servers with computed status and visibility", async () => { const user = userEvent.setup(); server.use( - http.get("*/gateways", () => + http.get("*/v1/mcp-servers", () => HttpResponse.json({ gateways: [ { @@ -660,7 +660,7 @@ describe("CreateServer", () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return new Promise(() => { // never resolves }); @@ -675,7 +675,7 @@ describe("CreateServer", () => { it("renders error state when fetch fails in edit mode", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ detail: "Not found" }, { status: 404 }); }), ); @@ -691,7 +691,7 @@ describe("CreateServer", () => { it("renders the edit form when data is loaded successfully", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", @@ -701,7 +701,7 @@ describe("CreateServer", () => { description: "A test server for editing", }); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.json({ gateways: [] }, { status: 200 }); }), ); @@ -719,7 +719,7 @@ describe("CreateServer", () => { it("renders MCP servers section in edit mode and allows source selection", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", @@ -727,7 +727,7 @@ describe("CreateServer", () => { oauthEnabled: false, }); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.json({ gateways: [ { @@ -795,10 +795,10 @@ describe("CreateServer", () => { it("renders every MCP server status and visibility in the edit accordion", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => + http.get("*/v1/virtual-servers/gateway-1", () => HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", visibility: "team" }), ), - http.get("*/gateways", () => + http.get("*/v1/mcp-servers", () => HttpResponse.json({ gateways: [ { @@ -849,7 +849,7 @@ describe("CreateServer", () => { it("renders warning alert when mcpServers fails to load", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", @@ -857,7 +857,7 @@ describe("CreateServer", () => { oauthEnabled: false, }); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.error(); }), ); @@ -872,7 +872,7 @@ describe("CreateServer", () => { it("renders componentError alert when tools fetch fails inside accordion", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", @@ -880,7 +880,7 @@ describe("CreateServer", () => { oauthEnabled: false, }); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.json({ gateways: [ { @@ -919,10 +919,10 @@ describe("CreateServer", () => { it("renders fallback error message when editServerError has no message", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json(null); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.json({ gateways: [] }); }), ); @@ -939,7 +939,7 @@ describe("CreateServer", () => { it("calls updateVirtualServer and navigates when form is successfully submitted in edit mode", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", @@ -947,7 +947,7 @@ describe("CreateServer", () => { oauthEnabled: false, }); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.json({ gateways: [] }); }), ); @@ -981,7 +981,7 @@ describe("CreateServer", () => { it("shows error when updateVirtualServer fails", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", @@ -989,7 +989,7 @@ describe("CreateServer", () => { oauthEnabled: false, }); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.json({ gateways: [] }); }), ); @@ -1027,7 +1027,7 @@ describe("CreateServer", () => { it("displays a message when there are no connected MCP servers", async () => { routerMock.path = "/app/gateways/create-server?editServerId=gateway-1"; server.use( - http.get("*/servers/gateway-1", () => { + http.get("*/v1/virtual-servers/gateway-1", () => { return HttpResponse.json({ id: "gateway-1", name: "Test Edit Server", @@ -1035,7 +1035,7 @@ describe("CreateServer", () => { oauthEnabled: false, }); }), - http.get("*/gateways", () => { + http.get("*/v1/mcp-servers", () => { return HttpResponse.json({ gateways: [] }); }), ); diff --git a/src/pages/CreateServer.tsx b/src/pages/CreateServer.tsx index f6b3498c..93515fee 100644 --- a/src/pages/CreateServer.tsx +++ b/src/pages/CreateServer.tsx @@ -32,7 +32,7 @@ import type { MCPServer, ServerStatus, VirtualServer, VirtualServerTag } from "@ const SERVERS_FORM_PATH = "/app/servers?openForm=true"; const EDIT_SERVER_ID_QUERY_PARAM = "editServerId"; -const MCP_SERVERS_QUERY_PATH = "/gateways?limit=100&include_inactive=true"; +const MCP_SERVERS_QUERY_PATH = "/v1/mcp-servers?limit=100&include_inactive=true"; const COMPONENT_PAGE_SIZE = 100; type CreateServerStep = "details" | "sources"; @@ -644,7 +644,9 @@ export function CreateServer() { const [createError, setCreateError] = useState(null); const [updateError, setUpdateError] = useState(null); const isEditMode = Boolean(editServerId); - const editServerPath = editServerId ? `/servers/${encodeURIComponent(editServerId)}` : null; + const editServerPath = editServerId + ? `/v1/virtual-servers/${encodeURIComponent(editServerId)}` + : null; const { data: editingServer, error: editServerError, diff --git a/src/pages/Dashboard.test.tsx b/src/pages/Dashboard.test.tsx index 15db4b5d..485a6c71 100644 --- a/src/pages/Dashboard.test.tsx +++ b/src/pages/Dashboard.test.tsx @@ -78,9 +78,11 @@ describe("Dashboard", () => { it("checks whether virtual and MCP servers exist", () => { renderWithProviders(); - expect(mockUseQuery).toHaveBeenCalledWith("/servers?limit=1&include_pagination=true"); expect(mockUseQuery).toHaveBeenCalledWith( - "/gateways?limit=1&include_inactive=true&include_pagination=true", + "/v1/virtual-servers?limit=1&include_pagination=true", + ); + expect(mockUseQuery).toHaveBeenCalledWith( + "/v1/mcp-servers?limit=1&include_inactive=true&include_pagination=true", ); }); diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index c632fc89..583f3bcc 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -33,8 +33,9 @@ import { useQuery } from "@/hooks/useQuery"; import { useRouter } from "@/router"; import type { VirtualServersResponse } from "@/types/server"; -const SERVERS_QUERY_PATH = "/servers?limit=1&include_pagination=true"; -const MCP_SERVERS_QUERY_PATH = "/gateways?limit=1&include_inactive=true&include_pagination=true"; +const SERVERS_QUERY_PATH = "/v1/virtual-servers?limit=1&include_pagination=true"; +const MCP_SERVERS_QUERY_PATH = + "/v1/mcp-servers?limit=1&include_inactive=true&include_pagination=true"; const SERVERS_FORM_PATH = "/app/servers?openForm=true"; /** Inline source cards shown in the default state (one per source type). */ diff --git a/src/pages/Gateways.test.tsx b/src/pages/Gateways.test.tsx index c08700a9..8877df65 100644 --- a/src/pages/Gateways.test.tsx +++ b/src/pages/Gateways.test.tsx @@ -143,7 +143,7 @@ describe("Gateways", () => { renderWithProviders(); expect(mockUseQuery).toHaveBeenCalledWith( - "/servers?limit=12&include_inactive=true&include_pagination=true", + "/v1/virtual-servers?limit=12&include_inactive=true&include_pagination=true", ); expect(mockUseQuery).toHaveBeenCalledTimes(1); }); @@ -384,7 +384,7 @@ describe("Gateways", () => { const setServerDetails = vi.fn(); mockUseQuery.mockImplementation((path) => { - if (path === "/servers?limit=12&include_inactive=true&include_pagination=true") { + if (path === "/v1/virtual-servers?limit=12&include_inactive=true&include_pagination=true") { return { data: { servers: [server] }, error: null, @@ -395,7 +395,7 @@ describe("Gateways", () => { }; } - if (path === "/servers/gateway-1") { + if (path === "/v1/virtual-servers/gateway-1") { return { data: server, error: null, @@ -683,7 +683,7 @@ describe("Gateways", () => { }; mockUseQuery.mockImplementation((path) => { - if (path === "/servers/gateway%2F1%3Fmode%3Ddetail") { + if (path === "/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail") { return { data: detailServer, error: null, @@ -694,7 +694,7 @@ describe("Gateways", () => { }; } - if (path === "/servers/gateway%2F1%3Fmode%3Ddetail/tools?include_inactive=true") { + if (path === "/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail/tools?include_inactive=true") { return { data: [ { @@ -718,7 +718,9 @@ describe("Gateways", () => { }; } - if (path === "/servers/gateway%2F1%3Fmode%3Ddetail/resources?include_inactive=true") { + if ( + path === "/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail/resources?include_inactive=true" + ) { return { data: [ { @@ -735,7 +737,9 @@ describe("Gateways", () => { }; } - if (path === "/servers/gateway%2F1%3Fmode%3Ddetail/prompts?include_inactive=true") { + if ( + path === "/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail/prompts?include_inactive=true" + ) { return { data: [ { @@ -772,24 +776,27 @@ describe("Gateways", () => { await user.click(viewDetails); - expect(mockUseQuery).toHaveBeenCalledWith("/servers/gateway%2F1%3Fmode%3Ddetail"); + expect(mockUseQuery).toHaveBeenCalledWith("/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail"); expect(mockUseQuery).toHaveBeenCalledWith( - "/servers/gateway%2F1%3Fmode%3Ddetail/tools?include_inactive=true", + "/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail/tools?include_inactive=true", { enabled: true }, ); expect(mockUseQuery).toHaveBeenCalledWith( - "/servers/gateway%2F1%3Fmode%3Ddetail/resources?include_inactive=true", + "/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail/resources?include_inactive=true", { enabled: true }, ); expect(mockUseQuery).toHaveBeenCalledWith( - "/servers/gateway%2F1%3Fmode%3Ddetail/prompts?include_inactive=true", + "/v1/virtual-servers/gateway%2F1%3Fmode%3Ddetail/prompts?include_inactive=true", { enabled: true }, ); expect(mockUseQuery).not.toHaveBeenCalledWith( expect.stringContaining("virtual_server_id"), expect.anything(), ); - expect(mockUseQuery).not.toHaveBeenCalledWith("/servers/__pending__", expect.anything()); + expect(mockUseQuery).not.toHaveBeenCalledWith( + "/v1/virtual-servers/__pending__", + expect.anything(), + ); const detailsPanel = screen.getByRole("region", { name: "GH repo tasks details" }); expect(screen.getByText("Virtual server details")).toBeInTheDocument(); expect( @@ -1081,7 +1088,7 @@ describe("Gateways", () => { const mockServer = makeServer({ id: "gateway-details-rollback", name: "Details Server" }); mockUseQuery.mockImplementation((path) => { - if (path === "/servers/gateway-details-rollback") { + if (path === "/v1/virtual-servers/gateway-details-rollback") { return { data: mockServer, error: null, @@ -1178,7 +1185,7 @@ describe("Gateways", () => { const mockServer = makeServer({ associatedToolIds: ["tool1"] }); mockUseQuery.mockImplementation((path) => { - if (path === "/servers/gateway-1") { + if (path === "/v1/virtual-servers/gateway-1") { return { data: mockServer, error: null, @@ -1189,7 +1196,7 @@ describe("Gateways", () => { }; } - if (path === "/servers/gateway-1/tools?include_inactive=true") { + if (path === "/v1/virtual-servers/gateway-1/tools?include_inactive=true") { return { data: null, error: { message: "Failed to fetch tools" }, @@ -1200,7 +1207,7 @@ describe("Gateways", () => { }; } - if (path === "/servers/gateway-1/resources?include_inactive=true") { + if (path === "/v1/virtual-servers/gateway-1/resources?include_inactive=true") { return { data: null, error: { message: "Failed to fetch resources" }, @@ -1211,7 +1218,7 @@ describe("Gateways", () => { }; } - if (path === "/servers/gateway-1/prompts?include_inactive=true") { + if (path === "/v1/virtual-servers/gateway-1/prompts?include_inactive=true") { return { data: null, error: { message: "Failed to fetch prompts" }, @@ -1358,8 +1365,8 @@ describe("Gateways", () => { refetch: vi.fn(), setData: vi.fn(), }; - if (path === "/servers/gateway-1") return { ...base, data: detailServer }; - if (path?.startsWith("/servers/gateway-1/")) return { ...base, data: [] }; + if (path === "/v1/virtual-servers/gateway-1") return { ...base, data: detailServer }; + if (path?.startsWith("/v1/virtual-servers/gateway-1/")) return { ...base, data: [] }; return { ...base, data: { servers: [detailServer] } }; }); mockUpdateVirtualServerTags.mockResolvedValue({ @@ -1391,8 +1398,8 @@ describe("Gateways", () => { refetch: vi.fn(), setData: vi.fn(), }; - if (path === "/servers/gateway-1") return { ...base, data: detailServer }; - if (path?.startsWith("/servers/gateway-1/")) return { ...base, data: [] }; + if (path === "/v1/virtual-servers/gateway-1") return { ...base, data: detailServer }; + if (path?.startsWith("/v1/virtual-servers/gateway-1/")) return { ...base, data: [] }; return { ...base, data: { servers: [detailServer] } }; }); mockUpdateVirtualServerTags.mockRejectedValue(new Error("boom")); diff --git a/src/pages/Gateways.tsx b/src/pages/Gateways.tsx index 8d5f73e5..b48da0c2 100644 --- a/src/pages/Gateways.tsx +++ b/src/pages/Gateways.tsx @@ -21,7 +21,7 @@ import type { VirtualServer, VirtualServersResponse } from "@/types/server"; import { extractApiErrorDetail, sanitizeError } from "@/utils/errors"; const DEFAULT_PAGE_SIZE = 12; -const SERVERS_QUERY_PATH = `/servers?limit=${DEFAULT_PAGE_SIZE}&include_inactive=true&include_pagination=true`; +const SERVERS_QUERY_PATH = `/v1/virtual-servers?limit=${DEFAULT_PAGE_SIZE}&include_inactive=true&include_pagination=true`; const CREATE_SERVER_PATH = "/app/gateways/create-server"; const EDIT_SERVER_ID_QUERY_PARAM = "editServerId"; @@ -368,7 +368,7 @@ function VirtualServerDetailsPanelContainer({ data: serverDetails, error, setData: setServerDetails, - } = useQuery(`/servers/${encodeURIComponent(serverId)}`); + } = useQuery(`/v1/virtual-servers/${encodeURIComponent(serverId)}`); const hydratedServer = serverDetails?.id === serverId ? serverDetails : server; useEffect(() => { diff --git a/src/pages/Resources.test.tsx b/src/pages/Resources.test.tsx index c5b5ec9b..cd8c905b 100644 --- a/src/pages/Resources.test.tsx +++ b/src/pages/Resources.test.tsx @@ -821,7 +821,7 @@ describe("Resources", () => { if (refetchCount === 1) return HttpResponse.json([]); return HttpResponse.json([createMockResource(99, "test-gw")]); }), - http.get("/api/gateways", () => + http.get("/api/v1/mcp-servers", () => HttpResponse.json({ gateways: [], next_cursor: null, total: 0 }), ), http.post("/api/resources", () => HttpResponse.json({ id: "res-99" }, { status: 201 })), @@ -843,7 +843,7 @@ describe("Resources", () => { server.use( http.get("/api/resources", () => HttpResponse.json([])), - http.get("/api/gateways", () => + http.get("/api/v1/mcp-servers", () => HttpResponse.json({ gateways: [], next_cursor: null, total: 0 }), ), http.post("/api/resources", () => @@ -1447,7 +1447,7 @@ describe("Resources", () => { const resource = createMockResource(1, "gw-1"); server.use( http.get("/api/resources", () => HttpResponse.json([resource])), - http.get("/api/gateways", () => + http.get("/api/v1/mcp-servers", () => HttpResponse.json({ gateways: [{ id: "gw-1", slug: "gh-repo-tasks", name: "GH Repo" }], }), diff --git a/src/pages/Resources.tsx b/src/pages/Resources.tsx index 1f9ed0db..cc2b73d4 100644 --- a/src/pages/Resources.tsx +++ b/src/pages/Resources.tsx @@ -250,7 +250,7 @@ export function Resources() { setData: setResourcesData, } = useQuery("/resources?limit=0&include_inactive=true"); const { data: gatewaysData } = useQuery( - "/gateways?limit=0&include_pagination=true", + "/v1/mcp-servers?limit=0&include_pagination=true", { enabled: !isLoading, }, diff --git a/src/pages/Servers.test.tsx b/src/pages/Servers.test.tsx index 6489e0d3..645af467 100644 --- a/src/pages/Servers.test.tsx +++ b/src/pages/Servers.test.tsx @@ -367,7 +367,11 @@ describe("Servers", () => { await user.click(submit); await waitFor(() => - expect(api.post).toHaveBeenCalledWith("/gateways", expect.anything(), expect.anything()), + expect(api.post).toHaveBeenCalledWith( + "/v1/mcp-servers", + expect.anything(), + expect.anything(), + ), ); }); @@ -377,7 +381,7 @@ describe("Servers", () => { if (path.includes("/tools")) return Promise.resolve({ tools: [] }); if (path.includes("/resources")) return Promise.resolve({ resources: [] }); if (path.includes("/prompts")) return Promise.resolve({ prompts: [] }); - if (/\/gateways\/server-0/.test(path)) return Promise.resolve(mockServerDetails); + if (/\/v1\/mcp-servers\/server-0/.test(path)) return Promise.resolve(mockServerDetails); return Promise.resolve({ gateways: createMockServers(0, 1), nextCursor: null }); }); vi.mocked(api.put).mockResolvedValue({ @@ -397,7 +401,7 @@ describe("Servers", () => { await user.click(screen.getByRole("button", { name: "Add" })); await waitFor(() => { - expect(api.put).toHaveBeenCalledWith(expect.stringContaining("/gateways/server-0"), { + expect(api.put).toHaveBeenCalledWith(expect.stringContaining("/v1/mcp-servers/server-0"), { tags: ["newtag"], }); }); @@ -409,7 +413,7 @@ describe("Servers", () => { if (path.includes("/tools")) return Promise.resolve({ tools: [] }); if (path.includes("/resources")) return Promise.resolve({ resources: [] }); if (path.includes("/prompts")) return Promise.resolve({ prompts: [] }); - if (/\/gateways\/server-0/.test(path)) return Promise.resolve(mockServerDetails); + if (/\/v1\/mcp-servers\/server-0/.test(path)) return Promise.resolve(mockServerDetails); return Promise.resolve({ gateways: createMockServers(0, 1), nextCursor: null }); }); vi.mocked(api.put).mockRejectedValue(new Error("boom")); @@ -427,7 +431,7 @@ describe("Servers", () => { // The rejected update runs through the error branch (which surfaces a toast). await waitFor(() => { - expect(api.put).toHaveBeenCalledWith(expect.stringContaining("/gateways/server-0"), { + expect(api.put).toHaveBeenCalledWith(expect.stringContaining("/v1/mcp-servers/server-0"), { tags: ["newtag"], }); }); @@ -438,7 +442,7 @@ describe("Servers", () => { if (path.includes("/tools")) return Promise.resolve({ tools: [] }); if (path.includes("/resources")) return Promise.resolve({ resources: [] }); if (path.includes("/prompts")) return Promise.resolve({ prompts: [] }); - if (/\/gateways\/server-0/.test(path)) return Promise.resolve(mockServerDetails); + if (/\/v1\/mcp-servers\/server-0/.test(path)) return Promise.resolve(mockServerDetails); return Promise.resolve({ gateways: createMockServers(0, 1), nextCursor: null }); }); @@ -1006,7 +1010,7 @@ describe("Servers", () => { const user = userEvent.setup(); vi.mocked(api.get).mockImplementation((path) => { - if (path.includes("/gateways/server-0")) { + if (path.includes("/v1/mcp-servers/server-0")) { return Promise.resolve(mockServerDetails); } return Promise.resolve({ diff --git a/src/pages/Servers.tsx b/src/pages/Servers.tsx index 5dc2e87f..c4b892eb 100644 --- a/src/pages/Servers.tsx +++ b/src/pages/Servers.tsx @@ -49,7 +49,7 @@ export function Servers() { params.set("limit", limit.toString()); params.set("include_pagination", "true"); params.set("include_inactive", "true"); - return `/gateways?${params.toString()}`; + return `/v1/mcp-servers?${params.toString()}`; }, [limit]); // Use useQuery hook for initial data fetching and limit changes @@ -65,8 +65,8 @@ export function Servers() { const detailsQueryPath = useMemo( () => selectedServerIdForDetails - ? `/gateways/${encodeURIComponent(selectedServerIdForDetails)}` - : "/gateways/_placeholder_", + ? `/v1/mcp-servers/${encodeURIComponent(selectedServerIdForDetails)}` + : "/v1/mcp-servers/_placeholder_", [selectedServerIdForDetails], ); @@ -236,7 +236,7 @@ export function Servers() { params.set("include_pagination", "true"); params.set("include_inactive", "true"); - const result = await api.get(`/gateways?${params.toString()}`); + const result = await api.get(`/v1/mcp-servers?${params.toString()}`); setAllServers((prev) => [...prev, ...result.gateways]); setNextCursor(result.nextCursor ?? null); } catch (err) { diff --git a/src/pages/Tools.test.tsx b/src/pages/Tools.test.tsx index 53b7ae40..59152e5b 100644 --- a/src/pages/Tools.test.tsx +++ b/src/pages/Tools.test.tsx @@ -195,7 +195,7 @@ describe("Tools", () => { const mockTools: Tool[] = [createMockTool(1, "described-gateway")]; server.use( http.get("/api/tools", () => HttpResponse.json(mockTools)), - http.get("/api/gateways", () => + http.get("/api/v1/mcp-servers", () => HttpResponse.json({ gateways: [ { diff --git a/src/pages/Tools.tsx b/src/pages/Tools.tsx index eb202c5c..a80f9532 100644 --- a/src/pages/Tools.tsx +++ b/src/pages/Tools.tsx @@ -203,7 +203,7 @@ export function Tools() { setData: setToolsData, } = useQuery("/tools?limit=0&include_inactive=true"); const { data: gatewaysData } = useQuery( - "/gateways?limit=0&include_pagination=true", + "/v1/mcp-servers?limit=0&include_pagination=true", ); useEffect(() => { diff --git a/src/test/mocks/handlers.ts b/src/test/mocks/handlers.ts index 55d64b90..a4b72628 100644 --- a/src/test/mocks/handlers.ts +++ b/src/test/mocks/handlers.ts @@ -56,7 +56,7 @@ export const handlers = [ }), // Mock gateways endpoint with cursor pagination - http.get("*/api/gateways", ({ request }) => { + http.get("*/api/v1/mcp-servers", ({ request }) => { const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); const limit = parseInt(url.searchParams.get("limit") || "25", 10); @@ -86,7 +86,7 @@ export const handlers = [ }), // Mock single gateway fetch endpoint - http.get("*/api/gateways/:id", ({ params }) => { + http.get("*/api/v1/mcp-servers/:id", ({ params }) => { return HttpResponse.json({ id: params.id, name: "Test Server", @@ -98,12 +98,12 @@ export const handlers = [ }), // Mock gateway delete endpoint - http.delete("*/api/gateways/:id", () => { + http.delete("*/api/v1/mcp-servers/:id", () => { return HttpResponse.json({ success: true }); }), // Mock gateway test endpoint - http.post("*/api/gateways/:id/test", () => { + http.post("*/api/v1/mcp-servers/:id/test", () => { return HttpResponse.json({ success: true, message: "Connection successful", @@ -111,7 +111,7 @@ export const handlers = [ }), // Mock create gateway endpoint - http.post("*/api/gateways", async ({ request }) => { + http.post("*/api/v1/mcp-servers", async ({ request }) => { const body = (await request.json()) as Record; return HttpResponse.json( { @@ -124,7 +124,7 @@ export const handlers = [ }), // Mock update gateway endpoint - http.put("*/api/gateways/:gatewayId", async ({ request, params }) => { + http.put("*/api/v1/mcp-servers/:gatewayId", async ({ request, params }) => { const body = (await request.json()) as Record; const { gatewayId } = params; return HttpResponse.json({ From 92bde32f30b56dbec2f57047525b78896f3bd24b Mon Sep 17 00:00:00 2001 From: Marek Dano Date: Wed, 2 Sep 2026 12:43:05 +0100 Subject: [PATCH 2/2] fix: failing unit tests Signed-off-by: Marek Dano --- src/components/gateways/ExposeComponentsForm.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/gateways/ExposeComponentsForm.test.tsx b/src/components/gateways/ExposeComponentsForm.test.tsx index 5027c7ba..09a3f609 100644 --- a/src/components/gateways/ExposeComponentsForm.test.tsx +++ b/src/components/gateways/ExposeComponentsForm.test.tsx @@ -52,7 +52,7 @@ const server = setupServer( } return HttpResponse.json([]); }), - http.post("/api/servers", () => { + http.post("/api/v1/virtual-servers", () => { return HttpResponse.json({ id: "virtual-server-456" }); }), ); @@ -380,7 +380,7 @@ describe("ExposeComponentsForm", () => { it("should disable submit button while creating", async () => { // Delay the server response to test the loading state server.use( - http.post("/api/servers", async () => { + http.post("/api/v1/virtual-servers", async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return HttpResponse.json({ id: "virtual-server-456" }); }), @@ -404,7 +404,7 @@ describe("ExposeComponentsForm", () => { it("should show error message on creation failure", async () => { server.use( - http.post("/api/servers", () => { + http.post("/api/v1/virtual-servers", () => { return HttpResponse.json({ error: "Failed to create server" }, { status: 500 }); }), );