diff --git a/apps/mobile/assets/services/airops.png b/apps/mobile/assets/services/airops.png
deleted file mode 100644
index a70365de09..0000000000
Binary files a/apps/mobile/assets/services/airops.png and /dev/null differ
diff --git a/apps/mobile/assets/services/atlassian.svg b/apps/mobile/assets/services/atlassian.svg
deleted file mode 100644
index 8a9e9f402e..0000000000
--- a/apps/mobile/assets/services/atlassian.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/attio.png b/apps/mobile/assets/services/attio.png
deleted file mode 100644
index 3a17902fe4..0000000000
Binary files a/apps/mobile/assets/services/attio.png and /dev/null differ
diff --git a/apps/mobile/assets/services/box.svg b/apps/mobile/assets/services/box.svg
deleted file mode 100644
index 5eec50e55d..0000000000
--- a/apps/mobile/assets/services/box.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/browserbase.svg b/apps/mobile/assets/services/browserbase.svg
deleted file mode 100644
index 5a168aa0ef..0000000000
--- a/apps/mobile/assets/services/browserbase.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/apps/mobile/assets/services/canva.svg b/apps/mobile/assets/services/canva.svg
deleted file mode 100644
index 938fcd63e0..0000000000
--- a/apps/mobile/assets/services/canva.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/circle.png b/apps/mobile/assets/services/circle.png
deleted file mode 100644
index a020b87f5a..0000000000
Binary files a/apps/mobile/assets/services/circle.png and /dev/null differ
diff --git a/apps/mobile/assets/services/cisco_thousandeyes.png b/apps/mobile/assets/services/cisco_thousandeyes.png
deleted file mode 100644
index 5460ea5ad4..0000000000
Binary files a/apps/mobile/assets/services/cisco_thousandeyes.png and /dev/null differ
diff --git a/apps/mobile/assets/services/clerk.svg b/apps/mobile/assets/services/clerk.svg
deleted file mode 100644
index 6870089b04..0000000000
--- a/apps/mobile/assets/services/clerk.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/apps/mobile/assets/services/clickhouse.svg b/apps/mobile/assets/services/clickhouse.svg
deleted file mode 100644
index 6484dc6b01..0000000000
--- a/apps/mobile/assets/services/clickhouse.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/cloudflare.svg b/apps/mobile/assets/services/cloudflare.svg
deleted file mode 100644
index 11ff6493a9..0000000000
--- a/apps/mobile/assets/services/cloudflare.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/apps/mobile/assets/services/context7.svg b/apps/mobile/assets/services/context7.svg
deleted file mode 100644
index 664bf9d94a..0000000000
--- a/apps/mobile/assets/services/context7.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/apps/mobile/assets/services/datadog.svg b/apps/mobile/assets/services/datadog.svg
deleted file mode 100644
index bb83b63370..0000000000
--- a/apps/mobile/assets/services/datadog.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
diff --git a/apps/mobile/assets/services/figma.svg b/apps/mobile/assets/services/figma.svg
deleted file mode 100644
index 5c8bb0d731..0000000000
--- a/apps/mobile/assets/services/figma.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/firetiger.svg b/apps/mobile/assets/services/firetiger.svg
deleted file mode 100644
index d9c1a670f6..0000000000
--- a/apps/mobile/assets/services/firetiger.svg
+++ /dev/null
@@ -1,25 +0,0 @@
-
diff --git a/apps/mobile/assets/services/github.svg b/apps/mobile/assets/services/github.svg
deleted file mode 100755
index 4f46bf3571..0000000000
--- a/apps/mobile/assets/services/github.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/apps/mobile/assets/services/gitlab.svg b/apps/mobile/assets/services/gitlab.svg
deleted file mode 100644
index e0e22ac32e..0000000000
--- a/apps/mobile/assets/services/gitlab.svg
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/granola.svg b/apps/mobile/assets/services/granola.svg
deleted file mode 100644
index e7d62b1455..0000000000
--- a/apps/mobile/assets/services/granola.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/apps/mobile/assets/services/hex.svg b/apps/mobile/assets/services/hex.svg
deleted file mode 100644
index f9aeebe334..0000000000
--- a/apps/mobile/assets/services/hex.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/hubspot.svg b/apps/mobile/assets/services/hubspot.svg
deleted file mode 100644
index 9423b8af9e..0000000000
--- a/apps/mobile/assets/services/hubspot.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/launchdarkly.png b/apps/mobile/assets/services/launchdarkly.png
deleted file mode 100644
index 75a86dda1b..0000000000
Binary files a/apps/mobile/assets/services/launchdarkly.png and /dev/null differ
diff --git a/apps/mobile/assets/services/linear.svg b/apps/mobile/assets/services/linear.svg
deleted file mode 100644
index 53d625598d..0000000000
--- a/apps/mobile/assets/services/linear.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/apps/mobile/assets/services/mem0.svg b/apps/mobile/assets/services/mem0.svg
deleted file mode 100644
index 13a9616ae3..0000000000
--- a/apps/mobile/assets/services/mem0.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/apps/mobile/assets/services/monday.svg b/apps/mobile/assets/services/monday.svg
deleted file mode 100644
index c2feb2b4c0..0000000000
--- a/apps/mobile/assets/services/monday.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
diff --git a/apps/mobile/assets/services/neon.svg b/apps/mobile/assets/services/neon.svg
deleted file mode 100644
index eddf840522..0000000000
--- a/apps/mobile/assets/services/neon.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/apps/mobile/assets/services/notion.svg b/apps/mobile/assets/services/notion.svg
deleted file mode 100644
index bf6442f76a..0000000000
--- a/apps/mobile/assets/services/notion.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/apps/mobile/assets/services/pagerduty.svg b/apps/mobile/assets/services/pagerduty.svg
deleted file mode 100644
index 477f6bb3df..0000000000
--- a/apps/mobile/assets/services/pagerduty.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/planetscale.svg b/apps/mobile/assets/services/planetscale.svg
deleted file mode 100644
index 2f3a95c3c6..0000000000
--- a/apps/mobile/assets/services/planetscale.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/apps/mobile/assets/services/postman.svg b/apps/mobile/assets/services/postman.svg
deleted file mode 100644
index 87ed4ac39a..0000000000
--- a/apps/mobile/assets/services/postman.svg
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/prisma.svg b/apps/mobile/assets/services/prisma.svg
deleted file mode 100644
index fbf24acc72..0000000000
--- a/apps/mobile/assets/services/prisma.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/render.svg b/apps/mobile/assets/services/render.svg
deleted file mode 100644
index cfb5a52e5c..0000000000
--- a/apps/mobile/assets/services/render.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/apps/mobile/assets/services/sanity.svg b/apps/mobile/assets/services/sanity.svg
deleted file mode 100644
index 613194b4c2..0000000000
--- a/apps/mobile/assets/services/sanity.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/apps/mobile/assets/services/sentry.svg b/apps/mobile/assets/services/sentry.svg
deleted file mode 100644
index d810538965..0000000000
--- a/apps/mobile/assets/services/sentry.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/mobile/assets/services/slack.png b/apps/mobile/assets/services/slack.png
deleted file mode 100644
index b034210831..0000000000
Binary files a/apps/mobile/assets/services/slack.png and /dev/null differ
diff --git a/apps/mobile/assets/services/stripe.png b/apps/mobile/assets/services/stripe.png
deleted file mode 100644
index 26850e852e..0000000000
Binary files a/apps/mobile/assets/services/stripe.png and /dev/null differ
diff --git a/apps/mobile/assets/services/supabase.svg b/apps/mobile/assets/services/supabase.svg
deleted file mode 100644
index cb6632ff5d..0000000000
--- a/apps/mobile/assets/services/supabase.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/apps/mobile/assets/services/svelte.png b/apps/mobile/assets/services/svelte.png
deleted file mode 100644
index 7008ac16e6..0000000000
Binary files a/apps/mobile/assets/services/svelte.png and /dev/null differ
diff --git a/apps/mobile/assets/services/wix.png b/apps/mobile/assets/services/wix.png
deleted file mode 100644
index ee0a13b0d3..0000000000
Binary files a/apps/mobile/assets/services/wix.png and /dev/null differ
diff --git a/apps/mobile/src/app/mcp-servers/installation/[id].tsx b/apps/mobile/src/app/mcp-servers/installation/[id].tsx
index 21c5edf5be..d0dd6fcd02 100644
--- a/apps/mobile/src/app/mcp-servers/installation/[id].tsx
+++ b/apps/mobile/src/app/mcp-servers/installation/[id].tsx
@@ -158,7 +158,11 @@ export default function McpInstallationDetailScreen() {
>
{/* Header */}
-
+
{installation.display_name || installation.name}
diff --git a/apps/mobile/src/app/mcp-servers/template/[id].tsx b/apps/mobile/src/app/mcp-servers/template/[id].tsx
index 1bc61b2e92..78e138c19a 100644
--- a/apps/mobile/src/app/mcp-servers/template/[id].tsx
+++ b/apps/mobile/src/app/mcp-servers/template/[id].tsx
@@ -121,7 +121,11 @@ export default function McpTemplateDetailScreen() {
}}
>
-
+
{template.name}
diff --git a/apps/mobile/src/features/mcp/components/McpServerRow.tsx b/apps/mobile/src/features/mcp/components/McpServerRow.tsx
index a2526fa348..0cfd05775f 100644
--- a/apps/mobile/src/features/mcp/components/McpServerRow.tsx
+++ b/apps/mobile/src/features/mcp/components/McpServerRow.tsx
@@ -16,7 +16,8 @@ interface McpServerRowProps {
isStdio?: boolean;
needsReauth?: boolean;
installed?: boolean;
- iconKey?: string | null;
+ iconDomain?: string | null;
+ serverUrl?: string | null;
onPress: () => void;
}
@@ -35,7 +36,8 @@ export function McpServerRow({
isStdio,
needsReauth,
installed,
- iconKey,
+ iconDomain,
+ serverUrl,
onPress,
}: McpServerRowProps) {
const themeColors = useThemeColors();
@@ -46,7 +48,7 @@ export function McpServerRow({
onPress={onPress}
className="flex-row items-center gap-3 border-gray-5 border-b bg-card px-4 py-3 active:bg-gray-2"
>
-
+
onPress(template),
};
}
@@ -137,7 +140,8 @@ export function installationToRowProps(
isStdio: isStdioServer(installation),
needsReauth: installation.needs_reauth,
installed: true,
- iconKey: installation.icon_key,
+ iconDomain: installation.icon_domain,
+ serverUrl: installation.url,
onPress: () => onPress(installation),
};
}
diff --git a/apps/mobile/src/features/mcp/components/ServerIcon.tsx b/apps/mobile/src/features/mcp/components/ServerIcon.tsx
index f0fe08ef5d..ccfb78feb7 100644
--- a/apps/mobile/src/features/mcp/components/ServerIcon.tsx
+++ b/apps/mobile/src/features/mcp/components/ServerIcon.tsx
@@ -1,35 +1,73 @@
+import { useColorScheme } from "nativewind";
import { PuzzlePiece } from "phosphor-react-native";
+import { useState } from "react";
import { Image, View } from "react-native";
+import { useAuthStore } from "@/features/auth";
import { useThemeColors } from "@/lib/theme";
-import { resolveServerLogo } from "./serverIcons";
+import { iconDomainFromServerUrl } from "../iconDomain";
interface ServerIconProps {
- iconKey?: string | null;
+ /** The template's brand domain (`icon_domain`). Falls back to deriving one from serverUrl. */
+ iconDomain?: string | null;
+ /** The MCP server URL — lets custom installs without a template still get a brand icon. */
+ serverUrl?: string | null;
size?: number;
className?: string;
}
/**
- * Renders the brand logo for an MCP server, keyed by `icon_key` from the
- * PostHog cloud schema. Falls back to a generic plug glyph when the icon
- * key is missing or doesn't match the bundled set.
+ * Renders the brand logo for an MCP server through the authenticated logo.dev
+ * icon proxy (`mcp_servers/icon/`), keyed by the template's `icon_domain` with
+ * a best-effort domain derived from the server URL as fallback. Falls back to
+ * a generic plug glyph when no domain resolves or the proxy has no icon (404).
*/
-export function ServerIcon({ iconKey, size = 32, className }: ServerIconProps) {
+export function ServerIcon({
+ iconDomain,
+ serverUrl,
+ size = 32,
+ className,
+}: ServerIconProps) {
const themeColors = useThemeColors();
- const logo = resolveServerLogo(iconKey);
+ const { colorScheme } = useColorScheme();
+ const oauthAccessToken = useAuthStore((state) => state.oauthAccessToken);
+ const cloudRegion = useAuthStore((state) => state.cloudRegion);
+ const projectId = useAuthStore((state) => state.projectId);
+ const getCloudUrlFromRegion = useAuthStore(
+ (state) => state.getCloudUrlFromRegion,
+ );
+
+ const domain = iconDomain || iconDomainFromServerUrl(serverUrl);
+ // logo.dev picks the logo variant suited to the active background theme.
+ const theme = colorScheme === "dark" ? "dark" : "light";
+ // Failure latches per (domain, theme) — the unit the request URL varies
+ // over — so a transient failure in one theme doesn't blank the other and a
+ // theme flip retries.
+ const iconCacheKey = `${domain}|${theme}`;
+ const [failedIconKey, setFailedIconKey] = useState(null);
+
+ const iconUrl =
+ domain &&
+ oauthAccessToken &&
+ cloudRegion &&
+ projectId &&
+ failedIconKey !== iconCacheKey
+ ? `${getCloudUrlFromRegion(cloudRegion)}/api/environments/${projectId}/mcp_servers/icon/?domain=${encodeURIComponent(domain)}&theme=${theme}`
+ : null;
return (
- {logo?.kind === "svg" && typeof logo.component === "function" ? (
-
- ) : logo?.kind === "png" ? (
+ {iconUrl ? (
setFailedIconKey(iconCacheKey)}
/>
) : (
`.
-const AiropsPng: ImageSourcePropType = require("../../../../assets/services/airops.png");
-const AttioPng: ImageSourcePropType = require("../../../../assets/services/attio.png");
-const CirclePng: ImageSourcePropType = require("../../../../assets/services/circle.png");
-const CiscoThousandeyesPng: ImageSourcePropType = require("../../../../assets/services/cisco_thousandeyes.png");
-const LaunchDarklyPng: ImageSourcePropType = require("../../../../assets/services/launchdarkly.png");
-const SlackPng: ImageSourcePropType = require("../../../../assets/services/slack.png");
-const StripePng: ImageSourcePropType = require("../../../../assets/services/stripe.png");
-const SveltePng: ImageSourcePropType = require("../../../../assets/services/svelte.png");
-const WixPng: ImageSourcePropType = require("../../../../assets/services/wix.png");
-
-export type ServerLogo =
- | { kind: "svg"; component: ComponentType }
- | { kind: "png"; source: ImageSourcePropType };
-
-function svg(component: ComponentType): ServerLogo {
- if (typeof component !== "function") {
- log.warn("SVG import resolved as non-component", {
- type: typeof component,
- });
- }
- return { kind: "svg", component };
-}
-
-function png(source: ImageSourcePropType): ServerLogo {
- return { kind: "png", source };
-}
-
-/** Lookup map keyed by `McpServerInstallation.icon_key` /
- * `McpRecommendedServer.icon_key`. Mirrors the desktop `BRAND_ICONS`. */
-export const SERVER_LOGOS: Record = {
- airops: png(AiropsPng),
- atlassian: svg(AtlassianSvg),
- attio: png(AttioPng),
- box: svg(BoxSvg),
- browserbase: svg(BrowserbaseSvg),
- canva: svg(CanvaSvg),
- circle: png(CirclePng),
- cisco_thousandeyes: png(CiscoThousandeyesPng),
- clerk: svg(ClerkSvg),
- clickhouse: svg(ClickHouseSvg),
- cloudflare: svg(CloudflareSvg),
- context7: svg(Context7Svg),
- datadog: svg(DatadogSvg),
- figma: svg(FigmaSvg),
- firetiger: svg(FiretigerSvg),
- github: svg(GitHubSvg),
- gitlab: svg(GitLabSvg),
- granola: svg(GranolaSvg),
- hex: svg(HexSvg),
- hubspot: svg(HubSpotSvg),
- launchdarkly: png(LaunchDarklyPng),
- linear: svg(LinearSvg),
- mem0: svg(Mem0Svg),
- monday: svg(MondaySvg),
- neon: svg(NeonSvg),
- notion: svg(NotionSvg),
- pagerduty: svg(PagerDutySvg),
- planetscale: svg(PlanetScaleSvg),
- postman: svg(PostmanSvg),
- prisma: svg(PrismaSvg),
- render: svg(RenderSvg),
- sanity: svg(SanitySvg),
- sentry: svg(SentrySvg),
- slack: png(SlackPng),
- stripe: png(StripePng),
- supabase: svg(SupabaseSvg),
- svelte: png(SveltePng),
- wix: png(WixPng),
-};
-
-export function resolveServerLogo(
- iconKey: string | null | undefined,
-): ServerLogo | null {
- if (!iconKey) return null;
- return SERVER_LOGOS[iconKey] ?? null;
-}
diff --git a/apps/mobile/src/features/mcp/iconDomain.test.ts b/apps/mobile/src/features/mcp/iconDomain.test.ts
new file mode 100644
index 0000000000..9cdcffb786
--- /dev/null
+++ b/apps/mobile/src/features/mcp/iconDomain.test.ts
@@ -0,0 +1,36 @@
+import { describe, expect, it } from "vitest";
+import { iconDomainFromServerUrl } from "./iconDomain";
+
+// Mirrors packages/core/src/mcp-servers/iconDomain.test.ts — the helper is
+// duplicated because mobile doesn't depend on @posthog/core.
+describe("iconDomainFromServerUrl", () => {
+ it.each([
+ ["strips mcp. from deep hosts", "https://mcp.linear.app/sse", "linear.app"],
+ ["strips api. from deep hosts", "https://api.acme.com/mcp", "acme.com"],
+ ["strips www. from deep hosts", "https://www.acme.com/mcp", "acme.com"],
+ ["keeps bare brand domains", "https://notion.com/mcp", "notion.com"],
+ [
+ "keeps two-label hosts named like a prefix",
+ "https://mcp.com/x",
+ "mcp.com",
+ ],
+ [
+ "keeps unrelated subdomains",
+ "https://server.smithery.ai/mcp",
+ "server.smithery.ai",
+ ],
+ ["lowercases the host", "https://MCP.Linear.APP/sse", "linear.app"],
+ ])("%s", (_name, url, expected) => {
+ expect(iconDomainFromServerUrl(url)).toBe(expected);
+ });
+
+ it.each([
+ ["null", null],
+ ["undefined", undefined],
+ ["an empty string", ""],
+ ["a non-URL", "not a url"],
+ ["a dotless host", "https://localhost:3000/mcp"],
+ ])("returns null for %s", (_name, url) => {
+ expect(iconDomainFromServerUrl(url)).toBeNull();
+ });
+});
diff --git a/apps/mobile/src/features/mcp/iconDomain.ts b/apps/mobile/src/features/mcp/iconDomain.ts
new file mode 100644
index 0000000000..a5c5f89265
--- /dev/null
+++ b/apps/mobile/src/features/mcp/iconDomain.ts
@@ -0,0 +1,33 @@
+// Mirrors `@posthog/core/mcp-servers/iconDomain` (mobile doesn't depend on
+// @posthog/core, same as the hand-mirrored types in ./types.ts).
+
+// Machine-facing subdomains stripped when deriving a brand domain from a
+// server URL, so a custom install at https://mcp.linear.app/mcp still
+// resolves the vendor's brand (linear.app).
+const STRIPPED_SUBDOMAINS = ["mcp.", "api.", "www."];
+
+/**
+ * Best-effort brand domain for an MCP server without a template-provided
+ * `icon_domain`: the server URL's hostname with machine-facing subdomains
+ * stripped. Null when no plausible brand domain exists (invalid URL, or a
+ * dotless host like localhost).
+ */
+export function iconDomainFromServerUrl(
+ serverUrl: string | null | undefined,
+): string | null {
+ if (!serverUrl) {
+ return null;
+ }
+ let host: string;
+ try {
+ host = new URL(serverUrl).hostname.toLowerCase();
+ } catch {
+ return null;
+ }
+ for (const prefix of STRIPPED_SUBDOMAINS) {
+ if (host.startsWith(prefix) && host.split(".").length >= 3) {
+ return host.slice(prefix.length);
+ }
+ }
+ return host.includes(".") ? host : null;
+}
diff --git a/apps/mobile/src/features/mcp/types.ts b/apps/mobile/src/features/mcp/types.ts
index b8cdb4c439..74f259ef0c 100644
--- a/apps/mobile/src/features/mcp/types.ts
+++ b/apps/mobile/src/features/mcp/types.ts
@@ -15,7 +15,9 @@ export interface McpRecommendedServer {
docs_url?: string;
description?: string;
auth_type?: McpAuthType;
- icon_key?: string;
+ /** The vendor's brand domain (e.g. "linear.app"), rendered via the
+ * logo.dev icon proxy. Empty when no brand icon is known. */
+ icon_domain?: string;
category?: string;
/** Some templates expose a `transport_type` ("stdio" | "streamable_http"); when
* absent, treat as HTTP. Stdio servers can't run on mobile; we badge them. */
@@ -27,7 +29,9 @@ export interface McpServerInstallation {
id: string;
template_id: string | null;
name: string;
- icon_key: string;
+ /** Brand domain from the linked template, rendered via the logo.dev icon
+ * proxy. Empty if custom install (no template). */
+ icon_domain?: string;
display_name?: string;
url?: string;
description?: string;
diff --git a/packages/api-client/src/generated.ts b/packages/api-client/src/generated.ts
index 4f16fdb00c..fc57711b82 100644
--- a/packages/api-client/src/generated.ts
+++ b/packages/api-client/src/generated.ts
@@ -9711,7 +9711,10 @@ export namespace Schemas {
id: string;
template_id: string | null;
name: string;
+ /** Deprecated: use icon_domain instead. Lowercase key from the linked template for clients that still render bundled icon assets. Empty if custom install (no template). */
icon_key: string;
+ /** Brand domain from the linked template, rendered via the logo.dev icon proxy. Empty if custom install (no template). */
+ icon_domain: string;
display_name?: string | undefined;
url?: string | undefined;
description?: string | undefined;
@@ -9744,7 +9747,10 @@ export namespace Schemas {
docs_url?: string | undefined;
description?: string | undefined;
auth_type?: MCPAuthTypeEnum | undefined;
- icon_key?: string | undefined;
+ /** Deprecated: use icon_domain instead. Lowercase key for clients that still render bundled icon assets. */
+ icon_key: string;
+ /** The vendor's brand domain (e.g. 'linear.app'), resolved to an icon at render time via the logo.dev proxy endpoint. Empty when no brand icon is known. */
+ icon_domain: string;
category?: CategoryEnum | undefined;
};
export type MarkToleratedInput = { snapshot_id: string };
diff --git a/packages/api-client/src/posthog-client.test.ts b/packages/api-client/src/posthog-client.test.ts
index 927fd90fc2..9eb76b1cfe 100644
--- a/packages/api-client/src/posthog-client.test.ts
+++ b/packages/api-client/src/posthog-client.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
+import { ApiRequestError } from "./fetcher";
import { PostHogAPIClient } from "./posthog-client";
describe("PostHogAPIClient", () => {
@@ -1763,4 +1764,70 @@ describe("PostHogAPIClient", () => {
});
});
});
+
+ describe("getMcpServerIconUrl", () => {
+ function makeClient(fetch: ReturnType) {
+ const client = new PostHogAPIClient(
+ "http://localhost:8000",
+ async () => "token",
+ async () => "token",
+ 123,
+ );
+ (
+ client as unknown as {
+ api: { baseUrl: string; fetcher: { fetch: typeof fetch } };
+ }
+ ).api = { baseUrl: "http://localhost:8000", fetcher: { fetch } };
+ return client;
+ }
+
+ it("requests the icon proxy and returns an object URL for the bytes", async () => {
+ const fetch = vi
+ .fn()
+ .mockResolvedValue(
+ new Response(new Blob(["png"], { type: "image/png" })),
+ );
+ const client = makeClient(fetch);
+
+ const url = await client.getMcpServerIconUrl("linear.app", "dark");
+
+ expect(url).toMatch(/^blob:/);
+ expect(fetch.mock.calls[0][0].url.toString()).toBe(
+ "http://localhost:8000/api/environments/123/mcp_servers/icon/?domain=linear.app&theme=dark",
+ );
+ });
+
+ it("omits the theme param when none is given", async () => {
+ const fetch = vi
+ .fn()
+ .mockResolvedValue(
+ new Response(new Blob(["png"], { type: "image/png" })),
+ );
+ const client = makeClient(fetch);
+
+ await client.getMcpServerIconUrl("linear.app");
+
+ expect(fetch.mock.calls[0][0].url.toString()).toBe(
+ "http://localhost:8000/api/environments/123/mcp_servers/icon/?domain=linear.app",
+ );
+ });
+
+ it("treats the proxy's 404 as a definitive no-icon null, not a failure", async () => {
+ const fetch = vi.fn().mockRejectedValue(new ApiRequestError(404, "{}"));
+ const client = makeClient(fetch);
+
+ await expect(
+ client.getMcpServerIconUrl("no-logo.example"),
+ ).resolves.toBeNull();
+ });
+
+ it("propagates non-404 failures so callers can retry", async () => {
+ const fetch = vi.fn().mockRejectedValue(new ApiRequestError(500, "{}"));
+ const client = makeClient(fetch);
+
+ await expect(client.getMcpServerIconUrl("linear.app")).rejects.toThrow(
+ "Failed request: [500]",
+ );
+ });
+ });
});
diff --git a/packages/api-client/src/posthog-client.ts b/packages/api-client/src/posthog-client.ts
index bc33a43596..e4d994957b 100644
--- a/packages/api-client/src/posthog-client.ts
+++ b/packages/api-client/src/posthog-client.ts
@@ -96,7 +96,7 @@ import {
type HogQLGrid,
shapeAgentAnalytics,
} from "./agent-analytics";
-import { buildApiFetcher } from "./fetcher";
+import { buildApiFetcher, requestErrorStatus } from "./fetcher";
import { createApiClient, type Schemas } from "./generated";
import type { SpendAnalysisResponse } from "./spend-analysis";
export interface ApiClientLogger {
@@ -4200,6 +4200,40 @@ export class PostHogAPIClient {
return data.results ?? [];
}
+ /**
+ * Object URL for an MCP server's brand icon, proxied from logo.dev by the
+ * authenticated `mcp_servers/icon/` endpoint. Returns null when no brand
+ * icon exists for the domain (the endpoint 404s so callers render their own
+ * fallback glyph, e.g. on self-hosted instances without a logo.dev token).
+ */
+ async getMcpServerIconUrl(
+ domain: string,
+ theme?: "light" | "dark",
+ ): Promise {
+ const teamId = await this.getTeamId();
+ const path = `/api/environments/${teamId}/mcp_servers/icon/`;
+ const url = new URL(`${this.api.baseUrl}${path}`);
+ url.searchParams.set("domain", domain);
+ if (theme) {
+ url.searchParams.set("theme", theme);
+ }
+ let response: Response;
+ try {
+ response = await this.api.fetcher.fetch({
+ method: "get",
+ url,
+ path,
+ });
+ } catch (error) {
+ // 404 is the endpoint's definitive "no icon for this domain" answer,
+ // not a failure; anything else propagates so callers can retry.
+ if (requestErrorStatus(error) === 404) return null;
+ throw error;
+ }
+ const blob = await response.blob();
+ return URL.createObjectURL(blob);
+ }
+
async getMcpServerInstallations(): Promise {
const teamId = await this.getTeamId();
const url = new URL(
diff --git a/packages/core/src/mcp-servers/iconDomain.test.ts b/packages/core/src/mcp-servers/iconDomain.test.ts
new file mode 100644
index 0000000000..b32786a330
--- /dev/null
+++ b/packages/core/src/mcp-servers/iconDomain.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it } from "vitest";
+import { iconDomainFromServerUrl } from "./iconDomain";
+
+describe("iconDomainFromServerUrl", () => {
+ it.each([
+ ["strips mcp. from deep hosts", "https://mcp.linear.app/sse", "linear.app"],
+ ["strips api. from deep hosts", "https://api.acme.com/mcp", "acme.com"],
+ ["strips www. from deep hosts", "https://www.acme.com/mcp", "acme.com"],
+ ["keeps bare brand domains", "https://notion.com/mcp", "notion.com"],
+ [
+ "keeps two-label hosts named like a prefix",
+ "https://mcp.com/x",
+ "mcp.com",
+ ],
+ [
+ "keeps unrelated subdomains",
+ "https://server.smithery.ai/mcp",
+ "server.smithery.ai",
+ ],
+ ["lowercases the host", "https://MCP.Linear.APP/sse", "linear.app"],
+ ])("%s", (_name, url, expected) => {
+ expect(iconDomainFromServerUrl(url)).toBe(expected);
+ });
+
+ it.each([
+ ["null", null],
+ ["undefined", undefined],
+ ["an empty string", ""],
+ ["a non-URL", "not a url"],
+ ["a dotless host", "https://localhost:3000/mcp"],
+ ])("returns null for %s", (_name, url) => {
+ expect(iconDomainFromServerUrl(url)).toBeNull();
+ });
+});
diff --git a/packages/core/src/mcp-servers/iconDomain.ts b/packages/core/src/mcp-servers/iconDomain.ts
new file mode 100644
index 0000000000..8f7506c0ca
--- /dev/null
+++ b/packages/core/src/mcp-servers/iconDomain.ts
@@ -0,0 +1,30 @@
+// Machine-facing subdomains stripped when deriving a brand domain from a
+// server URL, so a custom install at https://mcp.linear.app/mcp still
+// resolves the vendor's brand (linear.app).
+const STRIPPED_SUBDOMAINS = ["mcp.", "api.", "www."];
+
+/**
+ * Best-effort brand domain for an MCP server without a template-provided
+ * `icon_domain`: the server URL's hostname with machine-facing subdomains
+ * stripped. Null when no plausible brand domain exists (invalid URL, or a
+ * dotless host like localhost).
+ */
+export function iconDomainFromServerUrl(
+ serverUrl: string | null | undefined,
+): string | null {
+ if (!serverUrl) {
+ return null;
+ }
+ let host: string;
+ try {
+ host = new URL(serverUrl).hostname.toLowerCase();
+ } catch {
+ return null;
+ }
+ for (const prefix of STRIPPED_SUBDOMAINS) {
+ if (host.startsWith(prefix) && host.split(".").length >= 3) {
+ return host.slice(prefix.length);
+ }
+ }
+ return host.includes(".") ? host : null;
+}
diff --git a/packages/core/src/mcp-servers/resolveServerName.test.ts b/packages/core/src/mcp-servers/resolveServerName.test.ts
index 2960eecc37..93d5a76459 100644
--- a/packages/core/src/mcp-servers/resolveServerName.test.ts
+++ b/packages/core/src/mcp-servers/resolveServerName.test.ts
@@ -16,7 +16,7 @@ function installation(
id: "inst-1",
template_id: null,
name: "",
- icon_key: "",
+ icon_domain: "",
proxy_url: "https://proxy.example.com/inst-1",
tool_count: 0,
created_at: "2026-01-01T00:00:00Z",
@@ -59,19 +59,33 @@ describe("resolveServerName", () => {
describe("resolveServerDetails", () => {
it("resolves description/docs/icon/auth fallbacks", () => {
const out = resolveServerDetails(
- installation({ name: "N", icon_key: "" }),
+ installation({
+ name: "N",
+ icon_domain: "",
+ url: "https://mcp.acme.dev/mcp",
+ }),
template({
description: "desc",
docs_url: "https://docs",
- icon_key: "k",
+ icon_domain: "linear.app",
}),
);
expect(out.name).toBe("N");
expect(out.description).toBe("desc");
expect(out.docsUrl).toBe("https://docs");
- expect(out.iconKey).toBe("k");
+ expect(out.iconDomain).toBe("linear.app");
+ expect(out.serverUrl).toBe("https://mcp.acme.dev/mcp");
expect(out.authType).toBe("oauth");
});
+
+ it("falls back to the template url when the installation has none", () => {
+ const out = resolveServerDetails(
+ null,
+ template({ icon_domain: "", url: "https://mcp.linear.app/sse" }),
+ );
+ expect(out.iconDomain).toBeNull();
+ expect(out.serverUrl).toBe("https://mcp.linear.app/sse");
+ });
});
describe("sortInstallationsByName", () => {
diff --git a/packages/core/src/mcp-servers/resolveServerName.ts b/packages/core/src/mcp-servers/resolveServerName.ts
index f63e540b1e..e7facc61da 100644
--- a/packages/core/src/mcp-servers/resolveServerName.ts
+++ b/packages/core/src/mcp-servers/resolveServerName.ts
@@ -20,7 +20,8 @@ export interface ResolvedServerDetails {
name: string;
description: string;
docsUrl: string | null;
- iconKey: string | null;
+ iconDomain: string | null;
+ serverUrl: string | null;
authType: McpRecommendedServer["auth_type"] | undefined;
}
@@ -37,7 +38,8 @@ export function resolveServerDetails(
"Server",
description: installation?.description || template?.description || "",
docsUrl: template?.docs_url || null,
- iconKey: installation?.icon_key || template?.icon_key || null,
+ iconDomain: installation?.icon_domain || template?.icon_domain || null,
+ serverUrl: installation?.url || template?.url || null,
authType: installation?.auth_type || template?.auth_type,
};
}
diff --git a/packages/core/src/mcp-servers/status.test.ts b/packages/core/src/mcp-servers/status.test.ts
index 8b6d02c9ca..23bb277a1b 100644
--- a/packages/core/src/mcp-servers/status.test.ts
+++ b/packages/core/src/mcp-servers/status.test.ts
@@ -10,6 +10,7 @@ function makeInstallation(
template_id: null,
name: "Test",
icon_key: "",
+ icon_domain: "",
proxy_url: "https://proxy.example.com/inst-1",
tool_count: 0,
created_at: "2026-01-01T00:00:00Z",
diff --git a/packages/ui/src/assets/services/airops.png b/packages/ui/src/assets/services/airops.png
deleted file mode 100644
index a70365de09..0000000000
Binary files a/packages/ui/src/assets/services/airops.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/atlassian.svg b/packages/ui/src/assets/services/atlassian.svg
deleted file mode 100644
index 8a9e9f402e..0000000000
--- a/packages/ui/src/assets/services/atlassian.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/attio.png b/packages/ui/src/assets/services/attio.png
deleted file mode 100644
index 6065fbe897..0000000000
Binary files a/packages/ui/src/assets/services/attio.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/box.svg b/packages/ui/src/assets/services/box.svg
deleted file mode 100644
index 5eec50e55d..0000000000
--- a/packages/ui/src/assets/services/box.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/browserbase.svg b/packages/ui/src/assets/services/browserbase.svg
deleted file mode 100644
index 5a168aa0ef..0000000000
--- a/packages/ui/src/assets/services/browserbase.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/canva.svg b/packages/ui/src/assets/services/canva.svg
deleted file mode 100644
index 938fcd63e0..0000000000
--- a/packages/ui/src/assets/services/canva.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/circle.png b/packages/ui/src/assets/services/circle.png
deleted file mode 100644
index a020b87f5a..0000000000
Binary files a/packages/ui/src/assets/services/circle.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/cisco_thousandeyes.png b/packages/ui/src/assets/services/cisco_thousandeyes.png
deleted file mode 100644
index 5460ea5ad4..0000000000
Binary files a/packages/ui/src/assets/services/cisco_thousandeyes.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/clerk.svg b/packages/ui/src/assets/services/clerk.svg
deleted file mode 100644
index 6870089b04..0000000000
--- a/packages/ui/src/assets/services/clerk.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/clickhouse.svg b/packages/ui/src/assets/services/clickhouse.svg
deleted file mode 100644
index 6484dc6b01..0000000000
--- a/packages/ui/src/assets/services/clickhouse.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/cloudflare.svg b/packages/ui/src/assets/services/cloudflare.svg
deleted file mode 100644
index 11ff6493a9..0000000000
--- a/packages/ui/src/assets/services/cloudflare.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/context7.svg b/packages/ui/src/assets/services/context7.svg
deleted file mode 100644
index 664bf9d94a..0000000000
--- a/packages/ui/src/assets/services/context7.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/packages/ui/src/assets/services/datadog.svg b/packages/ui/src/assets/services/datadog.svg
deleted file mode 100644
index bb83b63370..0000000000
--- a/packages/ui/src/assets/services/datadog.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/figma.svg b/packages/ui/src/assets/services/figma.svg
deleted file mode 100644
index 5c8bb0d731..0000000000
--- a/packages/ui/src/assets/services/figma.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/firetiger.svg b/packages/ui/src/assets/services/firetiger.svg
deleted file mode 100644
index d9c1a670f6..0000000000
--- a/packages/ui/src/assets/services/firetiger.svg
+++ /dev/null
@@ -1,25 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/github.svg b/packages/ui/src/assets/services/github.svg
deleted file mode 100755
index 4f46bf3571..0000000000
--- a/packages/ui/src/assets/services/github.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/gitlab.svg b/packages/ui/src/assets/services/gitlab.svg
deleted file mode 100644
index e0e22ac32e..0000000000
--- a/packages/ui/src/assets/services/gitlab.svg
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/granola.svg b/packages/ui/src/assets/services/granola.svg
deleted file mode 100644
index e7d62b1455..0000000000
--- a/packages/ui/src/assets/services/granola.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/hex.svg b/packages/ui/src/assets/services/hex.svg
deleted file mode 100644
index f9aeebe334..0000000000
--- a/packages/ui/src/assets/services/hex.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/hubspot.svg b/packages/ui/src/assets/services/hubspot.svg
deleted file mode 100644
index 9423b8af9e..0000000000
--- a/packages/ui/src/assets/services/hubspot.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/launchdarkly.png b/packages/ui/src/assets/services/launchdarkly.png
deleted file mode 100644
index 75a86dda1b..0000000000
Binary files a/packages/ui/src/assets/services/launchdarkly.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/linear.svg b/packages/ui/src/assets/services/linear.svg
deleted file mode 100644
index 53d625598d..0000000000
--- a/packages/ui/src/assets/services/linear.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/mem0.svg b/packages/ui/src/assets/services/mem0.svg
deleted file mode 100644
index d05ad55dd6..0000000000
--- a/packages/ui/src/assets/services/mem0.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/monday.svg b/packages/ui/src/assets/services/monday.svg
deleted file mode 100644
index c2feb2b4c0..0000000000
--- a/packages/ui/src/assets/services/monday.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/neon.svg b/packages/ui/src/assets/services/neon.svg
deleted file mode 100644
index eddf840522..0000000000
--- a/packages/ui/src/assets/services/neon.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/notion.svg b/packages/ui/src/assets/services/notion.svg
deleted file mode 100644
index bf6442f76a..0000000000
--- a/packages/ui/src/assets/services/notion.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/pagerduty.svg b/packages/ui/src/assets/services/pagerduty.svg
deleted file mode 100644
index 477f6bb3df..0000000000
--- a/packages/ui/src/assets/services/pagerduty.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/planetscale.svg b/packages/ui/src/assets/services/planetscale.svg
deleted file mode 100644
index 2f3a95c3c6..0000000000
--- a/packages/ui/src/assets/services/planetscale.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/postman.svg b/packages/ui/src/assets/services/postman.svg
deleted file mode 100644
index 87ed4ac39a..0000000000
--- a/packages/ui/src/assets/services/postman.svg
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/prisma.svg b/packages/ui/src/assets/services/prisma.svg
deleted file mode 100644
index fbf24acc72..0000000000
--- a/packages/ui/src/assets/services/prisma.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/render.svg b/packages/ui/src/assets/services/render.svg
deleted file mode 100644
index cfb5a52e5c..0000000000
--- a/packages/ui/src/assets/services/render.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/packages/ui/src/assets/services/sanity.svg b/packages/ui/src/assets/services/sanity.svg
deleted file mode 100644
index 613194b4c2..0000000000
--- a/packages/ui/src/assets/services/sanity.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/sentry.svg b/packages/ui/src/assets/services/sentry.svg
deleted file mode 100644
index d810538965..0000000000
--- a/packages/ui/src/assets/services/sentry.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/ui/src/assets/services/slack.png b/packages/ui/src/assets/services/slack.png
deleted file mode 100644
index b034210831..0000000000
Binary files a/packages/ui/src/assets/services/slack.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/stripe.png b/packages/ui/src/assets/services/stripe.png
deleted file mode 100644
index 26850e852e..0000000000
Binary files a/packages/ui/src/assets/services/stripe.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/supabase.svg b/packages/ui/src/assets/services/supabase.svg
deleted file mode 100644
index cb6632ff5d..0000000000
--- a/packages/ui/src/assets/services/supabase.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/packages/ui/src/assets/services/svelte.png b/packages/ui/src/assets/services/svelte.png
deleted file mode 100644
index 7008ac16e6..0000000000
Binary files a/packages/ui/src/assets/services/svelte.png and /dev/null differ
diff --git a/packages/ui/src/assets/services/wix.png b/packages/ui/src/assets/services/wix.png
deleted file mode 100644
index ee0a13b0d3..0000000000
Binary files a/packages/ui/src/assets/services/wix.png and /dev/null differ
diff --git a/packages/ui/src/features/mcp-server-manager/useMcpConnect.ts b/packages/ui/src/features/mcp-server-manager/useMcpConnect.ts
index e7717232ae..0a4e171d56 100644
--- a/packages/ui/src/features/mcp-server-manager/useMcpConnect.ts
+++ b/packages/ui/src/features/mcp-server-manager/useMcpConnect.ts
@@ -18,6 +18,8 @@ import { useCallback, useMemo } from "react";
export const mcpKeys = {
servers: ["mcp", "servers"] as const,
installations: ["mcp", "installations"] as const,
+ icon: (domain: string, theme: "light" | "dark") =>
+ ["mcp", "icon", domain, theme] as const,
tools: (installationId: string) =>
["mcp", "installations", installationId, "tools"] as const,
};
diff --git a/packages/ui/src/features/mcp-servers/components/parts/MarketplaceView.tsx b/packages/ui/src/features/mcp-servers/components/parts/MarketplaceView.tsx
index 861754946e..9abd722638 100644
--- a/packages/ui/src/features/mcp-servers/components/parts/MarketplaceView.tsx
+++ b/packages/ui/src/features/mcp-servers/components/parts/MarketplaceView.tsx
@@ -73,8 +73,8 @@ export function MarketplaceView({
Marketplace
- Browse and connect MCP servers that extend your agent with tools,
- data and integrations.
+ Connect MCP servers to make them available to in PostHog on desktop
+ and in the cloud.