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

Commit f11f3d3

Browse files
authored
fix(mcp-gateway): stamp the session user as grantor in the optimistic access patch
setAccessMutation's no-refetch cache patch filled granted_by from the stale cache entry (null for a fresh grant), so the "shared by" attribution on the server-detail access row stayed blank until an unrelated refetch. The backend assigns granted_by to the requesting user on every enable, so mirror that by projecting the session user (useCurrentUser) into the patched agents row, falling back to the previous behavior only while the user is still loading. Generated-By: PostHog Code Task-Id: ebf7c1be-72ee-497b-8ed2-225c71d59b32
1 parent c8b4a12 commit f11f3d3

2 files changed

Lines changed: 91 additions & 1 deletion

File tree

‎packages/ui/src/features/mcp-gateway/hooks/useServiceAccounts.test.tsx‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ const mocks = vi.hoisted(() => ({
1212
getAccounts: vi.fn(),
1313
setAccess: vi.fn(),
1414
infoToast: vi.fn(),
15+
currentUser: {
16+
id: 7,
17+
uuid: "user-uuid-7",
18+
distinct_id: "distinct-7",
19+
first_name: "Ada",
20+
last_name: "Lovelace",
21+
email: "ada@posthog.com",
22+
is_email_verified: true,
23+
hedgehog_config: null,
24+
},
1525
}));
1626

1727
vi.mock("@posthog/ui/features/auth/authClient", () => ({
@@ -21,6 +31,11 @@ vi.mock("@posthog/ui/features/auth/authClient", () => ({
2131
}),
2232
}));
2333

34+
vi.mock("@posthog/ui/features/auth/useCurrentUser", async (importOriginal) => ({
35+
...(await importOriginal<Record<string, unknown>>()),
36+
useCurrentUser: () => ({ data: mocks.currentUser }),
37+
}));
38+
2439
vi.mock("@posthog/ui/primitives/toast", () => ({
2540
toast: {
2641
error: vi.fn(),
@@ -104,4 +119,45 @@ describe("useServiceAccounts", () => {
104119
"Support agent no longer has access to Linear",
105120
);
106121
});
122+
123+
it("stamps the current user as grantor on a fresh grant without a refetch", async () => {
124+
queryClient.setQueryData(gatewayKeys.servers, [
125+
{ ...server, agents: [] } as McpGatewayServer,
126+
]);
127+
mocks.setAccess.mockResolvedValue({ ...account, server_ids: [server.id] });
128+
const { result } = renderHook(() => useServiceAccounts(), { wrapper });
129+
130+
await waitFor(() => expect(result.current.accounts).toEqual([account]));
131+
132+
act(() => {
133+
result.current.setAccess({
134+
accountId: account.id,
135+
serverId: server.id,
136+
enabled: true,
137+
});
138+
});
139+
140+
await waitFor(() =>
141+
expect(
142+
queryClient.getQueryData<McpGatewayServer[]>(gatewayKeys.servers)?.[0]
143+
?.agents,
144+
).toHaveLength(1),
145+
);
146+
147+
expect(
148+
queryClient.getQueryData<McpGatewayServer[]>(gatewayKeys.servers)?.[0]
149+
?.agents[0],
150+
).toMatchObject({
151+
service_account_id: account.id,
152+
granted_by: {
153+
id: mocks.currentUser.id,
154+
uuid: mocks.currentUser.uuid,
155+
first_name: "Ada",
156+
last_name: "Lovelace",
157+
email: "ada@posthog.com",
158+
hedgehog_config: null,
159+
},
160+
});
161+
expect(mocks.getAccounts).toHaveBeenCalledTimes(1);
162+
});
107163
});

‎packages/ui/src/features/mcp-gateway/hooks/useServiceAccounts.ts‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,53 @@
11
import type {
22
McpGatewayServer,
3+
McpGatewayUser,
34
McpServiceAccount,
45
McpServiceAccountStatus,
56
McpServiceAccountWithToken,
67
McpToolPolicyEntry,
78
} from "@posthog/api-client/posthog-client";
9+
import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
10+
import { useCurrentUser } from "@posthog/ui/features/auth/useCurrentUser";
811
import { gatewayKeys } from "@posthog/ui/features/mcp-gateway/hooks/gatewayKeys";
912
import { useAuthenticatedMutation } from "@posthog/ui/hooks/useAuthenticatedMutation";
1013
import { useAuthenticatedQuery } from "@posthog/ui/hooks/useAuthenticatedQuery";
1114
import { toast } from "@posthog/ui/primitives/toast";
1215
import { useQueryClient } from "@tanstack/react-query";
1316
import { useCallback, useState } from "react";
1417

18+
/** Project the current-user shape onto the UserBasic fields the gateway serves. */
19+
function toGatewayUser(user: {
20+
id: number;
21+
uuid: string;
22+
distinct_id: string | null;
23+
first_name?: string;
24+
last_name?: string;
25+
email: string;
26+
is_email_verified: boolean | null;
27+
hedgehog_config?: McpGatewayUser["hedgehog_config"];
28+
role_at_organization?: McpGatewayUser["role_at_organization"];
29+
}): McpGatewayUser {
30+
return {
31+
id: user.id,
32+
uuid: user.uuid,
33+
distinct_id: user.distinct_id,
34+
first_name: user.first_name,
35+
last_name: user.last_name,
36+
email: user.email,
37+
is_email_verified: user.is_email_verified,
38+
hedgehog_config: user.hedgehog_config ?? null,
39+
role_at_organization: user.role_at_organization,
40+
};
41+
}
42+
1543
/**
1644
* Agent service accounts and their mutations. A freshly-issued token surfaces
1745
* once via `newToken` on creation and is discarded on dismiss.
1846
*/
1947
export function useServiceAccounts() {
2048
const queryClient = useQueryClient();
49+
const client = useOptionalAuthenticatedClient();
50+
const { data: currentUser } = useCurrentUser({ client });
2151
const [newToken, setNewToken] = useState<McpServiceAccountWithToken | null>(
2252
null,
2353
);
@@ -104,6 +134,8 @@ export function useServiceAccounts() {
104134
onSuccess: (account, vars) => {
105135
// The response is the updated account, so keep both access views in
106136
// sync without racing it against a potentially stale list refetch.
137+
// The backend stamps `granted_by` with the requesting user on every
138+
// enable, so mirror that with the current user here.
107139
queryClient.setQueryData<McpServiceAccount[]>(
108140
gatewayKeys.accounts,
109141
(current) =>
@@ -133,7 +165,9 @@ export function useServiceAccounts() {
133165
handle: account.handle,
134166
status: account.status,
135167
last_active_at: account.last_active_at,
136-
granted_by: currentAccess?.granted_by ?? null,
168+
granted_by: currentUser
169+
? toGatewayUser(currentUser)
170+
: (currentAccess?.granted_by ?? null),
137171
},
138172
]
139173
: withoutAccount,

0 commit comments

Comments
 (0)