4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
6
import { CancellationToken } from '../../../../base/common/cancellation.js' ;
7
- import { Emitter } from '../../../../base/common/event.js' ;
7
+ import { Emitter , Event } from '../../../../base/common/event.js' ;
8
8
import { Disposable } from '../../../../base/common/lifecycle.js' ;
9
9
import { Schemas } from '../../../../base/common/network.js' ;
10
10
import { basename } from '../../../../base/common/resources.js' ;
@@ -32,12 +32,17 @@ import { IWorkbenchEnvironmentService } from '../../../services/environment/comm
32
32
import { IWorkbenchLocalMcpServer , IWorkbenchMcpManagementService , LocalMcpServerScope } from '../../../services/mcp/common/mcpWorkbenchManagementService.js' ;
33
33
import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js' ;
34
34
import { mcpConfigurationSection } from '../common/mcpConfiguration.js' ;
35
- import { HasInstalledMcpServersContext , IMcpConfigPath , IMcpWorkbenchService , IWorkbenchMcpServer , McpCollectionSortOrder , McpServersGalleryEnabledContext } from '../common/mcpTypes.js' ;
35
+ import { HasInstalledMcpServersContext , IMcpConfigPath , IMcpWorkbenchService , IWorkbenchMcpServer , McpCollectionSortOrder , McpServerInstallState , McpServersGalleryEnabledContext } from '../common/mcpTypes.js' ;
36
36
import { McpServerEditorInput } from './mcpServerEditorInput.js' ;
37
37
38
+ interface IMcpServerStateProvider < T > {
39
+ ( mcpWorkbenchServer : McpWorkbenchServer ) : T ;
40
+ }
41
+
38
42
class McpWorkbenchServer implements IWorkbenchMcpServer {
39
43
40
44
constructor (
45
+ private installStateProvider : IMcpServerStateProvider < McpServerInstallState > ,
41
46
public local : IWorkbenchLocalMcpServer | undefined ,
42
47
public gallery : IGalleryMcpServer | undefined ,
43
48
public readonly installable : IInstallableMcpServer | undefined ,
@@ -66,6 +71,10 @@ class McpWorkbenchServer implements IWorkbenchMcpServer {
66
71
return this . gallery ?. icon ?? this . local ?. icon ;
67
72
}
68
73
74
+ get installState ( ) : McpServerInstallState {
75
+ return this . installStateProvider ( this ) ;
76
+ }
77
+
69
78
get codicon ( ) : string | undefined {
70
79
return this . gallery ?. codicon ?? this . local ?. codicon ;
71
80
}
@@ -133,8 +142,11 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
133
142
134
143
_serviceBrand : undefined ;
135
144
145
+ private installing : McpWorkbenchServer [ ] = [ ] ;
146
+ private uninstalling : McpWorkbenchServer [ ] = [ ] ;
147
+
136
148
private _local : McpWorkbenchServer [ ] = [ ] ;
137
- get local ( ) : readonly McpWorkbenchServer [ ] { return this . _local ; }
149
+ get local ( ) : readonly McpWorkbenchServer [ ] { return [ ... this . _local ] ; }
138
150
139
151
private readonly _onChange = this . _register ( new Emitter < IWorkbenchMcpServer | undefined > ( ) ) ;
140
152
readonly onChange = this . _onChange . event ;
@@ -191,7 +203,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
191
203
if ( server ) {
192
204
server . local = local ;
193
205
} else {
194
- server = this . instantiationService . createInstance ( McpWorkbenchServer , local , gallery , undefined ) ;
206
+ server = this . instantiationService . createInstance ( McpWorkbenchServer , e => this . getInstallState ( e ) , local , gallery , undefined ) ;
195
207
this . _local . push ( server ) ;
196
208
}
197
209
this . _onChange . fire ( server ) ;
@@ -209,7 +221,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
209
221
this . _local [ serverIndex ] . local = result . local ;
210
222
server = this . _local [ serverIndex ] ;
211
223
} else {
212
- server = this . instantiationService . createInstance ( McpWorkbenchServer , result . local , result . source , undefined ) ;
224
+ server = this . instantiationService . createInstance ( McpWorkbenchServer , e => this . getInstallState ( e ) , result . local , result . source , undefined ) ;
213
225
this . _local . push ( server ) ;
214
226
}
215
227
this . _onChange . fire ( server ) ;
@@ -270,28 +282,32 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
270
282
return [ ] ;
271
283
}
272
284
const result = await this . mcpGalleryService . query ( options , token ) ;
273
- return result . map ( gallery => this . fromGallery ( gallery ) ?? this . instantiationService . createInstance ( McpWorkbenchServer , undefined , gallery , undefined ) ) ;
285
+ return result . map ( gallery => this . fromGallery ( gallery ) ?? this . instantiationService . createInstance ( McpWorkbenchServer , e => this . getInstallState ( e ) , undefined , gallery , undefined ) ) ;
274
286
}
275
287
276
288
async queryLocal ( ) : Promise < IWorkbenchMcpServer [ ] > {
277
289
const installed = await this . mcpManagementService . getInstalled ( ) ;
278
290
this . _local = installed . map ( i => {
279
- const local = this . _local . find ( server => server . name === i . name ) ?? this . instantiationService . createInstance ( McpWorkbenchServer , undefined , undefined , undefined ) ;
291
+ const local = this . _local . find ( server => server . name === i . name ) ?? this . instantiationService . createInstance ( McpWorkbenchServer , e => this . getInstallState ( e ) , undefined , undefined , undefined ) ;
280
292
local . local = i ;
281
293
return local ;
282
294
} ) ;
283
- return this . _local ;
295
+ return [ ... this . local ] ;
284
296
}
285
297
286
298
async install ( server : IWorkbenchMcpServer ) : Promise < IWorkbenchMcpServer > {
299
+ if ( ! ( server instanceof McpWorkbenchServer ) ) {
300
+ throw new Error ( 'Invalid server instance' ) ;
301
+ }
302
+
287
303
if ( server . installable ) {
288
- const local = await this . mcpManagementService . install ( server . installable ) ;
289
- return this . onDidInstallMcpServer ( local ) ;
304
+ const installable = server . installable ;
305
+ return this . doInstall ( server , ( ) => this . mcpManagementService . install ( installable ) ) ;
290
306
}
291
307
292
308
if ( server . gallery ) {
293
- const local = await this . mcpManagementService . installFromGallery ( server . gallery , { packageType : server . gallery . packageTypes [ 0 ] } ) ;
294
- return this . onDidInstallMcpServer ( local ) ;
309
+ const gallery = server . gallery ;
310
+ return this . doInstall ( server , ( ) => this . mcpManagementService . installFromGallery ( gallery , { packageType : gallery . packageTypes [ 0 ] } ) ) ;
295
311
}
296
312
297
313
throw new Error ( 'No installable server found' ) ;
@@ -304,6 +320,26 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
304
320
await this . mcpManagementService . uninstall ( server . local ) ;
305
321
}
306
322
323
+ private async doInstall ( server : McpWorkbenchServer , installTask : ( ) => Promise < IWorkbenchLocalMcpServer > ) : Promise < IWorkbenchMcpServer > {
324
+ this . installing . push ( server ) ;
325
+ this . _onChange . fire ( server ) ;
326
+ await installTask ( ) . finally ( ( ) => this . installing = this . installing . filter ( s => s !== server ) ) ;
327
+ return this . waitAndGetInstalledMcpServer ( server ) ;
328
+ }
329
+
330
+ private async waitAndGetInstalledMcpServer ( server : McpWorkbenchServer ) : Promise < IWorkbenchMcpServer > {
331
+ let installed = this . local . find ( local => local . name === server . name ) ;
332
+ if ( ! installed ) {
333
+ await Event . toPromise ( Event . filter ( this . onChange , e => ! ! e && this . local . some ( local => local . name === server . name ) ) ) ;
334
+ }
335
+ installed = this . local . find ( local => local . name === server . name ) ;
336
+ if ( ! installed ) {
337
+ // This should not happen
338
+ throw new Error ( 'Extension should have been installed' ) ;
339
+ }
340
+ return installed ;
341
+ }
342
+
307
343
getMcpConfigPath ( localMcpServer : IWorkbenchLocalMcpServer ) : IMcpConfigPath | undefined ;
308
344
getMcpConfigPath ( mcpResource : URI ) : Promise < IMcpConfigPath | undefined > ;
309
345
getMcpConfigPath ( arg : URI | IWorkbenchLocalMcpServer ) : Promise < IMcpConfigPath | undefined > | IMcpConfigPath | undefined {
@@ -422,12 +458,12 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
422
458
if ( ! galleryServer ) {
423
459
throw new Error ( `MCP server '${ name } ' not found in gallery` ) ;
424
460
}
425
- this . open ( this . instantiationService . createInstance ( McpWorkbenchServer , undefined , galleryServer , undefined ) ) ;
461
+ this . open ( this . instantiationService . createInstance ( McpWorkbenchServer , e => this . getInstallState ( e ) , undefined , galleryServer , undefined ) ) ;
426
462
} else {
427
463
if ( config . type === undefined ) {
428
464
( < Mutable < IMcpServerConfiguration > > config ) . type = ( < IMcpStdioServerConfiguration > parsed ) . command ? McpServerType . LOCAL : McpServerType . REMOTE ;
429
465
}
430
- this . open ( this . instantiationService . createInstance ( McpWorkbenchServer , undefined , undefined , { name, config, inputs } ) ) ;
466
+ this . open ( this . instantiationService . createInstance ( McpWorkbenchServer , e => this . getInstallState ( e ) , undefined , undefined , { name, config, inputs } ) ) ;
431
467
}
432
468
} catch ( e ) {
433
469
// ignore
@@ -439,6 +475,17 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
439
475
await this . editorService . openEditor ( this . instantiationService . createInstance ( McpServerEditorInput , extension ) , options , ACTIVE_GROUP ) ;
440
476
}
441
477
478
+ private getInstallState ( extension : McpWorkbenchServer ) : McpServerInstallState {
479
+ if ( this . installing . some ( i => i . name === extension . name ) ) {
480
+ return McpServerInstallState . Installing ;
481
+ }
482
+ if ( this . uninstalling . some ( e => e . name === extension . name ) ) {
483
+ return McpServerInstallState . Uninstalling ;
484
+ }
485
+ const local = this . local . find ( e => e === extension ) ;
486
+ return local ? McpServerInstallState . Installed : McpServerInstallState . Uninstalled ;
487
+ }
488
+
442
489
}
443
490
444
491
export class MCPContextsInitialisation extends Disposable implements IWorkbenchContribution {
0 commit comments