Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/mcp/common/mcpManagementService.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { RunOnceScheduler } from '../../../base/common/async.js';
7
import { VSBuffer } from '../../../base/common/buffer.js';
8
import { CancellationToken } from '../../../base/common/cancellation.js';
9
import { Emitter, Event } from '../../../base/common/event.js';
10
import { IMarkdownString, MarkdownString } from '../../../base/common/htmlContent.js';
11
import { Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js';
12
import { ResourceMap } from '../../../base/common/map.js';
13
import { equals } from '../../../base/common/objects.js';
14
import { isString } from '../../../base/common/types.js';
15
import { URI } from '../../../base/common/uri.js';
16
import { localize } from '../../../nls.js';
17
import { ConfigurationTarget } from '../../configuration/common/configuration.js';
18
import { IEnvironmentService } from '../../environment/common/environment.js';
19
import { IFileService } from '../../files/common/files.js';
20
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
21
import { ILogService } from '../../log/common/log.js';
22
import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
23
import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
24
import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpGalleryService, IMcpManagementService, IMcpServerInput, IGalleryMcpServerConfiguration, InstallMcpServerEvent, InstallMcpServerResult, RegistryType, UninstallMcpServerEvent, InstallOptions, UninstallOptions, IInstallableMcpServer, IAllowedMcpServersService } from './mcpManagement.js';
25
import { IMcpServerVariable, McpServerVariableType, IMcpServerConfiguration, McpServerType } from './mcpPlatformTypes.js';
26
import { IMcpResourceScannerService, McpResourceTarget } from './mcpResourceScannerService.js';
27
28
export interface ILocalMcpServerInfo {
29
name: string;
30
version?: string;
31
id?: string;
32
displayName?: string;
33
galleryUrl?: string;
34
description?: string;
35
repositoryUrl?: string;
36
publisher?: string;
37
publisherDisplayName?: string;
38
icon?: {
39
dark: string;
40
light: string;
41
};
42
codicon?: string;
43
manifest?: IGalleryMcpServerConfiguration;
44
readmeUrl?: URI;
45
location?: URI;
46
licenseUrl?: string;
47
}
48
49
export abstract class AbstractCommonMcpManagementService extends Disposable {
50
51
_serviceBrand: undefined;
52
53
getMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType): Omit<IInstallableMcpServer, 'name'> {
54
let config: IMcpServerConfiguration;
55
const inputs: IMcpServerVariable[] = [];
56
57
if (packageType === RegistryType.REMOTE && manifest.remotes?.length) {
58
const headers: Record<string, string> = {};
59
for (const input of manifest.remotes[0].headers ?? []) {
60
const variables = input.variables ? this.getVariables(input.variables) : [];
61
let value = input.value;
62
for (const variable of variables) {
63
value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`);
64
}
65
headers[input.name] = value;
66
if (variables.length) {
67
inputs.push(...variables);
68
}
69
}
70
config = {
71
type: McpServerType.REMOTE,
72
url: manifest.remotes[0].url,
73
headers: Object.keys(headers).length ? headers : undefined,
74
};
75
} else {
76
const serverPackage = manifest.packages?.find(p => p.registry_type === packageType) ?? manifest.packages?.[0];
77
if (!serverPackage) {
78
throw new Error(`No server package found`);
79
}
80
81
const args: string[] = [];
82
const env: Record<string, string> = {};
83
84
if (serverPackage.registry_type === RegistryType.DOCKER) {
85
args.push('run');
86
args.push('-i');
87
args.push('--rm');
88
}
89
90
for (const arg of serverPackage.runtime_arguments ?? []) {
91
const variables = arg.variables ? this.getVariables(arg.variables) : [];
92
if (arg.type === 'positional') {
93
let value = arg.value;
94
if (value) {
95
for (const variable of variables) {
96
value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`);
97
}
98
}
99
args.push(value ?? arg.value_hint);
100
} else if (arg.type === 'named') {
101
args.push(arg.name);
102
if (arg.value) {
103
let value = arg.value;
104
for (const variable of variables) {
105
value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`);
106
}
107
args.push(value);
108
}
109
}
110
if (variables.length) {
111
inputs.push(...variables);
112
}
113
}
114
115
for (const input of serverPackage.environment_variables ?? []) {
116
const variables = input.variables ? this.getVariables(input.variables) : [];
117
let value = input.value;
118
for (const variable of variables) {
119
value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`);
120
}
121
env[input.name] = value;
122
if (variables.length) {
123
inputs.push(...variables);
124
}
125
if (serverPackage.registry_type === RegistryType.DOCKER) {
126
args.push('-e');
127
args.push(input.name);
128
}
129
}
130
131
if (serverPackage.registry_type === RegistryType.NODE) {
132
args.push(serverPackage.version ? `${serverPackage.identifier}@${serverPackage.version}` : serverPackage.identifier);
133
}
134
else if (serverPackage.registry_type === RegistryType.PYTHON) {
135
args.push(serverPackage.version ? `${serverPackage.identifier}==${serverPackage.version}` : serverPackage.identifier);
136
}
137
else if (serverPackage.registry_type === RegistryType.DOCKER) {
138
args.push(serverPackage.version ? `${serverPackage.identifier}:${serverPackage.version}` : serverPackage.identifier);
139
}
140
else if (serverPackage.registry_type === RegistryType.NUGET) {
141
args.push(serverPackage.version ? `${serverPackage.identifier}@${serverPackage.version}` : serverPackage.identifier);
142
args.push('--yes'); // installation is confirmed by the UI, so --yes is appropriate here
143
if (serverPackage.package_arguments?.length) {
144
args.push('--');
145
}
146
}
147
148
for (const arg of serverPackage.package_arguments ?? []) {
149
const variables = arg.variables ? this.getVariables(arg.variables) : [];
150
if (arg.type === 'positional') {
151
let value = arg.value;
152
if (value) {
153
for (const variable of variables) {
154
value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`);
155
}
156
}
157
args.push(value ?? arg.value_hint);
158
} else if (arg.type === 'named') {
159
args.push(arg.name);
160
if (arg.value) {
161
let value = arg.value;
162
for (const variable of variables) {
163
value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`);
164
}
165
args.push(value);
166
}
167
}
168
if (variables.length) {
169
inputs.push(...variables);
170
}
171
}
172
173
config = {
174
type: McpServerType.LOCAL,
175
command: this.getCommandName(serverPackage.registry_type),
176
args: args.length ? args : undefined,
177
env: Object.keys(env).length ? env : undefined,
178
};
179
}
180
181
return {
182
config,
183
inputs: inputs.length ? inputs : undefined,
184
};
185
}
186
187
protected getCommandName(packageType: RegistryType): string {
188
switch (packageType) {
189
case RegistryType.NODE: return 'npx';
190
case RegistryType.DOCKER: return 'docker';
191
case RegistryType.PYTHON: return 'uvx';
192
case RegistryType.NUGET: return 'dnx';
193
}
194
return packageType;
195
}
196
197
protected getVariables(variableInputs: Record<string, IMcpServerInput>): IMcpServerVariable[] {
198
const variables: IMcpServerVariable[] = [];
199
for (const [key, value] of Object.entries(variableInputs)) {
200
variables.push({
201
id: key,
202
type: value.choices ? McpServerVariableType.PICK : McpServerVariableType.PROMPT,
203
description: value.description ?? '',
204
password: !!value.is_secret,
205
default: value.default,
206
options: value.choices,
207
});
208
}
209
return variables;
210
}
211
212
}
213
214
export abstract class AbstractMcpResourceManagementService extends AbstractCommonMcpManagementService {
215
216
private initializePromise: Promise<void> | undefined;
217
private readonly reloadConfigurationScheduler: RunOnceScheduler;
218
private local = new Map<string, ILocalMcpServer>();
219
220
protected readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());
221
readonly onInstallMcpServer = this._onInstallMcpServer.event;
222
223
protected readonly _onDidInstallMcpServers = this._register(new Emitter<InstallMcpServerResult[]>());
224
get onDidInstallMcpServers() { return this._onDidInstallMcpServers.event; }
225
226
protected readonly _onDidUpdateMcpServers = this._register(new Emitter<InstallMcpServerResult[]>());
227
get onDidUpdateMcpServers() { return this._onDidUpdateMcpServers.event; }
228
229
protected readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());
230
get onUninstallMcpServer() { return this._onUninstallMcpServer.event; }
231
232
protected _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());
233
get onDidUninstallMcpServer() { return this._onDidUninstallMcpServer.event; }
234
235
constructor(
236
protected readonly mcpResource: URI,
237
protected readonly target: McpResourceTarget,
238
@IMcpGalleryService protected readonly mcpGalleryService: IMcpGalleryService,
239
@IFileService protected readonly fileService: IFileService,
240
@IUriIdentityService protected readonly uriIdentityService: IUriIdentityService,
241
@ILogService protected readonly logService: ILogService,
242
@IMcpResourceScannerService protected readonly mcpResourceScannerService: IMcpResourceScannerService,
243
) {
244
super();
245
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.updateLocal(), 50));
246
}
247
248
private initialize(): Promise<void> {
249
if (!this.initializePromise) {
250
this.initializePromise = (async () => {
251
try {
252
this.local = await this.populateLocalServers();
253
} finally {
254
this.startWatching();
255
}
256
})();
257
}
258
return this.initializePromise;
259
}
260
261
private async populateLocalServers(): Promise<Map<string, ILocalMcpServer>> {
262
this.logService.trace('AbstractMcpResourceManagementService#populateLocalServers', this.mcpResource.toString());
263
const local = new Map<string, ILocalMcpServer>();
264
try {
265
const scannedMcpServers = await this.mcpResourceScannerService.scanMcpServers(this.mcpResource, this.target);
266
if (scannedMcpServers.servers) {
267
await Promise.allSettled(Object.entries(scannedMcpServers.servers).map(async ([name, scannedServer]) => {
268
const server = await this.scanLocalServer(name, scannedServer);
269
local.set(name, server);
270
}));
271
}
272
} catch (error) {
273
this.logService.debug('Could not read user MCP servers:', error);
274
throw error;
275
}
276
return local;
277
}
278
279
private startWatching(): void {
280
this._register(this.fileService.watch(this.mcpResource));
281
this._register(this.fileService.onDidFilesChange(e => {
282
if (e.affects(this.mcpResource)) {
283
this.reloadConfigurationScheduler.schedule();
284
}
285
}));
286
}
287
288
protected async updateLocal(): Promise<void> {
289
try {
290
const current = await this.populateLocalServers();
291
292
const added: ILocalMcpServer[] = [];
293
const updated: ILocalMcpServer[] = [];
294
const removed = [...this.local.keys()].filter(name => !current.has(name));
295
296
for (const server of removed) {
297
this.local.delete(server);
298
}
299
300
for (const [name, server] of current) {
301
const previous = this.local.get(name);
302
if (previous) {
303
if (!equals(previous, server)) {
304
updated.push(server);
305
this.local.set(name, server);
306
}
307
} else {
308
added.push(server);
309
this.local.set(name, server);
310
}
311
}
312
313
for (const server of removed) {
314
this.local.delete(server);
315
this._onDidUninstallMcpServer.fire({ name: server, mcpResource: this.mcpResource });
316
}
317
318
if (updated.length) {
319
this._onDidUpdateMcpServers.fire(updated.map(server => ({ name: server.name, local: server, mcpResource: this.mcpResource })));
320
}
321
322
if (added.length) {
323
this._onDidInstallMcpServers.fire(added.map(server => ({ name: server.name, local: server, mcpResource: this.mcpResource })));
324
}
325
326
} catch (error) {
327
this.logService.error('Failed to load installed MCP servers:', error);
328
}
329
}
330
331
async getInstalled(): Promise<ILocalMcpServer[]> {
332
await this.initialize();
333
return Array.from(this.local.values());
334
}
335
336
protected async scanLocalServer(name: string, config: IMcpServerConfiguration): Promise<ILocalMcpServer> {
337
let mcpServerInfo = await this.getLocalServerInfo(name, config);
338
if (!mcpServerInfo) {
339
mcpServerInfo = { name, version: config.version, galleryUrl: isString(config.gallery) ? config.gallery : undefined };
340
}
341
342
return {
343
name,
344
config,
345
mcpResource: this.mcpResource,
346
version: mcpServerInfo.version,
347
location: mcpServerInfo.location,
348
displayName: mcpServerInfo.displayName,
349
description: mcpServerInfo.description,
350
publisher: mcpServerInfo.publisher,
351
publisherDisplayName: mcpServerInfo.publisherDisplayName,
352
galleryUrl: mcpServerInfo.galleryUrl,
353
repositoryUrl: mcpServerInfo.repositoryUrl,
354
readmeUrl: mcpServerInfo.readmeUrl,
355
icon: mcpServerInfo.icon,
356
codicon: mcpServerInfo.codicon,
357
manifest: mcpServerInfo.manifest,
358
source: config.gallery ? 'gallery' : 'local'
359
};
360
}
361
362
async install(server: IInstallableMcpServer, options?: Omit<InstallOptions, 'mcpResource'>): Promise<ILocalMcpServer> {
363
this.logService.trace('MCP Management Service: install', server.name);
364
365
this._onInstallMcpServer.fire({ name: server.name, mcpResource: this.mcpResource });
366
try {
367
await this.mcpResourceScannerService.addMcpServers([server], this.mcpResource, this.target);
368
await this.updateLocal();
369
const local = this.local.get(server.name);
370
if (!local) {
371
throw new Error(`Failed to install MCP server: ${server.name}`);
372
}
373
return local;
374
} catch (e) {
375
this._onDidInstallMcpServers.fire([{ name: server.name, error: e, mcpResource: this.mcpResource }]);
376
throw e;
377
}
378
}
379
380
async uninstall(server: ILocalMcpServer, options?: Omit<UninstallOptions, 'mcpResource'>): Promise<void> {
381
this.logService.trace('MCP Management Service: uninstall', server.name);
382
this._onUninstallMcpServer.fire({ name: server.name, mcpResource: this.mcpResource });
383
384
try {
385
const currentServers = await this.mcpResourceScannerService.scanMcpServers(this.mcpResource, this.target);
386
if (!currentServers.servers) {
387
return;
388
}
389
await this.mcpResourceScannerService.removeMcpServers([server.name], this.mcpResource, this.target);
390
if (server.location) {
391
await this.fileService.del(URI.revive(server.location), { recursive: true });
392
}
393
await this.updateLocal();
394
} catch (e) {
395
this._onDidUninstallMcpServer.fire({ name: server.name, error: e, mcpResource: this.mcpResource });
396
throw e;
397
}
398
}
399
400
abstract installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer>;
401
abstract updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation: URI): Promise<ILocalMcpServer>;
402
protected abstract getLocalServerInfo(name: string, mcpServerConfig: IMcpServerConfiguration): Promise<ILocalMcpServerInfo | undefined>;
403
protected abstract installFromUri(uri: URI, options?: Omit<InstallOptions, 'mcpResource'>): Promise<ILocalMcpServer>;
404
}
405
406
export class McpUserResourceManagementService extends AbstractMcpResourceManagementService {
407
408
protected readonly mcpLocation: URI;
409
410
constructor(
411
mcpResource: URI,
412
@IMcpGalleryService mcpGalleryService: IMcpGalleryService,
413
@IFileService fileService: IFileService,
414
@IUriIdentityService uriIdentityService: IUriIdentityService,
415
@ILogService logService: ILogService,
416
@IMcpResourceScannerService mcpResourceScannerService: IMcpResourceScannerService,
417
@IEnvironmentService environmentService: IEnvironmentService
418
) {
419
super(mcpResource, ConfigurationTarget.USER, mcpGalleryService, fileService, uriIdentityService, logService, mcpResourceScannerService);
420
this.mcpLocation = uriIdentityService.extUri.joinPath(environmentService.userRoamingDataHome, 'mcp');
421
}
422
423
async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {
424
throw new Error('Not supported');
425
}
426
427
async updateMetadata(local: ILocalMcpServer, gallery: IGalleryMcpServer): Promise<ILocalMcpServer> {
428
await this.updateMetadataFromGallery(gallery);
429
await this.updateLocal();
430
const updatedLocal = (await this.getInstalled()).find(s => s.name === local.name);
431
if (!updatedLocal) {
432
throw new Error(`Failed to find MCP server: ${local.name}`);
433
}
434
return updatedLocal;
435
}
436
437
protected async updateMetadataFromGallery(gallery: IGalleryMcpServer): Promise<IGalleryMcpServerConfiguration> {
438
const manifest = await this.mcpGalleryService.getMcpServerConfiguration(gallery, CancellationToken.None);
439
const location = this.getLocation(gallery.name, gallery.version);
440
const manifestPath = this.uriIdentityService.extUri.joinPath(location, 'manifest.json');
441
const local: ILocalMcpServerInfo = {
442
id: gallery.id,
443
galleryUrl: gallery.url,
444
name: gallery.name,
445
displayName: gallery.displayName,
446
description: gallery.description,
447
version: gallery.version,
448
publisher: gallery.publisher,
449
publisherDisplayName: gallery.publisherDisplayName,
450
repositoryUrl: gallery.repositoryUrl,
451
licenseUrl: gallery.license,
452
icon: gallery.icon,
453
codicon: gallery.codicon,
454
manifest,
455
};
456
await this.fileService.writeFile(manifestPath, VSBuffer.fromString(JSON.stringify(local)));
457
458
if (gallery.readmeUrl || gallery.readme) {
459
const readme = gallery.readme ? gallery.readme : await this.mcpGalleryService.getReadme(gallery, CancellationToken.None);
460
await this.fileService.writeFile(this.uriIdentityService.extUri.joinPath(location, 'README.md'), VSBuffer.fromString(readme));
461
}
462
463
return manifest;
464
}
465
466
protected async getLocalServerInfo(name: string, mcpServerConfig: IMcpServerConfiguration): Promise<ILocalMcpServerInfo | undefined> {
467
let storedMcpServerInfo: ILocalMcpServerInfo | undefined;
468
let location: URI | undefined;
469
let readmeUrl: URI | undefined;
470
if (mcpServerConfig.gallery) {
471
location = this.getLocation(name, mcpServerConfig.version);
472
const manifestLocation = this.uriIdentityService.extUri.joinPath(location, 'manifest.json');
473
try {
474
const content = await this.fileService.readFile(manifestLocation);
475
storedMcpServerInfo = JSON.parse(content.value.toString()) as ILocalMcpServerInfo;
476
storedMcpServerInfo.location = location;
477
readmeUrl = this.uriIdentityService.extUri.joinPath(location, 'README.md');
478
if (!await this.fileService.exists(readmeUrl)) {
479
readmeUrl = undefined;
480
}
481
storedMcpServerInfo.readmeUrl = readmeUrl;
482
} catch (e) {
483
this.logService.error('MCP Management Service: failed to read manifest', location.toString(), e);
484
}
485
}
486
return storedMcpServerInfo;
487
}
488
489
protected getLocation(name: string, version?: string): URI {
490
name = name.replace('/', '.');
491
return this.uriIdentityService.extUri.joinPath(this.mcpLocation, version ? `${name}-${version}` : name);
492
}
493
494
protected override installFromUri(uri: URI, options?: Omit<InstallOptions, 'mcpResource'>): Promise<ILocalMcpServer> {
495
throw new Error('Method not supported.');
496
}
497
498
}
499
500
export abstract class AbstractMcpManagementService extends AbstractCommonMcpManagementService implements IMcpManagementService {
501
502
constructor(
503
@IAllowedMcpServersService protected readonly allowedMcpServersService: IAllowedMcpServersService,
504
) {
505
super();
506
}
507
508
canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString {
509
const allowedToInstall = this.allowedMcpServersService.isAllowed(server);
510
if (allowedToInstall !== true) {
511
return new MarkdownString(localize('not allowed to install', "This mcp server cannot be installed because {0}", allowedToInstall.value));
512
}
513
return true;
514
}
515
516
abstract onInstallMcpServer: Event<InstallMcpServerEvent>;
517
abstract onDidInstallMcpServers: Event<readonly InstallMcpServerResult[]>;
518
abstract onDidUpdateMcpServers: Event<readonly InstallMcpServerResult[]>;
519
abstract onUninstallMcpServer: Event<UninstallMcpServerEvent>;
520
abstract onDidUninstallMcpServer: Event<DidUninstallMcpServerEvent>;
521
522
abstract getInstalled(mcpResource?: URI): Promise<ILocalMcpServer[]>;
523
abstract install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer>;
524
abstract installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer>;
525
abstract updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise<ILocalMcpServer>;
526
abstract uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise<void>;
527
}
528
529
export class McpManagementService extends AbstractMcpManagementService implements IMcpManagementService {
530
531
private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());
532
readonly onInstallMcpServer = this._onInstallMcpServer.event;
533
534
private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
535
readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;
536
537
private readonly _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
538
readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;
539
540
private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());
541
readonly onUninstallMcpServer = this._onUninstallMcpServer.event;
542
543
private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());
544
readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;
545
546
private readonly mcpResourceManagementServices = new ResourceMap<{ service: McpUserResourceManagementService } & IDisposable>();
547
548
constructor(
549
@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,
550
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
551
@IInstantiationService protected readonly instantiationService: IInstantiationService,
552
) {
553
super(allowedMcpServersService);
554
}
555
556
private getMcpResourceManagementService(mcpResource: URI): McpUserResourceManagementService {
557
let mcpResourceManagementService = this.mcpResourceManagementServices.get(mcpResource);
558
if (!mcpResourceManagementService) {
559
const disposables = new DisposableStore();
560
const service = disposables.add(this.createMcpResourceManagementService(mcpResource));
561
disposables.add(service.onInstallMcpServer(e => this._onInstallMcpServer.fire(e)));
562
disposables.add(service.onDidInstallMcpServers(e => this._onDidInstallMcpServers.fire(e)));
563
disposables.add(service.onDidUpdateMcpServers(e => this._onDidUpdateMcpServers.fire(e)));
564
disposables.add(service.onUninstallMcpServer(e => this._onUninstallMcpServer.fire(e)));
565
disposables.add(service.onDidUninstallMcpServer(e => this._onDidUninstallMcpServer.fire(e)));
566
this.mcpResourceManagementServices.set(mcpResource, mcpResourceManagementService = { service, dispose: () => disposables.dispose() });
567
}
568
return mcpResourceManagementService.service;
569
}
570
571
async getInstalled(mcpResource?: URI): Promise<ILocalMcpServer[]> {
572
const mcpResourceUri = mcpResource || this.userDataProfilesService.defaultProfile.mcpResource;
573
return this.getMcpResourceManagementService(mcpResourceUri).getInstalled();
574
}
575
576
async install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {
577
const mcpResourceUri = options?.mcpResource || this.userDataProfilesService.defaultProfile.mcpResource;
578
return this.getMcpResourceManagementService(mcpResourceUri).install(server, options);
579
}
580
581
async uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise<void> {
582
const mcpResourceUri = options?.mcpResource || this.userDataProfilesService.defaultProfile.mcpResource;
583
return this.getMcpResourceManagementService(mcpResourceUri).uninstall(server, options);
584
}
585
586
async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {
587
const mcpResourceUri = options?.mcpResource || this.userDataProfilesService.defaultProfile.mcpResource;
588
return this.getMcpResourceManagementService(mcpResourceUri).installFromGallery(server, options);
589
}
590
591
async updateMetadata(local: ILocalMcpServer, gallery: IGalleryMcpServer, mcpResource?: URI): Promise<ILocalMcpServer> {
592
return this.getMcpResourceManagementService(mcpResource || this.userDataProfilesService.defaultProfile.mcpResource).updateMetadata(local, gallery);
593
}
594
595
override dispose(): void {
596
this.mcpResourceManagementServices.forEach(service => service.dispose());
597
this.mcpResourceManagementServices.clear();
598
super.dispose();
599
}
600
601
protected createMcpResourceManagementService(mcpResource: URI): McpUserResourceManagementService {
602
return this.instantiationService.createInstance(McpUserResourceManagementService, mcpResource);
603
}
604
605
}
606
607