Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit c4ecb71

Browse files
authored
fix(mcp-gateway): give connected members a refresh-tools affordance
The only "Refresh tools from server" button lived inside the scope-switcher bar, which is gated to admins/agent-managers with multiple scopes. A plain connected member never saw it, yet the empty-state copy told them to "Refresh to try again." Render the refresh button in the always-visible Tools header row whenever the scope bar is hidden, so any connected member can manually retry tool discovery against their own credential. Generated-By: PostHog Code Task-Id: 4df9bfd3-de17-470b-a26d-7214a2e2af17
1 parent f11f3d3 commit c4ecb71

2 files changed

Lines changed: 84 additions & 26 deletions

File tree

‎packages/ui/src/features/mcp-gateway/components/parts/GatewayServerDetail.test.tsx‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const mocks = vi.hoisted(() => ({
88
gateway: {} as Record<string, unknown>,
99
setMemberAccess: vi.fn(),
1010
currentUser: null as { id: number } | null,
11+
refresh: vi.fn(),
1112
}));
1213

1314
vi.mock("@posthog/ui/features/auth/authClient", () => ({
@@ -27,7 +28,7 @@ vi.mock(
2728
setPolicy: vi.fn(),
2829
setAll: vi.fn(),
2930
setAllPending: false,
30-
refresh: vi.fn(),
31+
refresh: mocks.refresh,
3132
refreshPending: false,
3233
}),
3334
}),
@@ -260,4 +261,36 @@ describe("GatewayServerDetail", () => {
260261
},
261262
});
262263
});
264+
265+
it("lets a connected plain member refresh tools", async () => {
266+
const connectedServer = {
267+
...server,
268+
your_connection: {
269+
installation_id: "installation-2",
270+
is_enabled: true,
271+
pending_oauth: false,
272+
needs_reauth: false,
273+
last_used_at: null,
274+
},
275+
} as McpGatewayServer;
276+
mocks.gateway.servers = [connectedServer];
277+
278+
const user = userEvent.setup();
279+
render(
280+
<Theme>
281+
<GatewayServerDetail
282+
serverId={connectedServer.id}
283+
isAdmin={false}
284+
canManageAgentAccess={false}
285+
onNavigate={vi.fn()}
286+
/>
287+
</Theme>,
288+
);
289+
290+
await user.click(
291+
screen.getByRole("button", { name: "Refresh tools from server" }),
292+
);
293+
294+
expect(mocks.refresh).toHaveBeenCalledWith("installation-2");
295+
});
263296
});

‎packages/ui/src/features/mcp-gateway/components/parts/GatewayServerDetail.tsx‎

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export function GatewayServerDetail({
210210
// Any connected member can re-list — it runs against their own credential,
211211
// and it is the manual retry when auto-discovery failed.
212212
const refreshInstallationId = liveInstallationId;
213+
const showScopeBar = (isAdmin || canManageAgentAccess) && scopes.length > 1;
213214

214215
const setBulkPolicy = (state: McpApprovalState) => {
215216
tools.setAll(
@@ -447,18 +448,26 @@ export function GatewayServerDetail({
447448
)}
448449
</Flex>
449450
</Flex>
450-
{!isAdmin && scopeEditable && scopes.length <= 1 && (
451-
<BulkTrio
452-
label="Set all"
453-
filtered={hasToolSearch}
454-
disabled={tools.setAllPending || bulkEditableCount === 0}
455-
allowNeedsApproval={!agentScope}
456-
onSet={setBulkPolicy}
457-
/>
458-
)}
451+
<Flex align="center" gap="2">
452+
{!isAdmin && scopeEditable && scopes.length <= 1 && (
453+
<BulkTrio
454+
label="Set all"
455+
filtered={hasToolSearch}
456+
disabled={tools.setAllPending || bulkEditableCount === 0}
457+
allowNeedsApproval={!agentScope}
458+
onSet={setBulkPolicy}
459+
/>
460+
)}
461+
{!showScopeBar && refreshInstallationId && (
462+
<RefreshToolsButton
463+
pending={tools.refreshPending}
464+
onRefresh={() => tools.refresh(refreshInstallationId)}
465+
/>
466+
)}
467+
</Flex>
459468
</Flex>
460469

461-
{(isAdmin || canManageAgentAccess) && scopes.length > 1 && (
470+
{showScopeBar && (
462471
<Flex
463472
align="center"
464473
gap="2"
@@ -502,21 +511,10 @@ export function GatewayServerDetail({
502511
onSet={setBulkPolicy}
503512
/>
504513
{refreshInstallationId && (
505-
<Tooltip content="Refresh tools from server">
506-
<IconButton
507-
variant="soft"
508-
color="gray"
509-
size="1"
510-
disabled={tools.refreshPending}
511-
onClick={() => tools.refresh(refreshInstallationId)}
512-
>
513-
{tools.refreshPending ? (
514-
<Spinner size="1" />
515-
) : (
516-
<ArrowClockwise size={11} weight="bold" />
517-
)}
518-
</IconButton>
519-
</Tooltip>
514+
<RefreshToolsButton
515+
pending={tools.refreshPending}
516+
onRefresh={() => tools.refresh(refreshInstallationId)}
517+
/>
520518
)}
521519
</div>
522520
</Flex>
@@ -691,6 +689,33 @@ function BackButton({
691689
);
692690
}
693691

692+
function RefreshToolsButton({
693+
pending,
694+
onRefresh,
695+
}: {
696+
pending: boolean;
697+
onRefresh: () => void;
698+
}) {
699+
return (
700+
<Tooltip content="Refresh tools from server">
701+
<IconButton
702+
variant="soft"
703+
color="gray"
704+
size="1"
705+
aria-label="Refresh tools from server"
706+
disabled={pending}
707+
onClick={onRefresh}
708+
>
709+
{pending ? (
710+
<Spinner size="1" />
711+
) : (
712+
<ArrowClockwise size={11} weight="bold" />
713+
)}
714+
</IconButton>
715+
</Tooltip>
716+
);
717+
}
718+
694719
function BulkTrio({
695720
label,
696721
filtered,

0 commit comments

Comments
 (0)