diff --git a/e2e/virtual-servers.spec.ts b/e2e/virtual-servers.spec.ts
index 9022f902..b629c29b 100644
--- a/e2e/virtual-servers.spec.ts
+++ b/e2e/virtual-servers.spec.ts
@@ -1208,6 +1208,45 @@ test.describe("Virtual Servers page", () => {
await expect(detailsPanel.getByText("github://repo/{owner}/{repo}")).toHaveCount(0);
});
+ test("filters components by source in the details panel when there are several", async ({
+ page,
+ }) => {
+ await routeVirtualServerTryIt(page, [
+ makeTryItTool(),
+ makeTryItTool({
+ id: "tool-post",
+ name: "slack.post_message",
+ originalName: "post_message",
+ gatewayId: MOCK_MCP_SERVER_2.id,
+ }),
+ ]);
+ // Registered after the helper's route, so it takes precedence.
+ await page.route("**/v1/mcp-servers?*", async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({ gateways: [MOCK_MCP_SERVER, MOCK_MCP_SERVER_2] }),
+ });
+ });
+
+ await page.goto(APP.GATEWAYS);
+ await page.getByRole("button", { name: "Actions for testVS" }).click();
+ await page.getByRole("menuitem", { name: "View details" }).click();
+
+ const panel = page.getByRole("region", { name: "testVS details" });
+ await panel.getByRole("tab", { name: "Components" }).click();
+
+ await expect(panel.getByRole("tab", { name: "All sources" })).toBeVisible();
+ await expect(panel.getByRole("tab", { name: "github-mcp" })).toBeVisible();
+ await expect(panel.getByRole("tab", { name: "slack-mcp" })).toBeVisible();
+ await expect(panel.getByText("search_issues")).toBeVisible();
+ await expect(panel.getByText("post_message")).toBeVisible();
+
+ await panel.getByRole("tab", { name: "slack-mcp" }).click();
+ await expect(panel.getByText("post_message")).toBeVisible();
+ await expect(panel.getByText("search_issues")).toHaveCount(0);
+ });
+
test("shows a tooltip with the full endpoint when it's truncated in the Try it tab", async ({
page,
}) => {
diff --git a/src/components/gateways/VirtualServerDetailsPanel.test.tsx b/src/components/gateways/VirtualServerDetailsPanel.test.tsx
index 70e219c1..1f1a5f6f 100644
--- a/src/components/gateways/VirtualServerDetailsPanel.test.tsx
+++ b/src/components/gateways/VirtualServerDetailsPanel.test.tsx
@@ -295,6 +295,36 @@ describe("VirtualServerDetailsPanel components list", () => {
expect(screen.getByRole("tab", { name: "Gateway A" })).toHaveFocus();
});
+ it("hides the source selector when the server has a single source", async () => {
+ const user = userEvent.setup();
+ mswServer.use(
+ http.get("*/v1/virtual-servers/:id/tools", () =>
+ HttpResponse.json({
+ tools: [{ id: "t1", name: "Tool One", originalName: "tool_one", gateway_id: "gwA" }],
+ }),
+ ),
+ http.get("*/v1/mcp-servers", () =>
+ HttpResponse.json({ gateways: [{ id: "gwA", name: "Gateway A" }] }),
+ ),
+ );
+
+ render(
+ ,
+ );
+
+ await user.click(await screen.findByRole("tab", { name: "Components" }));
+ expect(await screen.findByText("tool_one")).toBeInTheDocument();
+
+ expect(screen.queryByRole("tab", { name: "All sources" })).not.toBeInTheDocument();
+ expect(screen.queryByRole("tab", { name: "Gateway A" })).not.toBeInTheDocument();
+ });
+
it("moves the active tab with arrow keys", async () => {
const user = userEvent.setup();
renderWithComponents();
diff --git a/src/components/gateways/VirtualServerDetailsPanel.tsx b/src/components/gateways/VirtualServerDetailsPanel.tsx
index 2b02ff4d..4cf81bae 100644
--- a/src/components/gateways/VirtualServerDetailsPanel.tsx
+++ b/src/components/gateways/VirtualServerDetailsPanel.tsx
@@ -399,9 +399,7 @@ export function VirtualServerDetailsPanel({
return `/v1/mcp-servers?${params}`;
}, []);
- const { data: sourcesData, isLoading: sourcesLoading } = useQuery<
- MCPServersResponse | MCPServer[]
- >(sourcesPath, {
+ const { data: sourcesData } = useQuery(sourcesPath, {
enabled: fetchEnabled && sourceIds.length > 0,
});
@@ -637,7 +635,7 @@ export function VirtualServerDetailsPanel({
/>
) : (
<>
- {(sourcesLoading || sourceTabs.length > 0) && (
+ {sourceTabs.length > 1 && (