Skip to content

Commit b9421d6

Browse files
committed
Reduces MCP refreshes during init
1 parent 1f6831c commit b9421d6

File tree

1 file changed

+52
-25
lines changed

1 file changed

+52
-25
lines changed

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

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,66 @@ import { Disposable, env, EventEmitter, lm, McpStdioServerDefinition } from 'vsc
33
import type { Container } from '../../../../container';
44
import type { StorageChangeEvent } from '../../../../system/-webview/storage';
55
import { getHostAppName } from '../../../../system/-webview/vscode';
6-
import { log } from '../../../../system/decorators/log';
6+
import { debug, log } from '../../../../system/decorators/log';
7+
import type { Deferrable } from '../../../../system/function/debounce';
8+
import { debounce } from '../../../../system/function/debounce';
79
import { Logger } from '../../../../system/logger';
810
import { runCLICommand, toMcpInstallProvider } from '../cli/utils';
911

1012
const CLIProxyMCPConfigOutputs = {
1113
checkingForUpdates: /checking for updates.../i,
1214
} as const;
1315

16+
type McpConfiguration = { name: string; type: string; command: string; args: string[]; version?: string };
17+
1418
export class GkMcpProvider implements McpServerDefinitionProvider, Disposable {
19+
private _cliVersion: string | undefined;
1520
private readonly _disposable: Disposable;
1621
private readonly _onDidChangeMcpServerDefinitions = new EventEmitter<void>();
22+
private _fireChangeDebounced: Deferrable<() => void> | undefined = undefined;
23+
private _getMcpConfigurationFromCLIPromise: Promise<McpConfiguration | undefined> | undefined;
24+
1725
get onDidChangeMcpServerDefinitions(): Event<void> {
1826
return this._onDidChangeMcpServerDefinitions.event;
1927
}
2028

2129
constructor(private readonly container: Container) {
2230
this._disposable = Disposable.from(
23-
this.container.storage.onDidChange(e => this.checkStorage(e)),
31+
this.container.storage.onDidChange(e => this.onStorageChanged(e)),
2432
lm.registerMcpServerDefinitionProvider('gitlens.gkMcpProvider', this),
2533
);
2634
}
2735

28-
private checkStorage(e?: StorageChangeEvent): void {
29-
if (e != null && !(e.keys as string[]).includes('gk:cli:install')) return;
30-
this._onDidChangeMcpServerDefinitions.fire();
36+
dispose(): void {
37+
this._disposable.dispose();
38+
this._onDidChangeMcpServerDefinitions.dispose();
3139
}
3240

41+
private onStorageChanged(e: StorageChangeEvent): void {
42+
if (e.workspace || !e.keys.includes('gk:cli:install')) return;
43+
44+
// Only refresh if installation is completed
45+
const cliInstall = this.container.storage.get('gk:cli:install');
46+
if (cliInstall?.status !== 'completed') {
47+
return;
48+
}
49+
50+
// Invalidate configuration promise if the version changed
51+
if (this._cliVersion !== cliInstall?.version) {
52+
this._getMcpConfigurationFromCLIPromise = undefined;
53+
}
54+
this._cliVersion = cliInstall?.version;
55+
56+
this._fireChangeDebounced ??= debounce(() => {
57+
this._onDidChangeMcpServerDefinitions.fire();
58+
}, 500);
59+
this._fireChangeDebounced();
60+
}
61+
62+
@log({ exit: true })
3363
async provideMcpServerDefinitions(): Promise<McpServerDefinition[]> {
3464
const config = await this.getMcpConfigurationFromCLI();
35-
if (config == null) {
36-
return [];
37-
}
65+
if (config == null) return [];
3866

3967
const serverDefinition = new McpStdioServerDefinition(
4068
`${config.name} (bundled with GitLens)`,
@@ -48,9 +76,13 @@ export class GkMcpProvider implements McpServerDefinitionProvider, Disposable {
4876
}
4977

5078
@log()
51-
private async getMcpConfigurationFromCLI(): Promise<
52-
{ name: string; type: string; command: string; args: string[]; version?: string } | undefined
53-
> {
79+
private getMcpConfigurationFromCLI(): Promise<McpConfiguration | undefined> {
80+
this._getMcpConfigurationFromCLIPromise ??= this.getMcpConfigurationFromCLICore();
81+
return this._getMcpConfigurationFromCLIPromise;
82+
}
83+
84+
@debug()
85+
private async getMcpConfigurationFromCLICore(): Promise<McpConfiguration | undefined> {
5486
const cliInstall = this.container.storage.get('gk:cli:install');
5587
const cliPath = this.container.storage.get('gk:cli:path');
5688

@@ -69,32 +101,32 @@ export class GkMcpProvider implements McpServerDefinitionProvider, Disposable {
69101
output = output.replace(CLIProxyMCPConfigOutputs.checkingForUpdates, '').trim();
70102

71103
try {
72-
const configuration = JSON.parse(output) as { name: string; type: string; command: string; args: string[] };
104+
const config: McpConfiguration = JSON.parse(output);
73105

74-
this.notifyRegistrationCompleted(cliInstall.version);
106+
this.onRegistrationCompleted(cliInstall.version);
75107

76108
return {
77-
name: configuration.name,
78-
type: configuration.type,
79-
command: configuration.command,
80-
args: configuration.args,
109+
name: config.name,
110+
type: config.type,
111+
command: config.command,
112+
args: config.args,
81113
version: cliInstall.version,
82114
};
83115
} catch (ex) {
84116
Logger.error(`Error getting MCP configuration: ${ex}`);
85-
this.notifyRegistrationFailed('Error getting MCP configuration', undefined, cliInstall.version);
117+
this.onRegistrationFailed('Error getting MCP configuration', undefined, cliInstall.version);
86118
}
87119

88120
return undefined;
89121
}
90122

91-
private notifyRegistrationCompleted(_cliVersion?: string | undefined) {
123+
private onRegistrationCompleted(_cliVersion?: string | undefined) {
92124
if (!this.container.telemetry.enabled) return;
93125

94126
this.container.telemetry.setGlobalAttribute('gk.mcp.registrationCompleted', true);
95127
}
96128

97-
private notifyRegistrationFailed(reason: string, message?: string | undefined, cliVersion?: string | undefined) {
129+
private onRegistrationFailed(reason: string, message?: string | undefined, cliVersion?: string | undefined) {
98130
if (!this.container.telemetry.enabled) return;
99131

100132
this.container.telemetry.sendEvent('mcp/registration/failed', {
@@ -104,9 +136,4 @@ export class GkMcpProvider implements McpServerDefinitionProvider, Disposable {
104136
'cli.version': cliVersion,
105137
});
106138
}
107-
108-
dispose(): void {
109-
this._disposable.dispose();
110-
this._onDidChangeMcpServerDefinitions.dispose();
111-
}
112139
}

0 commit comments

Comments
 (0)