@@ -3,38 +3,66 @@ import { Disposable, env, EventEmitter, lm, McpStdioServerDefinition } from 'vsc
33import type { Container } from '../../../../container' ;
44import type { StorageChangeEvent } from '../../../../system/-webview/storage' ;
55import { 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' ;
79import { Logger } from '../../../../system/logger' ;
810import { runCLICommand , toMcpInstallProvider } from '../cli/utils' ;
911
1012const CLIProxyMCPConfigOutputs = {
1113 checkingForUpdates : / c h e c k i n g f o r u p d a t e s .../ i,
1214} as const ;
1315
16+ type McpConfiguration = { name : string ; type : string ; command : string ; args : string [ ] ; version ?: string } ;
17+
1418export 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