From 0db077c142a26eb5fa44ed8a36069d16c105cdbd Mon Sep 17 00:00:00 2001 From: Georgiy Tarasov Date: Thu, 7 May 2026 11:52:53 +0200 Subject: [PATCH 1/5] wip --- .../add-mcp-server-to-clients/defaults.ts | 11 ++- src/ui/tui/__tests__/store.test.ts | 31 +++++++ src/ui/tui/screens/McpScreen.tsx | 84 ++++++++++++++----- src/ui/tui/store.ts | 6 ++ 4 files changed, 107 insertions(+), 25 deletions(-) diff --git a/src/steps/add-mcp-server-to-clients/defaults.ts b/src/steps/add-mcp-server-to-clients/defaults.ts index 7f274854..8c204ae6 100644 --- a/src/steps/add-mcp-server-to-clients/defaults.ts +++ b/src/steps/add-mcp-server-to-clients/defaults.ts @@ -213,22 +213,21 @@ export const ALL_FEATURE_VALUES = Object.values(AVAILABLE_FEATURES) .flat() .map((feature) => feature.value); +export const isAllFeaturesSelected = (features: string[]): boolean => + features.length === ALL_FEATURE_VALUES.length && + ALL_FEATURE_VALUES.every((feature) => features.includes(feature)); + export const buildMCPUrl = (selectedFeatures?: string[], local?: boolean) => { const host = local ? 'http://localhost:8787' : 'https://mcp.posthog.com'; const baseUrl = `${host}/mcp`; - const isAllFeaturesSelected = - selectedFeatures && - selectedFeatures.length === ALL_FEATURE_VALUES.length && - ALL_FEATURE_VALUES.every((feature) => selectedFeatures.includes(feature)); - const params: string[] = []; // Add features param if not all features selected if ( selectedFeatures && selectedFeatures.length > 0 && - !isAllFeaturesSelected + !isAllFeaturesSelected(selectedFeatures) ) { params.push(`features=${selectedFeatures.join(',')}`); } diff --git a/src/ui/tui/__tests__/store.test.ts b/src/ui/tui/__tests__/store.test.ts index 9e262131..580c7f0a 100644 --- a/src/ui/tui/__tests__/store.test.ts +++ b/src/ui/tui/__tests__/store.test.ts @@ -333,6 +333,37 @@ describe('WizardStore', () => { }), ); }); + + it('setMcpComplete includes mcp_features_selected when installed', () => { + const store = createStore(); + store.setMcpComplete(McpOutcome.Installed, ['Cursor'], 'all'); + expect(wizardCaptureMock).toHaveBeenCalledWith( + 'mcp complete', + expect.objectContaining({ mcp_features_selected: 'all' }), + ); + + wizardCaptureMock.mockClear(); + store.setMcpComplete( + McpOutcome.Installed, + ['Cursor'], + ['dashboards', 'insights'], + ); + expect(wizardCaptureMock).toHaveBeenCalledWith( + 'mcp complete', + expect.objectContaining({ + mcp_features_selected: ['dashboards', 'insights'], + }), + ); + }); + + it('setMcpComplete omits mcp_features_selected when not installed', () => { + const store = createStore(); + store.setMcpComplete(McpOutcome.Skipped, [], 'all'); + const call = wizardCaptureMock.mock.calls.find( + ([event]) => event === 'mcp complete', + ); + expect(call?.[1]).not.toHaveProperty('mcp_features_selected'); + }); }); // ── Screen resolution (derived state) ──────────────────────────── diff --git a/src/ui/tui/screens/McpScreen.tsx b/src/ui/tui/screens/McpScreen.tsx index f536cf55..f27726ad 100644 --- a/src/ui/tui/screens/McpScreen.tsx +++ b/src/ui/tui/screens/McpScreen.tsx @@ -25,6 +25,7 @@ import type { McpInstaller, McpClientInfo } from '../services/mcp-installer.js'; import { AVAILABLE_FEATURES, ALL_FEATURE_VALUES, + isAllFeaturesSelected, } from '../../../steps/add-mcp-server-to-clients/defaults.js'; export type McpMode = 'install' | 'remove'; @@ -49,10 +50,14 @@ const markDone = ( store: WizardStore, outcome: McpOutcome, clients: string[] = [], + featuresSelected?: 'all' | string[], ) => { - store.setMcpComplete(outcome, clients); + store.setMcpComplete(outcome, clients, featuresSelected); }; +const reportFeatures = (features: string[]): 'all' | string[] => + isAllFeaturesSelected(features) ? 'all' : features; + export const McpScreen = ({ store, installer, @@ -74,6 +79,7 @@ export const McpScreen = ({ const [selectedClientNames, setSelectedClientNames] = useState([]); const [resultClients, setResultClients] = useState([]); const [pluginClients, setPluginClients] = useState([]); + const [installMode, setInstallMode] = useState<'all' | 'custom'>('custom'); useEffect(() => { void (async () => { @@ -93,10 +99,14 @@ export const McpScreen = ({ })(); }, [installer]); // eslint-disable-line - const proceedToFeatureSelectOrInstall = (clientNames: string[]) => { + const proceedAfterClientPick = ( + clientNames: string[], + chosenMode: 'all' | 'custom', + ) => { setSelectedClientNames(clientNames); - // Skip feature picker if CLI already specified features - if (store.session.mcpFeatures) { + if (chosenMode === 'all') { + void doInstall(clientNames, [...ALL_FEATURE_VALUES]); + } else if (store.session.mcpFeatures) { void doInstall(clientNames, store.session.mcpFeatures); } else { setPhase(Phase.FeatureSelect); @@ -107,7 +117,20 @@ export const McpScreen = ({ if (isRemove) { void doRemove(); } else if (clients.length === 1) { - proceedToFeatureSelectOrInstall(clients.map((c) => c.name)); + proceedAfterClientPick([clients[0]!.name], 'custom'); + } else { + setPhase(Phase.Pick); + } + }; + + const handleTriStateChoice = (choice: 'all' | 'custom' | 'skip') => { + if (choice === 'skip') { + handleSkip(); + return; + } + setInstallMode(choice); + if (clients.length === 1) { + proceedAfterClientPick([clients[0]!.name], choice); } else { setPhase(Phase.Pick); } @@ -140,7 +163,8 @@ export const McpScreen = ({ setPhase(Phase.Done); const outcome = mcpResult.length > 0 ? McpOutcome.Installed : McpOutcome.Failed; - setTimeout(() => markDone(store, outcome, mcpResult), 2000); + const featuresReport = reportFeatures(features ?? [...ALL_FEATURE_VALUES]); + setTimeout(() => markDone(store, outcome, mcpResult, featuresReport), 2000); }; const doRemove = async () => { @@ -182,17 +206,37 @@ export const McpScreen = ({ Detected: {clients.map((c) => c.name).join(', ')} - c.supportsPlugin) ? ' and plugin' : '' - }?`} - confirmLabel={isRemove ? 'Remove' : 'Install'} - cancelLabel="No thanks" - onConfirm={handleConfirm} - onCancel={handleSkip} - /> + {!isRemove && !store.session.mcpFeatures ? ( + c.supportsPlugin) ? ' and plugin' : '' + }?`} + options={[ + { + label: 'Install with all features (recommended)', + value: 'all', + }, + { label: 'Customize features', value: 'custom' }, + { label: 'No thanks', value: 'skip' }, + ]} + mode="single" + onSelect={(choice) => + handleTriStateChoice(choice as 'all' | 'custom' | 'skip') + } + /> + ) : ( + c.supportsPlugin) ? ' and plugin' : '' + }?`} + confirmLabel={isRemove ? 'Remove' : 'Install'} + cancelLabel="No thanks" + onConfirm={handleConfirm} + onCancel={handleSkip} + /> + )} )} @@ -207,7 +251,7 @@ export const McpScreen = ({ mode="multi" onSelect={(selected) => { const names = Array.isArray(selected) ? selected : [selected]; - proceedToFeatureSelectOrInstall(names); + proceedAfterClientPick(names, installMode); }} /> )} @@ -235,7 +279,9 @@ export const McpScreen = ({ <> {'\u2714'} MCP server - {!isRemove && pluginClients.length > 0 ? ' and plugin' : ''}{' '} + {!isRemove && pluginClients.length > 0 + ? ' and plugin' + : ''}{' '} {isRemove ? 'removed from' : 'installed for'}: {resultClients.map((name, i) => ( diff --git a/src/ui/tui/store.ts b/src/ui/tui/store.ts index cf5b0550..d3d589c7 100644 --- a/src/ui/tui/store.ts +++ b/src/ui/tui/store.ts @@ -408,13 +408,19 @@ export class WizardStore { setMcpComplete( outcome: McpOutcome = McpOutcome.Skipped, installedClients: string[] = [], + featuresSelected?: 'all' | string[], ): void { this.$session.setKey('mcpComplete', true); this.$session.setKey('mcpOutcome', outcome); this.$session.setKey('mcpInstalledClients', installedClients); + const featuresPayload = + outcome === McpOutcome.Installed && featuresSelected !== undefined + ? { mcp_features_selected: featuresSelected } + : {}; analytics.wizardCapture('mcp complete', { mcp_outcome: outcome, mcp_installed_clients: installedClients, + ...featuresPayload, ...sessionProperties(this.session), }); this.emitChange(); From 3f4b4a1419dc4ea5806db4dfe001df663b114f1c Mon Sep 17 00:00:00 2001 From: Georgiy Tarasov Date: Thu, 7 May 2026 13:08:03 +0200 Subject: [PATCH 2/5] wip --- .../clients/__tests__/codex.test.ts | 62 +++++++-- .../clients/claude-code.ts | 58 +++++++- .../clients/codex.ts | 46 ++++++- src/ui/tui/screens/McpScreen.tsx | 130 +++++++++++++----- 4 files changed, 242 insertions(+), 54 deletions(-) diff --git a/src/steps/add-mcp-server-to-clients/clients/__tests__/codex.test.ts b/src/steps/add-mcp-server-to-clients/clients/__tests__/codex.test.ts index 543bfc2f..d0e02336 100644 --- a/src/steps/add-mcp-server-to-clients/clients/__tests__/codex.test.ts +++ b/src/steps/add-mcp-server-to-clients/clients/__tests__/codex.test.ts @@ -77,26 +77,72 @@ describe('CodexMCPClient', () => { }); describe('isServerInstalled', () => { - it('delegates to isPluginInstalled', async () => { - readFileSyncMock.mockReturnValue( - '[marketplaces.posthog]\nsource_type = "git"\n', - ); + it('returns true when posthog appears in mcp list output', async () => { + spawnSyncMock.mockReturnValue({ + status: 0, + stdout: 'posthog\n', + stderr: '', + }); const client = new CodexMCPClient(); await expect(client.isServerInstalled()).resolves.toBe(true); }); + + it('returns false when posthog is absent from mcp list output', async () => { + spawnSyncMock.mockReturnValue({ + status: 0, + stdout: 'other-server\n', + stderr: '', + }); + const client = new CodexMCPClient(); + await expect(client.isServerInstalled()).resolves.toBe(false); + }); + + it('returns false when mcp list exits non-zero', async () => { + spawnSyncMock.mockReturnValue({ status: 1, stdout: '', stderr: 'err' }); + const client = new CodexMCPClient(); + await expect(client.isServerInstalled()).resolves.toBe(false); + }); }); describe('addServer', () => { - it('delegates to installPlugin — returns success when plugin installs', async () => { + it('runs codex mcp add with the resolved URL and returns success on exit 0', async () => { spawnSyncMock.mockReturnValue({ status: 0, stderr: '' }); const client = new CodexMCPClient(); - await expect(client.addServer()).resolves.toEqual({ success: true }); + await expect(client.addServer('phx_test')).resolves.toEqual({ + success: true, + }); + const call = spawnSyncMock.mock.calls[0]!; + expect(call[0]).toBe(CODEX_PATH); + expect(call[1]).toEqual([ + 'mcp', + 'add', + 'posthog', + '--url', + 'https://mcp.posthog.com/mcp', + '--bearer-token-env-var', + 'POSTHOG_AUTH_HEADER', + ]); + expect(call[2].env.POSTHOG_AUTH_HEADER).toBe('Bearer phx_test'); + }); + + it('treats "already" stderr as success', async () => { + spawnSyncMock.mockReturnValue({ + status: 1, + stderr: "Server 'posthog' already exists", + }); + const client = new CodexMCPClient(); + await expect(client.addServer('phx_test')).resolves.toEqual({ + success: true, + }); }); - it('delegates to installPlugin — returns failure when plugin fails', async () => { + it('returns failure and captures exception on unexpected error', async () => { spawnSyncMock.mockReturnValue({ status: 1, stderr: 'network timeout' }); const client = new CodexMCPClient(); - await expect(client.addServer()).resolves.toEqual({ success: false }); + await expect(client.addServer('phx_test')).resolves.toEqual({ + success: false, + }); + expect(analytics.captureException).toHaveBeenCalled(); }); }); diff --git a/src/steps/add-mcp-server-to-clients/clients/claude-code.ts b/src/steps/add-mcp-server-to-clients/clients/claude-code.ts index 19c88b92..11244d4b 100644 --- a/src/steps/add-mcp-server-to-clients/clients/claude-code.ts +++ b/src/steps/add-mcp-server-to-clients/clients/claude-code.ts @@ -1,5 +1,5 @@ import { DefaultMCPClient } from '../MCPClient'; -import { DefaultMCPClientConfig } from '../defaults'; +import { DefaultMCPClientConfig, buildMCPUrl } from '../defaults'; import { PluginCapable, PluginInstallResult } from '../plugin-client'; import { z } from 'zod'; import { execSync } from 'child_process'; @@ -85,17 +85,63 @@ export class ClaudeCodeMCPClient } } - isServerInstalled(): Promise { - return this.isPluginInstalled(); + async isServerInstalled(local?: boolean): Promise { + const binary = this.findClaudeBinary(); + if (!binary) return false; + const serverName = local ? 'posthog-local' : 'posthog'; + try { + const output = execSync(`${binary} mcp list`, { stdio: 'pipe' }) + .toString() + .toLowerCase(); + return output.includes(serverName); + } catch { + return false; + } } getConfigPath(): Promise { throw new Error('Not implemented'); } - async addServer(): Promise<{ success: boolean }> { - const result = await this.installPlugin(); - return { success: result.success }; + async addServer( + apiKey?: string, + selectedFeatures?: string[], + local?: boolean, + ): Promise<{ success: boolean }> { + const binary = this.findClaudeBinary(); + if (!binary) return { success: false }; + + const serverName = local ? 'posthog-local' : 'posthog'; + const url = buildMCPUrl(selectedFeatures, local); + const args = [ + 'mcp', + 'add', + '--transport', + 'http', + '--scope', + 'user', + serverName, + url, + ]; + if (apiKey) { + args.push('--header', `Authorization: Bearer ${apiKey}`); + } + + try { + execSync(`${binary} ${args.map((a) => JSON.stringify(a)).join(' ')}`, { + stdio: 'pipe', + }); + return { success: true }; + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + if (msg.includes('already exists')) { + return { success: true }; + } + analytics.captureException( + new Error(`Claude Code MCP add failed: ${msg}`), + ); + return { success: false }; + } } removeServer(local?: boolean): Promise<{ success: boolean }> { diff --git a/src/steps/add-mcp-server-to-clients/clients/codex.ts b/src/steps/add-mcp-server-to-clients/clients/codex.ts index 937e75b1..9e34cfb2 100644 --- a/src/steps/add-mcp-server-to-clients/clients/codex.ts +++ b/src/steps/add-mcp-server-to-clients/clients/codex.ts @@ -5,7 +5,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { DefaultMCPClient } from '../MCPClient'; -import { DefaultMCPClientConfig } from '../defaults'; +import { DefaultMCPClientConfig, buildMCPUrl } from '../defaults'; import { PluginCapable, PluginInstallResult } from '../plugin-client'; import { analytics } from '../../../utils/analytics'; @@ -46,13 +46,47 @@ export class CodexMCPClient extends DefaultMCPClient implements PluginCapable { throw new Error('Not implemented'); } - isServerInstalled(): Promise { - return this.isPluginInstalled(); + isServerInstalled(local?: boolean): Promise { + const binary = this.findCodexBinary(); + if (!binary) return Promise.resolve(false); + const serverName = local ? 'posthog-local' : 'posthog'; + const result = spawnSync(binary, ['mcp', 'list'], { encoding: 'utf-8' }); + if (result.status !== 0) return Promise.resolve(false); + return Promise.resolve( + (result.stdout ?? '').toLowerCase().includes(serverName), + ); } - async addServer(): Promise<{ success: boolean }> { - const result = await this.installPlugin(); - return { success: result.success }; + async addServer( + apiKey?: string, + selectedFeatures?: string[], + local?: boolean, + ): Promise<{ success: boolean }> { + const binary = this.findCodexBinary(); + if (!binary) return { success: false }; + + const serverName = local ? 'posthog-local' : 'posthog'; + const url = buildMCPUrl(selectedFeatures, local); + const args = ['mcp', 'add', serverName, '--url', url]; + const env = { ...process.env }; + if (apiKey) { + const tokenVar = 'POSTHOG_AUTH_HEADER'; + env[tokenVar] = `Bearer ${apiKey}`; + args.push('--bearer-token-env-var', tokenVar); + } + + const result = spawnSync(binary, args, { encoding: 'utf-8', env }); + if (result.status !== 0) { + const stderr = result.stderr ?? ''; + if (stderr.toLowerCase().includes('already')) { + return { success: true }; + } + analytics.captureException( + new Error(`Codex MCP add failed: ${stderr}`), + ); + return { success: false }; + } + return { success: true }; } removeServer(): Promise<{ success: boolean }> { diff --git a/src/ui/tui/screens/McpScreen.tsx b/src/ui/tui/screens/McpScreen.tsx index f27726ad..bf805ec4 100644 --- a/src/ui/tui/screens/McpScreen.tsx +++ b/src/ui/tui/screens/McpScreen.tsx @@ -144,27 +144,60 @@ export const McpScreen = ({ setPhase(Phase.Working); let mcpResult: string[] = []; let pluginResult: string[] = []; - try { - mcpResult = await installer.install( - names, - features, - store.session.apiKey, - ); - } catch { - // mcpResult stays [] - } - try { - pluginResult = await installer.installPlugins(names); - } catch { - // best-effort — plugin failure does not affect MCP outcome + + const pluginCapableSet = new Set( + clients.filter((c) => c.supportsPlugin).map((c) => c.name), + ); + const pluginCapableNames = names.filter((n) => pluginCapableSet.has(n)); + const directNames = names.filter((n) => !pluginCapableSet.has(n)); + + if (installMode === 'all') { + // Plugin-capable clients get the plugin (which bundles MCP). + // Non-plugin-capable clients get a direct MCP config write. + try { + mcpResult = await installer.install( + directNames, + features, + store.session.apiKey, + ); + } catch { + // mcpResult stays [] + } + try { + pluginResult = await installer.installPlugins(pluginCapableNames); + } catch { + // best-effort + } + } else { + // 'custom' — MCP-only for every selected client. Plugin install is + // skipped so the user's feature selection is actually respected. + try { + mcpResult = await installer.install( + names, + features, + store.session.apiKey, + ); + } catch { + // mcpResult stays [] + } } + setResultClients(mcpResult); setPluginClients(pluginResult); setPhase(Phase.Done); - const outcome = - mcpResult.length > 0 ? McpOutcome.Installed : McpOutcome.Failed; + const succeeded = mcpResult.length + pluginResult.length > 0; + const outcome = succeeded ? McpOutcome.Installed : McpOutcome.Failed; const featuresReport = reportFeatures(features ?? [...ALL_FEATURE_VALUES]); - setTimeout(() => markDone(store, outcome, mcpResult, featuresReport), 2000); + setTimeout( + () => + markDone( + store, + outcome, + [...mcpResult, ...pluginResult], + featuresReport, + ), + 2000, + ); }; const doRemove = async () => { @@ -213,10 +246,15 @@ export const McpScreen = ({ }?`} options={[ { - label: 'Install with all features (recommended)', + label: 'Install with all features', value: 'all', + hint: 'recommended', + }, + { + label: 'Customize features', + value: 'custom', + hint: 'MCP only', }, - { label: 'Customize features', value: 'custom' }, { label: 'No thanks', value: 'skip' }, ]} mode="single" @@ -243,10 +281,20 @@ export const McpScreen = ({ {phase === Phase.Pick && ( ({ label: c.name, value: c.name, + hint: + installMode === 'all' + ? c.supportsPlugin + ? 'plugin' + : 'MCP' + : undefined, }))} mode="multi" onSelect={(selected) => { @@ -260,7 +308,7 @@ export const McpScreen = ({ { void doInstall(selectedClientNames, features); }} @@ -275,21 +323,35 @@ export const McpScreen = ({ {phase === Phase.Done && ( - {resultClients.length > 0 ? ( + {resultClients.length + pluginClients.length > 0 ? ( <> - - {'\u2714'} MCP server - {!isRemove && pluginClients.length > 0 - ? ' and plugin' - : ''}{' '} - {isRemove ? 'removed from' : 'installed for'}: - - {resultClients.map((name, i) => ( - - {' '} - {'\u2022'} {name} - - ))} + {pluginClients.length > 0 && ( + <> + + {'\u2714'} Plugin installed for: + + {pluginClients.map((name, i) => ( + + {' '} + {'\u2022'} {name} + + ))} + + )} + {resultClients.length > 0 && ( + <> + + {'\u2714'} MCP server{' '} + {isRemove ? 'removed from' : 'installed for'}: + + {resultClients.map((name, i) => ( + + {' '} + {'\u2022'} {name} + + ))} + + )} ) : ( From 215d5c9b6daef66694f69a828874e15a46bbfcf1 Mon Sep 17 00:00:00 2001 From: Georgiy Tarasov Date: Thu, 7 May 2026 13:08:08 +0200 Subject: [PATCH 3/5] scopes --- .../add-mcp-server-to-clients/defaults.ts | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/steps/add-mcp-server-to-clients/defaults.ts b/src/steps/add-mcp-server-to-clients/defaults.ts index 8c204ae6..28458f30 100644 --- a/src/steps/add-mcp-server-to-clients/defaults.ts +++ b/src/steps/add-mcp-server-to-clients/defaults.ts @@ -61,6 +61,21 @@ export const AVAILABLE_FEATURES = { label: 'SQL', hint: 'SQL query execution', }, + { + value: 'web_analytics', + label: 'Web Analytics', + hint: 'Web analytics queries and digests', + }, + { + value: 'customer_analytics', + label: 'Usage metrics', + hint: 'Customer usage metric tracking', + }, + { + value: 'signals', + label: 'Signals', + hint: 'Signal reports and source configs', + }, ], 'AI Engineering': [ { @@ -68,11 +83,6 @@ export const AVAILABLE_FEATURES = { label: 'LLM Analytics', hint: 'LLM usage and cost tracking', }, - { - value: 'prompts', - label: 'Prompts', - hint: 'LLM prompt management', - }, ], 'Development Tools': [ { @@ -100,6 +110,16 @@ export const AVAILABLE_FEATURES = { label: 'Cohorts', hint: 'Cohort management', }, + { + value: 'sdk_doctor', + label: 'SDK Doctor', + hint: 'SDK health diagnostics', + }, + { + value: 'tracing', + label: 'APM Tracing', + hint: 'Distributed trace and span queries', + }, ], 'Data Management': [ { @@ -132,6 +152,11 @@ export const AVAILABLE_FEATURES = { label: 'Data Schema', hint: 'Data schema exploration', }, + { + value: 'batch_exports', + label: 'Batch Exports', + hint: 'Scheduled data exports', + }, ], 'CDP & Automation': [ { From 870ddf28a6a2fe1e1277d225e0f177071e3662ff Mon Sep 17 00:00:00 2001 From: Georgiy Tarasov Date: Thu, 7 May 2026 13:17:30 +0200 Subject: [PATCH 4/5] fix --- src/steps/add-mcp-server-to-clients/clients/codex.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/steps/add-mcp-server-to-clients/clients/codex.ts b/src/steps/add-mcp-server-to-clients/clients/codex.ts index 9e34cfb2..690c70c7 100644 --- a/src/steps/add-mcp-server-to-clients/clients/codex.ts +++ b/src/steps/add-mcp-server-to-clients/clients/codex.ts @@ -81,9 +81,7 @@ export class CodexMCPClient extends DefaultMCPClient implements PluginCapable { if (stderr.toLowerCase().includes('already')) { return { success: true }; } - analytics.captureException( - new Error(`Codex MCP add failed: ${stderr}`), - ); + analytics.captureException(new Error(`Codex MCP add failed: ${stderr}`)); return { success: false }; } return { success: true }; From 4221e9d40caadbeb8cf34bdf09c7df0a10fbd9b8 Mon Sep 17 00:00:00 2001 From: Georgiy Tarasov Date: Thu, 7 May 2026 13:23:42 +0200 Subject: [PATCH 5/5] fix --- .../clients/claude-code.ts | 18 +++++++++--------- .../add-mcp-server-to-clients/clients/codex.ts | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/steps/add-mcp-server-to-clients/clients/claude-code.ts b/src/steps/add-mcp-server-to-clients/clients/claude-code.ts index 11244d4b..8a3d7c64 100644 --- a/src/steps/add-mcp-server-to-clients/clients/claude-code.ts +++ b/src/steps/add-mcp-server-to-clients/clients/claude-code.ts @@ -85,17 +85,17 @@ export class ClaudeCodeMCPClient } } - async isServerInstalled(local?: boolean): Promise { + isServerInstalled(local?: boolean): Promise { const binary = this.findClaudeBinary(); - if (!binary) return false; + if (!binary) return Promise.resolve(false); const serverName = local ? 'posthog-local' : 'posthog'; try { const output = execSync(`${binary} mcp list`, { stdio: 'pipe' }) .toString() .toLowerCase(); - return output.includes(serverName); + return Promise.resolve(output.includes(serverName)); } catch { - return false; + return Promise.resolve(false); } } @@ -103,13 +103,13 @@ export class ClaudeCodeMCPClient throw new Error('Not implemented'); } - async addServer( + addServer( apiKey?: string, selectedFeatures?: string[], local?: boolean, ): Promise<{ success: boolean }> { const binary = this.findClaudeBinary(); - if (!binary) return { success: false }; + if (!binary) return Promise.resolve({ success: false }); const serverName = local ? 'posthog-local' : 'posthog'; const url = buildMCPUrl(selectedFeatures, local); @@ -131,16 +131,16 @@ export class ClaudeCodeMCPClient execSync(`${binary} ${args.map((a) => JSON.stringify(a)).join(' ')}`, { stdio: 'pipe', }); - return { success: true }; + return Promise.resolve({ success: true }); } catch (error) { const msg = error instanceof Error ? error.message : String(error); if (msg.includes('already exists')) { - return { success: true }; + return Promise.resolve({ success: true }); } analytics.captureException( new Error(`Claude Code MCP add failed: ${msg}`), ); - return { success: false }; + return Promise.resolve({ success: false }); } } diff --git a/src/steps/add-mcp-server-to-clients/clients/codex.ts b/src/steps/add-mcp-server-to-clients/clients/codex.ts index 690c70c7..cf5f1285 100644 --- a/src/steps/add-mcp-server-to-clients/clients/codex.ts +++ b/src/steps/add-mcp-server-to-clients/clients/codex.ts @@ -57,13 +57,13 @@ export class CodexMCPClient extends DefaultMCPClient implements PluginCapable { ); } - async addServer( + addServer( apiKey?: string, selectedFeatures?: string[], local?: boolean, ): Promise<{ success: boolean }> { const binary = this.findCodexBinary(); - if (!binary) return { success: false }; + if (!binary) return Promise.resolve({ success: false }); const serverName = local ? 'posthog-local' : 'posthog'; const url = buildMCPUrl(selectedFeatures, local); @@ -79,12 +79,12 @@ export class CodexMCPClient extends DefaultMCPClient implements PluginCapable { if (result.status !== 0) { const stderr = result.stderr ?? ''; if (stderr.toLowerCase().includes('already')) { - return { success: true }; + return Promise.resolve({ success: true }); } analytics.captureException(new Error(`Codex MCP add failed: ${stderr}`)); - return { success: false }; + return Promise.resolve({ success: false }); } - return { success: true }; + return Promise.resolve({ success: true }); } removeServer(): Promise<{ success: boolean }> {