11import { homedir } from 'os' ;
22import { arch } from 'process' ;
33import type { ConfigurationChangeEvent } from 'vscode' ;
4- import { Disposable , env , ProgressLocation , Uri , window , workspace } from 'vscode' ;
4+ import { version as codeVersion , Disposable , env , ProgressLocation , Uri , window , workspace } from 'vscode' ;
55import type { Container } from '../../../../container' ;
66import type { SubscriptionChangeEvent } from '../../../../plus/gk/subscriptionService' ;
77import { registerCommand } from '../../../../system/-webview/command' ;
88import { configuration } from '../../../../system/-webview/configuration' ;
99import { getContext } from '../../../../system/-webview/context' ;
10+ import { openUrl } from '../../../../system/-webview/vscode/uris' ;
1011import { Logger } from '../../../../system/logger' ;
12+ import { compare } from '../../../../system/version' ;
1113import { run } from '../../git/shell' ;
1214import { getPlatform , isWeb } from '../../platform' ;
1315import { CliCommandHandlers } from './commands' ;
@@ -33,9 +35,12 @@ export class GkCliIntegrationProvider implements Disposable {
3335 ) ;
3436
3537 this . onConfigurationChanged ( ) ;
36- setTimeout ( ( ) => {
37- void this . installMCPIfNeeded ( true ) ;
38- } , 10000 + Math . floor ( Math . random ( ) * 20000 ) ) ;
38+ setTimeout (
39+ ( ) => {
40+ void this . installMCPIfNeeded ( true ) ;
41+ } ,
42+ 10000 + Math . floor ( Math . random ( ) * 20000 ) ,
43+ ) ;
3944 }
4045
4146 dispose ( ) : void {
@@ -74,6 +79,16 @@ export class GkCliIntegrationProvider implements Disposable {
7479
7580 private async installMCPIfNeeded ( silent ?: boolean ) : Promise < void > {
7681 try {
82+ if (
83+ ( env . appName === 'Visual Studio Code' || env . appName === 'Visual Studio Code - Insiders' ) &&
84+ compare ( codeVersion , '1.102' ) < 0
85+ ) {
86+ if ( ! silent ) {
87+ void window . showInformationMessage ( 'Use of this command requires VS Code 1.102 or later.' ) ;
88+ }
89+ return ;
90+ }
91+
7792 if ( silent && this . container . storage . get ( 'ai:mcp:attemptInstall' ) ) {
7893 return ;
7994 }
@@ -89,7 +104,7 @@ export class GkCliIntegrationProvider implements Disposable {
89104 return ;
90105 }
91106
92- if ( getContext ( 'gitlens:gk:organization:ai:enabled' , true ) !== true ) {
107+ if ( getContext ( 'gitlens:gk:organization:ai:enabled' , true ) !== true ) {
93108 const message = 'Cannot install MCP: AI is disabled by your organization' ;
94109 Logger . log ( message ) ;
95110 if ( silent !== true ) {
@@ -140,7 +155,9 @@ export class GkCliIntegrationProvider implements Disposable {
140155 const message = `Skipping MCP installation: unsupported platform ${ platform } ` ;
141156 Logger . log ( message ) ;
142157 if ( silent !== true ) {
143- void window . showErrorMessage ( `Cannot install MCP integration: unsupported platform ${ platform } ` ) ;
158+ void window . showErrorMessage (
159+ `Cannot install MCP integration: unsupported platform ${ platform } ` ,
160+ ) ;
144161 }
145162 return ;
146163 }
@@ -156,7 +173,14 @@ export class GkCliIntegrationProvider implements Disposable {
156173
157174 try {
158175 // Download the MCP proxy installer
159- const proxyUrl = this . container . urls . getGkApiUrl ( 'releases' , 'gkcli-proxy' , 'production' , platformName , architecture , 'active' ) ;
176+ const proxyUrl = this . container . urls . getGkApiUrl (
177+ 'releases' ,
178+ 'gkcli-proxy' ,
179+ 'production' ,
180+ platformName ,
181+ architecture ,
182+ 'active' ,
183+ ) ;
160184
161185 let response = await fetch ( proxyUrl ) ;
162186 if ( ! response . ok ) {
@@ -167,7 +191,8 @@ export class GkCliIntegrationProvider implements Disposable {
167191
168192 let downloadUrl : string | undefined ;
169193 try {
170- const mcpInstallerInfo : { version ?: string ; packages ?: { zip ?: string } } | undefined = await response . json ( ) as any ;
194+ const mcpInstallerInfo : { version ?: string ; packages ?: { zip ?: string } } | undefined =
195+ ( await response . json ( ) ) as any ;
171196 downloadUrl = mcpInstallerInfo ?. packages ?. zip ;
172197 } catch ( ex ) {
173198 const errorMsg = `Failed to parse MCP installer info: ${ ex } ` ;
@@ -211,7 +236,10 @@ export class GkCliIntegrationProvider implements Disposable {
211236 // On Windows, use PowerShell to extract the zip file
212237 await run (
213238 'powershell.exe' ,
214- [ '-Command' , `Expand-Archive -Path "${ mcpInstallerPath . fsPath } " -DestinationPath "${ this . container . context . globalStorageUri . fsPath } "` ] ,
239+ [
240+ '-Command' ,
241+ `Expand-Archive -Path "${ mcpInstallerPath . fsPath } " -DestinationPath "${ this . container . context . globalStorageUri . fsPath } "` ,
242+ ] ,
215243 'utf8' ,
216244 ) ;
217245 } else {
@@ -224,7 +252,10 @@ export class GkCliIntegrationProvider implements Disposable {
224252 }
225253 // The gk file should be in a subfolder named after the installer file name
226254 const extractedFolderName = installerFileName . replace ( / \. z i p $ / , '' ) ;
227- mcpExtractedFolderPath = Uri . joinPath ( this . container . context . globalStorageUri , extractedFolderName ) ;
255+ mcpExtractedFolderPath = Uri . joinPath (
256+ this . container . context . globalStorageUri ,
257+ extractedFolderName ,
258+ ) ;
228259 mcpExtractedPath = Uri . joinPath ( mcpExtractedFolderPath , mcpFileName ) ;
229260
230261 // Check using stat to make sure the newly extracted file exists.
@@ -263,12 +294,19 @@ export class GkCliIntegrationProvider implements Disposable {
263294
264295 // Configure the MCP server in settings.json
265296 try {
266- const installOutput = await run ( platform === 'windows' ? mcpFileName : `./${ mcpFileName } ` , [ 'install' ] , 'utf8' , { cwd : mcpExtractedFolderPath . fsPath } ) ;
297+ const installOutput = await run (
298+ platform === 'windows' ? mcpFileName : `./${ mcpFileName } ` ,
299+ [ 'install' ] ,
300+ 'utf8' ,
301+ { cwd : mcpExtractedFolderPath . fsPath } ,
302+ ) ;
267303 const directory = installOutput . match ( / D i r e c t o r y : ( .* ) / ) ;
304+ let directoryPath ;
268305 if ( directory != null ) {
269306 try {
270- const directoryPath = directory [ 1 ] ;
307+ directoryPath = directory [ 1 ] ;
271308 await this . container . storage . store ( 'gk:cli:installedPath' , directoryPath ) ;
309+ // Add to PATH
272310 if ( platform === 'windows' ) {
273311 await run (
274312 'powershell.exe' ,
@@ -279,32 +317,35 @@ export class GkCliIntegrationProvider implements Disposable {
279317 'utf8' ,
280318 ) ;
281319 } else {
282- // For Unix-like systems, detect and modify the appropriate shell profile
283- const homeDir = homedir ( ) ;
284- // Try to detect which shell profile exists and is in use
285- const possibleProfiles = [
286- { path : `${ homeDir } /.zshrc` , shell : 'zsh' } ,
320+ // For Unix-like systems, detect and modify the appropriate shell profile
321+ const homeDir = homedir ( ) ;
322+ // Try to detect which shell profile exists and is in use
323+ const possibleProfiles = [
324+ { path : `${ homeDir } /.zshrc` , shell : 'zsh' } ,
287325 { path : `${ homeDir } /.zprofile` , shell : 'zsh' } ,
288- { path : `${ homeDir } /.bashrc` , shell : 'bash' } ,
289- { path : `${ homeDir } /.profile` , shell : 'sh' }
290- ] ;
291-
292- // Find the first profile that exists
293- let shellProfile ;
294- for ( const profile of possibleProfiles ) {
295- try {
296- await workspace . fs . stat ( Uri . file ( profile . path ) ) ;
297- shellProfile = profile . path ;
298- break ;
299- } catch {
300- // Profile doesn't exist, try next one
301- }
302- }
326+ { path : `${ homeDir } /.bashrc` , shell : 'bash' } ,
327+ { path : `${ homeDir } /.profile` , shell : 'sh' } ,
328+ ] ;
329+
330+ // Find the first profile that exists
331+ let shellProfile ;
332+ for ( const profile of possibleProfiles ) {
333+ try {
334+ await workspace . fs . stat ( Uri . file ( profile . path ) ) ;
335+ shellProfile = profile . path ;
336+ break ;
337+ } catch {
338+ // Profile doesn't exist, try next one
339+ }
340+ }
303341
304342 if ( shellProfile != null ) {
305343 await run (
306344 'sh' ,
307- [ '-c' , `echo '# Added by GitLens for MCP support' >> ${ shellProfile } && echo 'export PATH="$PATH:${ directoryPath } "' >> ${ shellProfile } ` ] ,
345+ [
346+ '-c' ,
347+ `echo '# Added by GitLens for MCP support' >> ${ shellProfile } && echo 'export PATH="$PATH:${ directoryPath } "' >> ${ shellProfile } ` ,
348+ ] ,
308349 'utf8' ,
309350 ) ;
310351 } else {
@@ -316,12 +357,37 @@ export class GkCliIntegrationProvider implements Disposable {
316357 }
317358 } else {
318359 Logger . warn ( 'MCP Install: Failed to find directory in install output' ) ;
360+ if ( appName === 'vscode' ) {
361+ throw new Error ( 'MCP command path not availavle' ) ;
362+ }
363+ }
364+
365+ if ( appName === 'vscode' ) {
366+ const config = {
367+ name : 'GitKraken' ,
368+ command : Uri . file ( `${ directoryPath } \\${ mcpFileName } ` ) . fsPath ,
369+ args : [ 'mcp' ] ,
370+ type : 'stdio' ,
371+ } ;
372+ const installDeepLinkUrl = `${ isInsiders ? 'vscode-insiders' : 'vscode' } :mcp/install?${ encodeURIComponent ( JSON . stringify ( config ) ) } ` ;
373+ await openUrl ( installDeepLinkUrl ) ;
374+ } else {
375+ await run (
376+ platform === 'windows' ? mcpFileName : `./${ mcpFileName } ` ,
377+ [ 'mcp' , 'install' , appName , ...( isInsiders ? [ '--file-path' , settingsPath ] : [ ] ) ] ,
378+ 'utf8' ,
379+ { cwd : mcpExtractedFolderPath . fsPath } ,
380+ ) ;
319381 }
320382
321- await run ( platform === 'windows' ? mcpFileName : `./${ mcpFileName } ` , [ 'mcp' , 'install' , appName , ...isInsiders ? [ '--file-path' , settingsPath ] : [ ] ] , 'utf8' , { cwd : mcpExtractedFolderPath . fsPath } ) ;
322383 const gkAuth = ( await this . container . subscription . getAuthenticationSession ( ) ) ?. accessToken ;
323384 if ( gkAuth != null ) {
324- await run ( platform === 'windows' ? mcpFileName : `./${ mcpFileName } ` , [ 'auth' , 'login' , '-t' , gkAuth ] , 'utf8' , { cwd : mcpExtractedFolderPath . fsPath } ) ;
385+ await run (
386+ platform === 'windows' ? mcpFileName : `./${ mcpFileName } ` ,
387+ [ 'auth' , 'login' , '-t' , gkAuth ] ,
388+ 'utf8' ,
389+ { cwd : mcpExtractedFolderPath . fsPath } ,
390+ ) ;
325391 }
326392
327393 Logger . log ( 'MCP configuration completed' ) ;
@@ -369,7 +435,7 @@ export class GkCliIntegrationProvider implements Disposable {
369435 } ,
370436 async ( ) => {
371437 await installationTask ( ) ;
372- }
438+ } ,
373439 ) ;
374440 } else {
375441 await installationTask ( ) ;
@@ -383,7 +449,9 @@ export class GkCliIntegrationProvider implements Disposable {
383449
384450 // Show error notification if not silent
385451 if ( silent !== true ) {
386- void window . showErrorMessage ( `Failed to install MCP integration: ${ error instanceof Error ? error . message : String ( error ) } ` ) ;
452+ void window . showErrorMessage (
453+ `Failed to install MCP integration: ${ error instanceof Error ? error . message : String ( error ) } ` ,
454+ ) ;
387455 }
388456 }
389457 }
@@ -392,17 +460,26 @@ export class GkCliIntegrationProvider implements Disposable {
392460 const mcpInstallStatus = this . container . storage . get ( 'ai:mcp:attemptInstall' ) ;
393461 const mcpDirectoryPath = this . container . storage . get ( 'gk:cli:installedPath' ) ;
394462 const platform = getPlatform ( ) ;
395- if ( e . current ?. account ?. id != null && e . current . account . id !== e . previous ?. account ?. id && mcpInstallStatus === 'completed' && mcpDirectoryPath != null ) {
463+ if (
464+ e . current ?. account ?. id != null &&
465+ e . current . account . id !== e . previous ?. account ?. id &&
466+ mcpInstallStatus === 'completed' &&
467+ mcpDirectoryPath != null
468+ ) {
396469 const currentSessionToken = ( await this . container . subscription . getAuthenticationSession ( ) ) ?. accessToken ;
397470 if ( currentSessionToken != null ) {
398471 try {
399- await run ( platform === 'windows' ? 'gk.exe' : './gk' , [ 'auth' , 'login' , '-t' , currentSessionToken ] , 'utf8' , { cwd : mcpDirectoryPath } ) ;
472+ await run (
473+ platform === 'windows' ? 'gk.exe' : './gk' ,
474+ [ 'auth' , 'login' , '-t' , currentSessionToken ] ,
475+ 'utf8' ,
476+ { cwd : mcpDirectoryPath } ,
477+ ) ;
400478 } catch { }
401479 }
402480 }
403481 }
404482
405-
406483 private registerCommands ( ) : Disposable [ ] {
407484 return [ registerCommand ( 'gitlens.ai.mcp.install' , ( ) => this . installMCPIfNeeded ( ) ) ] ;
408485 }
0 commit comments