Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/shared/src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ export const TASKS_PREWARM_SANDBOX_FLAG = "tasks-prewarm-sandbox";
export const GLM_MODEL_FLAG = "posthog-code-glm-model";
/** Spoken narration (agent speaks via the `speak` tool). Gated for a staged rollout. */
export const SPOKEN_NARRATION_FLAG = "posthog-code-spoken-narration";
// Gates importing and relaying local MCP servers into cloud task runs.
export const LOCAL_MCP_IMPORT_FLAG = "posthog-code-local-mcp-import";
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { LocalMcpCloudClassification } from "@posthog/core/local-mcp/localMcpImport";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { renderHook, waitFor } from "@testing-library/react";
import type { ReactNode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";

const mocks = vi.hoisted(() => ({
flagEnabled: false,
getCloudAvailability: vi.fn(),
}));

vi.mock("@posthog/di/react", () => ({
useServiceOptional: () => ({
getCloudAvailability: mocks.getCloudAvailability,
}),
}));

vi.mock("../feature-flags/useFeatureFlag", () => ({
useFeatureFlag: () => mocks.flagEnabled,
}));

import { useLocalMcpCloudServers } from "./useLocalMcpCloudServers";

function wrapper({ children }: { children: ReactNode }) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}

const server: LocalMcpCloudClassification = {
name: "internal-tools",
availability: "requires_desktop",
reason: "stdio_transport",
};

describe("useLocalMcpCloudServers", () => {
beforeEach(() => {
mocks.flagEnabled = false;
mocks.getCloudAvailability.mockReset();
mocks.getCloudAvailability.mockResolvedValue([server]);
});

it("does not inspect local MCP servers while the feature flag is disabled", () => {
const { result } = renderHook(() => useLocalMcpCloudServers(true), {
wrapper,
});

expect(result.current).toEqual({ servers: [], isLoading: false });
expect(mocks.getCloudAvailability).not.toHaveBeenCalled();
});

it("returns local MCP servers while the feature flag is enabled", async () => {
mocks.flagEnabled = true;

const { result } = renderHook(() => useLocalMcpCloudServers(true), {
wrapper,
});

await waitFor(() =>
expect(result.current).toEqual({ servers: [server], isLoading: false }),
);
});

it("hides cached local MCP servers when the feature flag is disabled", async () => {
mocks.flagEnabled = true;
const { result, rerender } = renderHook(
() => useLocalMcpCloudServers(true),
{ wrapper },
);
await waitFor(() => expect(result.current.servers).toEqual([server]));

mocks.flagEnabled = false;
rerender();

expect(result.current).toEqual({ servers: [], isLoading: false });
});
});
13 changes: 10 additions & 3 deletions packages/ui/src/features/local-mcp/useLocalMcpCloudServers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import type {
LocalMcpImportService,
} from "@posthog/core/local-mcp/localMcpImport";
import { useServiceOptional } from "@posthog/di/react";
import { LOCAL_MCP_IMPORT_FLAG } from "@posthog/shared";
import { useQuery } from "@tanstack/react-query";
import { useFeatureFlag } from "../feature-flags/useFeatureFlag";

// Stable identity so consumers' memo deps don't churn while data is absent.
const NO_SERVERS: LocalMcpCloudClassification[] = [];
Expand All @@ -19,21 +21,26 @@ export interface LocalMcpCloudServersResult {
/**
* The user's local (~/.claude.json) MCP servers classified by cloud
* availability. Empty on hosts without a local workspace (web/mobile — the
* service is only bound on desktop).
* service is only bound on desktop) and while the feature flag is off.
*/
export function useLocalMcpCloudServers(
enabled: boolean,
): LocalMcpCloudServersResult {
const service = useServiceOptional<LocalMcpImportService>(
LOCAL_MCP_IMPORT_SERVICE,
);
const flagEnabled = useFeatureFlag(LOCAL_MCP_IMPORT_FLAG);
const queryEnabled = enabled && flagEnabled && !!service;

const query = useQuery({
queryKey: ["local-mcp-cloud-availability"],
queryFn: () => (service ? service.getCloudAvailability() : NO_SERVERS),
enabled: enabled && !!service,
enabled: queryEnabled,
staleTime: 30_000,
});

return { servers: query.data ?? NO_SERVERS, isLoading: query.isLoading };
return {
servers: queryEnabled ? (query.data ?? NO_SERVERS) : NO_SERVERS,
isLoading: queryEnabled && query.isLoading,
};
}
Loading