Skip to content

Commit 14f3289

Browse files
committed
Improves MCP setup error handling & user messaging
- Enhances error messages to be more user-friendly and actionable - Adds logging with entry/exit tracking for better debugging
1 parent e5d3241 commit 14f3289

File tree

3 files changed

+83
-63
lines changed

3 files changed

+83
-63
lines changed

src/env/node/gk/cli/integration.ts

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { arch } from 'process';
22
import type { ConfigurationChangeEvent } from 'vscode';
33
import { version as codeVersion, Disposable, env, ProgressLocation, Uri, window, workspace } from 'vscode';
4-
import { urls } from '../../../../constants';
54
import type { Source, Sources } from '../../../../constants.telemetry';
65
import type { Container } from '../../../../container';
76
import type { SubscriptionChangeEvent } from '../../../../plus/gk/subscriptionService';
@@ -11,18 +10,19 @@ import {
1110
} from '../../../../plus/gk/utils/-webview/mcp.utils';
1211
import { registerCommand } from '../../../../system/-webview/command';
1312
import { configuration } from '../../../../system/-webview/configuration';
14-
import { getHostAppName } from '../../../../system/-webview/vscode';
13+
import { getHostAppName, isHostVSCode } from '../../../../system/-webview/vscode';
1514
import { openUrl } from '../../../../system/-webview/vscode/uris';
1615
import { gate } from '../../../../system/decorators/gate';
16+
import { debug, log } from '../../../../system/decorators/log';
1717
import { Logger } from '../../../../system/logger';
18-
import { getLogScope } from '../../../../system/logger.scope';
18+
import { getLogScope, setLogScopeExit } from '../../../../system/logger.scope';
1919
import { compare } from '../../../../system/version';
2020
import { run } from '../../git/shell';
2121
import { getPlatform, isWeb } from '../../platform';
2222
import { CliCommandHandlers } from './commands';
2323
import type { IpcServer } from './ipcServer';
2424
import { createIpcServer } from './ipcServer';
25-
import { runCLICommand, toMcpInstallProvider } from './utils';
25+
import { runCLICommand, showManualMcpSetupPrompt, toMcpInstallProvider } from './utils';
2626

2727
const enum CLIInstallErrorReason {
2828
UnsupportedPlatform,
@@ -107,17 +107,21 @@ export class GkCliIntegrationProvider implements Disposable {
107107
}
108108

109109
@gate()
110+
@log({ exit: true })
110111
private async setupMCP(source?: Sources): Promise<void> {
112+
const scope = getLogScope();
113+
111114
await this.container.storage.store('mcp:banner:dismissed', true);
115+
112116
const commandSource = source ?? 'commandPalette';
113-
const scope = getLogScope();
114117
let cliVersion: string | undefined;
115118
if (this.container.telemetry.enabled) {
116119
this.container.telemetry.sendEvent('mcp/setup/started', { source: commandSource });
117120
}
118121

119122
if (isWeb) {
120-
void window.showErrorMessage('GitKraken MCP setup is not supported on this platform.');
123+
setLogScopeExit(scope, 'GitKraken MCP setup is not supported on the web');
124+
void window.showWarningMessage('GitKraken MCP setup is not supported on the web.');
121125
if (this.container.telemetry.enabled) {
122126
this.container.telemetry.sendEvent('mcp/setup/failed', {
123127
reason: 'web environment unsupported',
@@ -127,24 +131,11 @@ export class GkCliIntegrationProvider implements Disposable {
127131
return;
128132
}
129133

130-
const appName = toMcpInstallProvider(await getHostAppName());
131-
if (appName == null) {
132-
void window.showInformationMessage(`Failed to setup the GitKraken MCP: Could not determine app name`);
133-
if (this.container.telemetry.enabled) {
134-
this.container.telemetry.sendEvent('mcp/setup/failed', {
135-
reason: 'no app name',
136-
source: commandSource,
137-
});
138-
}
139-
return;
140-
}
134+
const hostAppName = await getHostAppName();
141135

142136
try {
143-
if (
144-
(appName === 'vscode' || appName === 'vscode-insiders' || appName === 'vscode-exploration') &&
145-
compare(codeVersion, '1.102') < 0
146-
) {
147-
void window.showInformationMessage('Use of this command requires VS Code 1.102 or later.');
137+
if (isHostVSCode(hostAppName) && compare(codeVersion, '1.102') < 0) {
138+
void window.showWarningMessage('GitKraken MCP setup requires VS Code 1.102 or later.');
148139
if (this.container.telemetry.enabled) {
149140
this.container.telemetry.sendEvent('mcp/setup/failed', {
150141
reason: 'unsupported vscode version',
@@ -193,17 +184,20 @@ export class GkCliIntegrationProvider implements Disposable {
193184
case CLIInstallErrorReason.ProxyExtract:
194185
case CLIInstallErrorReason.CoreDirectory:
195186
case CLIInstallErrorReason.CoreInstall:
196-
void window.showErrorMessage('Failed to install the GitKraken MCP server locally.');
187+
void window.showErrorMessage(
188+
'Unable to locally install the GitKraken MCP server. Please try again.',
189+
);
197190
failureReason = 'local installation failed';
198191
break;
199192
default:
200193
void window.showErrorMessage(
201-
`Failed to setup the GitKraken MCP: ${ex instanceof Error ? ex.message : 'Unknown error.'}`,
194+
`Unable to setup the GitKraken MCP: ${ex instanceof Error ? ex.message : 'Unknown error.'}`,
202195
);
203196
break;
204197
}
205198
}
206199

200+
Logger.error(ex, scope, `Error during MCP installation: ${ex}`);
207201
if (this.container.telemetry.enabled) {
208202
this.container.telemetry.sendEvent('mcp/setup/failed', {
209203
reason: failureReason,
@@ -215,7 +209,7 @@ export class GkCliIntegrationProvider implements Disposable {
215209
}
216210

217211
if (cliPath == null) {
218-
void window.showErrorMessage('Failed to setup the GitKraken MCP: Unknown error.');
212+
setLogScopeExit(scope, undefined, 'GitKraken MCP setup failed; installation failed');
219213
if (this.container.telemetry.enabled) {
220214
this.container.telemetry.sendEvent('mcp/setup/failed', {
221215
reason: 'unknown error',
@@ -224,35 +218,39 @@ export class GkCliIntegrationProvider implements Disposable {
224218
'cli.version': cliVersion,
225219
});
226220
}
221+
222+
void window.showErrorMessage(
223+
'Unable to setup the GitKraken MCP: installation failed. Please try again.',
224+
);
227225
return;
228226
}
229227

230228
// If MCP extension registration is supported, don't proceed with manual setup
231229
if (supportsMcpExtensionRegistration()) {
230+
setLogScopeExit(scope, 'supports provider-based MCP registration');
232231
return;
233232
}
234233

235-
if (appName !== 'cursor' && appName !== 'vscode' && appName !== 'vscode-insiders') {
236-
const confirmation = await window.showInformationMessage(
237-
`GitKraken MCP installed successfully. Click 'Finish' to add it to your MCP server list and complete the setup.`,
238-
{ modal: true },
239-
{ title: 'Finish' },
240-
{ title: 'Cancel', isCloseAffordance: true },
241-
);
242-
if (confirmation == null || confirmation.title === 'Cancel') {
243-
if (this.container.telemetry.enabled) {
244-
this.container.telemetry.sendEvent('mcp/setup/failed', {
245-
reason: 'user cancelled',
246-
source: commandSource,
247-
'cli.version': cliVersion,
248-
});
249-
}
250-
return;
234+
const mcpInstallAppName = toMcpInstallProvider(hostAppName);
235+
if (mcpInstallAppName == null) {
236+
setLogScopeExit(scope, undefined, `GitKraken MCP setup failed; unsupported host: ${hostAppName}`);
237+
if (this.container.telemetry.enabled) {
238+
this.container.telemetry.sendEvent('mcp/setup/failed', {
239+
reason: 'no app name',
240+
source: commandSource,
241+
'cli.version': cliVersion,
242+
});
251243
}
244+
245+
void showManualMcpSetupPrompt(
246+
'Automatic setup of the GitKraken MCP is not currently supported in this IDE. You may be able to configure it by adding the GitKraken MCP to your configuration manually.',
247+
);
248+
249+
return;
252250
}
253251

254252
let output = await runCLICommand(
255-
['mcp', 'install', appName, '--source=gitlens', `--scheme=${env.uriScheme}`],
253+
['mcp', 'install', mcpInstallAppName, '--source=gitlens', `--scheme=${env.uriScheme}`],
256254
{
257255
cwd: cliPath,
258256
},
@@ -269,34 +267,28 @@ export class GkCliIntegrationProvider implements Disposable {
269267
}
270268
return;
271269
} else if (CLIProxyMCPInstallOutputs.notASupportedClient.test(output)) {
270+
setLogScopeExit(scope, undefined, `GitKraken MCP setup failed; unsupported host: ${hostAppName}`);
272271
if (this.container.telemetry.enabled) {
273272
this.container.telemetry.sendEvent('mcp/setup/failed', {
274273
reason: 'unsupported app',
275-
'error.message': `Not a supported MCP client: ${appName}`,
274+
'error.message': `Not a supported MCP client: ${hostAppName}`,
276275
source: commandSource,
277276
'cli.version': cliVersion,
278277
});
279278
}
280279

281-
const learnMore = { title: 'View Setup Instructions' };
282-
const cancel = { title: 'Cancel', isCloseAffordance: true };
283-
const result = await window.showInformationMessage(
284-
"This application doesn't support automatic MCP setup. Please add the GitKraken MCP to your configuration manually.",
285-
{ modal: true },
286-
learnMore,
287-
cancel,
280+
void showManualMcpSetupPrompt(
281+
'Automatic setup of the GitKraken MCP is not currently supported in this IDE. You should be able to configure it by adding the GitKraken MCP to your configuration manually.',
288282
);
289-
if (result === learnMore) {
290-
void openUrl(urls.helpCenterMCP);
291-
}
292-
293283
return;
294284
}
295285

296286
// Check if the output is a valid url. If so, run it
297287
try {
298288
new URL(output);
299289
} catch {
290+
setLogScopeExit(scope, undefined, `GitKraken MCP setup failed; unexpected output from mcp install`);
291+
Logger.error(undefined, scope, `Unexpected output from mcp install command: ${output}`);
300292
if (this.container.telemetry.enabled) {
301293
this.container.telemetry.sendEvent('mcp/setup/failed', {
302294
reason: 'unexpected output from mcp install command',
@@ -305,8 +297,10 @@ export class GkCliIntegrationProvider implements Disposable {
305297
'cli.version': cliVersion,
306298
});
307299
}
308-
Logger.error(`Unexpected output from mcp install command: ${output}`, scope);
309-
void window.showErrorMessage(`Failed to setup the GitKraken MCP: unknown error`);
300+
301+
void showManualMcpSetupPrompt(
302+
'Unable to setup the GitKraken MCP. If this issue persists, please try adding the GitKraken MCP to your configuration manually.',
303+
);
310304
return;
311305
}
312306

@@ -319,7 +313,7 @@ export class GkCliIntegrationProvider implements Disposable {
319313
});
320314
}
321315
} catch (ex) {
322-
Logger.error(`Error during MCP installation: ${ex}`, scope);
316+
Logger.error(ex, scope, `Error during MCP installation: ${ex}`);
323317
if (this.container.telemetry.enabled) {
324318
this.container.telemetry.sendEvent('mcp/setup/failed', {
325319
reason: 'unknown error',
@@ -330,16 +324,19 @@ export class GkCliIntegrationProvider implements Disposable {
330324
}
331325

332326
void window.showErrorMessage(
333-
`Failed to setup the GitKraken MCP: ${ex instanceof Error ? ex.message : 'Unknown error'}`,
327+
`Unable to setup the GitKraken MCP: ${ex instanceof Error ? ex.message : 'Unknown error'}`,
334328
);
335329
}
336330
}
337331

338332
@gate()
333+
@log({ exit: true })
339334
private async installCLI(
340335
autoInstall?: boolean,
341336
source?: Sources,
342337
): Promise<{ cliVersion?: string; cliPath?: string; status: 'completed' | 'unsupported' | 'attempted' }> {
338+
const scope = getLogScope();
339+
343340
const cliInstall = this.container.storage.get('gk:cli:install');
344341
let cliInstallAttempts = cliInstall?.attempts ?? 0;
345342
let cliInstallStatus = cliInstall?.status ?? 'attempted';
@@ -427,6 +424,7 @@ export class GkCliIntegrationProvider implements Disposable {
427424
attempts: cliInstallAttempts,
428425
})
429426
.catch();
427+
430428
throw new CLIInstallError(CLIInstallErrorReason.UnsupportedPlatform, undefined, platform);
431429
}
432430
}
@@ -624,6 +622,8 @@ export class GkCliIntegrationProvider implements Disposable {
624622
}
625623
} catch (ex) {
626624
Logger.error(
625+
ex,
626+
scope,
627627
`Failed to ${autoInstall ? 'auto-install' : 'install'} CLI: ${ex instanceof Error ? ex.message : 'Unknown error during installation'}`,
628628
);
629629
if (this.container.telemetry.enabled) {
@@ -645,7 +645,10 @@ export class GkCliIntegrationProvider implements Disposable {
645645
return { cliVersion: cliVersion, cliPath: cliPath, status: cliInstallStatus };
646646
}
647647

648+
@debug()
648649
private async authCLI(): Promise<void> {
650+
const scope = getLogScope();
651+
649652
const cliInstall = this.container.storage.get('gk:cli:install');
650653
const cliPath = this.container.storage.get('gk:cli:path');
651654
if (cliInstall?.status !== 'completed' || cliPath == null) {
@@ -660,7 +663,7 @@ export class GkCliIntegrationProvider implements Disposable {
660663
try {
661664
await runCLICommand(['auth', 'login', '-t', currentSessionToken]);
662665
} catch (ex) {
663-
Logger.error(`Failed to auth CLI: ${ex instanceof Error ? ex.message : String(ex)}`);
666+
Logger.error(ex, scope);
664667
}
665668
}
666669

src/env/node/gk/cli/utils.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
import { window } from 'vscode';
2+
import { urls } from '../../../../constants';
13
import { Container } from '../../../../container';
4+
import { openUrl } from '../../../../system/-webview/vscode/uris';
25
import { run } from '../../git/shell';
36
import { getPlatform } from '../../platform';
47

5-
export function toMcpInstallProvider(appHostName: string | undefined): string | undefined {
8+
export function toMcpInstallProvider<T extends string | undefined>(appHostName: T): T {
69
switch (appHostName) {
710
case 'code':
8-
return 'vscode';
11+
return 'vscode' as T;
912
case 'code-insiders':
10-
return 'vscode-insiders';
13+
return 'vscode-insiders' as T;
1114
case 'code-exploration':
12-
return 'vscode-exploration';
15+
return 'vscode-exploration' as T;
1316
default:
1417
return appHostName;
1518
}
@@ -25,3 +28,13 @@ export async function runCLICommand(args: string[], options?: { cwd?: string }):
2528

2629
return run(platform === 'windows' ? 'gk.exe' : './gk', args, 'utf8', { cwd: cwd });
2730
}
31+
32+
export async function showManualMcpSetupPrompt(message: string): Promise<void> {
33+
const learnMore = { title: 'View Setup Instructions' };
34+
const cancel = { title: 'Cancel', isCloseAffordance: true };
35+
const result = await window.showErrorMessage(message, { modal: true }, learnMore, cancel);
36+
37+
if (result === learnMore) {
38+
void openUrl(urls.helpCenterMCP);
39+
}
40+
}

src/system/-webview/vscode.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ export function isLightTheme(theme: ColorTheme): boolean {
111111
return theme.kind === ColorThemeKind.Light || theme.kind === ColorThemeKind.HighContrastLight;
112112
}
113113

114+
export function isHostVSCode(hostAppName: string | undefined): boolean {
115+
return hostAppName === 'code' || hostAppName === 'code-insiders' || hostAppName === 'code-exploration';
116+
}
117+
114118
export async function openWalkthrough(
115119
extensionId: string,
116120
walkthroughId: string,

0 commit comments

Comments
 (0)