Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts
3296 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 { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
7
import { ILocalMcpServer, IMcpManagementService, IGalleryMcpServer, InstallOptions, InstallMcpServerEvent, UninstallMcpServerEvent, DidUninstallMcpServerEvent, InstallMcpServerResult, IInstallableMcpServer, IMcpGalleryService, UninstallOptions, IAllowedMcpServersService } from '../../../../platform/mcp/common/mcpManagement.js';
8
import { IInstantiationService, refineServiceDecorator } from '../../../../platform/instantiation/common/instantiation.js';
9
import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';
10
import { Emitter, Event } from '../../../../base/common/event.js';
11
import { IMcpResourceScannerService, McpResourceTarget } from '../../../../platform/mcp/common/mcpResourceScannerService.js';
12
import { isWorkspaceFolder, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from '../../../../platform/workspace/common/workspace.js';
13
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
14
import { MCP_CONFIGURATION_KEY, WORKSPACE_STANDALONE_CONFIGURATIONS } from '../../configuration/common/configuration.js';
15
import { ILogService } from '../../../../platform/log/common/log.js';
16
import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';
17
import { URI } from '../../../../base/common/uri.js';
18
import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js';
19
import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
20
import { McpManagementChannelClient } from '../../../../platform/mcp/common/mcpManagementIpc.js';
21
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
22
import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/remoteUserDataProfiles.js';
23
import { AbstractMcpManagementService, AbstractMcpResourceManagementService, ILocalMcpServerInfo } from '../../../../platform/mcp/common/mcpManagementService.js';
24
import { IFileService } from '../../../../platform/files/common/files.js';
25
import { ResourceMap } from '../../../../base/common/map.js';
26
27
export const USER_CONFIG_ID = 'usrlocal';
28
export const REMOTE_USER_CONFIG_ID = 'usrremote';
29
export const WORKSPACE_CONFIG_ID = 'workspace';
30
export const WORKSPACE_FOLDER_CONFIG_ID_PREFIX = 'ws';
31
32
export interface IWorkbencMcpServerInstallOptions extends InstallOptions {
33
target?: ConfigurationTarget | IWorkspaceFolder;
34
}
35
36
export const enum LocalMcpServerScope {
37
User = 'user',
38
RemoteUser = 'remoteUser',
39
Workspace = 'workspace',
40
}
41
42
export interface IWorkbenchLocalMcpServer extends ILocalMcpServer {
43
readonly id: string;
44
readonly scope: LocalMcpServerScope;
45
}
46
47
export interface InstallWorkbenchMcpServerEvent extends InstallMcpServerEvent {
48
readonly scope: LocalMcpServerScope;
49
}
50
51
export interface IWorkbenchMcpServerInstallResult extends InstallMcpServerResult {
52
readonly local?: IWorkbenchLocalMcpServer;
53
}
54
55
export interface UninstallWorkbenchMcpServerEvent extends UninstallMcpServerEvent {
56
readonly scope: LocalMcpServerScope;
57
}
58
59
export interface DidUninstallWorkbenchMcpServerEvent extends DidUninstallMcpServerEvent {
60
readonly scope: LocalMcpServerScope;
61
}
62
63
export const IWorkbenchMcpManagementService = refineServiceDecorator<IMcpManagementService, IWorkbenchMcpManagementService>(IMcpManagementService);
64
export interface IWorkbenchMcpManagementService extends IMcpManagementService {
65
readonly _serviceBrand: undefined;
66
67
readonly onInstallMcpServerInCurrentProfile: Event<InstallWorkbenchMcpServerEvent>;
68
readonly onDidInstallMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;
69
readonly onDidUpdateMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;
70
readonly onUninstallMcpServerInCurrentProfile: Event<UninstallWorkbenchMcpServerEvent>;
71
readonly onDidUninstallMcpServerInCurrentProfile: Event<DidUninstallWorkbenchMcpServerEvent>;
72
readonly onDidChangeProfile: Event<void>;
73
74
getInstalled(): Promise<IWorkbenchLocalMcpServer[]>;
75
install(server: IInstallableMcpServer | URI, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer>;
76
installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<IWorkbenchLocalMcpServer>;
77
updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise<IWorkbenchLocalMcpServer>;
78
}
79
80
export class WorkbenchMcpManagementService extends AbstractMcpManagementService implements IWorkbenchMcpManagementService {
81
82
private _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());
83
readonly onInstallMcpServer = this._onInstallMcpServer.event;
84
85
private _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
86
readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;
87
88
private _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
89
readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;
90
91
private _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());
92
readonly onUninstallMcpServer = this._onUninstallMcpServer.event;
93
94
private _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());
95
readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;
96
97
private readonly _onInstallMcpServerInCurrentProfile = this._register(new Emitter<InstallWorkbenchMcpServerEvent>());
98
readonly onInstallMcpServerInCurrentProfile = this._onInstallMcpServerInCurrentProfile.event;
99
100
private readonly _onDidInstallMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());
101
readonly onDidInstallMcpServersInCurrentProfile = this._onDidInstallMcpServersInCurrentProfile.event;
102
103
private readonly _onDidUpdateMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());
104
readonly onDidUpdateMcpServersInCurrentProfile = this._onDidUpdateMcpServersInCurrentProfile.event;
105
106
private readonly _onUninstallMcpServerInCurrentProfile = this._register(new Emitter<UninstallWorkbenchMcpServerEvent>());
107
readonly onUninstallMcpServerInCurrentProfile = this._onUninstallMcpServerInCurrentProfile.event;
108
109
private readonly _onDidUninstallMcpServerInCurrentProfile = this._register(new Emitter<DidUninstallWorkbenchMcpServerEvent>());
110
readonly onDidUninstallMcpServerInCurrentProfile = this._onDidUninstallMcpServerInCurrentProfile.event;
111
112
private readonly _onDidChangeProfile = this._register(new Emitter<void>());
113
readonly onDidChangeProfile = this._onDidChangeProfile.event;
114
115
private readonly workspaceMcpManagementService: IMcpManagementService;
116
private readonly remoteMcpManagementService: IMcpManagementService | undefined;
117
118
constructor(
119
private readonly mcpManagementService: IMcpManagementService,
120
@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,
121
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
122
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
123
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
124
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
125
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
126
@IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService,
127
@IInstantiationService instantiationService: IInstantiationService,
128
) {
129
super(allowedMcpServersService);
130
131
this.workspaceMcpManagementService = this._register(instantiationService.createInstance(WorkspaceMcpManagementService));
132
const remoteAgentConnection = remoteAgentService.getConnection();
133
if (remoteAgentConnection) {
134
this.remoteMcpManagementService = this._register(instantiationService.createInstance(McpManagementChannelClient, remoteAgentConnection.getChannel<IChannel>('mcpManagement')));
135
}
136
137
this._register(this.mcpManagementService.onInstallMcpServer(e => {
138
this._onInstallMcpServer.fire(e);
139
if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
140
this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });
141
}
142
}));
143
144
this._register(this.mcpManagementService.onDidInstallMcpServers(e => {
145
const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);
146
this._onDidInstallMcpServers.fire(mcpServerInstallResult);
147
if (mcpServerInstallResultInCurrentProfile.length) {
148
this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);
149
}
150
}));
151
152
this._register(this.mcpManagementService.onDidUpdateMcpServers(e => {
153
const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);
154
this._onDidUpdateMcpServers.fire(mcpServerInstallResult);
155
if (mcpServerInstallResultInCurrentProfile.length) {
156
this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);
157
}
158
}));
159
160
this._register(this.mcpManagementService.onUninstallMcpServer(e => {
161
this._onUninstallMcpServer.fire(e);
162
if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
163
this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });
164
}
165
}));
166
167
this._register(this.mcpManagementService.onDidUninstallMcpServer(e => {
168
this._onDidUninstallMcpServer.fire(e);
169
if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
170
this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });
171
}
172
}));
173
174
this._register(this.workspaceMcpManagementService.onInstallMcpServer(async e => {
175
this._onInstallMcpServer.fire(e);
176
this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });
177
}));
178
179
this._register(this.workspaceMcpManagementService.onDidInstallMcpServers(async e => {
180
const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);
181
this._onDidInstallMcpServers.fire(mcpServerInstallResult);
182
this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResult);
183
}));
184
185
this._register(this.workspaceMcpManagementService.onUninstallMcpServer(async e => {
186
this._onUninstallMcpServer.fire(e);
187
this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });
188
}));
189
190
this._register(this.workspaceMcpManagementService.onDidUninstallMcpServer(async e => {
191
this._onDidUninstallMcpServer.fire(e);
192
this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });
193
}));
194
195
this._register(this.workspaceMcpManagementService.onDidUpdateMcpServers(e => {
196
const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);
197
this._onDidUpdateMcpServers.fire(mcpServerInstallResult);
198
this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResult);
199
}));
200
201
if (this.remoteMcpManagementService) {
202
this._register(this.remoteMcpManagementService.onInstallMcpServer(async e => {
203
this._onInstallMcpServer.fire(e);
204
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
205
if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
206
this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });
207
}
208
}));
209
210
this._register(this.remoteMcpManagementService.onDidInstallMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));
211
this._register(this.remoteMcpManagementService.onDidUpdateMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));
212
213
this._register(this.remoteMcpManagementService.onUninstallMcpServer(async e => {
214
this._onUninstallMcpServer.fire(e);
215
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
216
if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
217
this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });
218
}
219
}));
220
221
this._register(this.remoteMcpManagementService.onDidUninstallMcpServer(async e => {
222
this._onDidUninstallMcpServer.fire(e);
223
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
224
if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
225
this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });
226
}
227
}));
228
}
229
230
this._register(userDataProfileService.onDidChangeCurrentProfile(e => {
231
if (!this.uriIdentityService.extUri.isEqual(e.previous.mcpResource, e.profile.mcpResource)) {
232
this._onDidChangeProfile.fire();
233
}
234
}));
235
}
236
237
private createInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], scope: LocalMcpServerScope): { mcpServerInstallResult: IWorkbenchMcpServerInstallResult[]; mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] } {
238
const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];
239
const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];
240
for (const result of e) {
241
const workbenchResult = {
242
...result,
243
local: result.local ? this.toWorkspaceMcpServer(result.local, scope) : undefined
244
};
245
mcpServerInstallResult.push(workbenchResult);
246
if (this.uriIdentityService.extUri.isEqual(result.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
247
mcpServerInstallResultInCurrentProfile.push(workbenchResult);
248
}
249
}
250
251
return { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile };
252
}
253
254
private async handleRemoteInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], emitter: Emitter<readonly InstallMcpServerResult[]>, currentProfileEmitter: Emitter<readonly IWorkbenchMcpServerInstallResult[]>): Promise<void> {
255
const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];
256
const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];
257
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
258
for (const result of e) {
259
const workbenchResult = {
260
...result,
261
local: result.local ? this.toWorkspaceMcpServer(result.local, LocalMcpServerScope.RemoteUser) : undefined
262
};
263
mcpServerInstallResult.push(workbenchResult);
264
if (remoteMcpResource ? this.uriIdentityService.extUri.isEqual(result.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
265
mcpServerInstallResultInCurrentProfile.push(workbenchResult);
266
}
267
}
268
269
emitter.fire(mcpServerInstallResult);
270
if (mcpServerInstallResultInCurrentProfile.length) {
271
currentProfileEmitter.fire(mcpServerInstallResultInCurrentProfile);
272
}
273
}
274
275
async getInstalled(): Promise<IWorkbenchLocalMcpServer[]> {
276
const installed: IWorkbenchLocalMcpServer[] = [];
277
const [userServers, remoteServers, workspaceServers] = await Promise.all([
278
this.mcpManagementService.getInstalled(this.userDataProfileService.currentProfile.mcpResource),
279
this.remoteMcpManagementService?.getInstalled(await this.getRemoteMcpResource()) ?? Promise.resolve<ILocalMcpServer[]>([]),
280
this.workspaceMcpManagementService?.getInstalled() ?? Promise.resolve<ILocalMcpServer[]>([]),
281
]);
282
283
for (const server of userServers) {
284
installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.User));
285
}
286
for (const server of remoteServers) {
287
installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.RemoteUser));
288
}
289
for (const server of workspaceServers) {
290
installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.Workspace));
291
}
292
293
return installed;
294
}
295
296
private toWorkspaceMcpServer(server: ILocalMcpServer, scope: LocalMcpServerScope): IWorkbenchLocalMcpServer {
297
return { ...server, id: `mcp.config.${this.getConfigId(server, scope)}.${server.name}`, scope };
298
}
299
300
private getConfigId(server: ILocalMcpServer, scope: LocalMcpServerScope): string {
301
if (scope === LocalMcpServerScope.User) {
302
return USER_CONFIG_ID;
303
}
304
305
if (scope === LocalMcpServerScope.RemoteUser) {
306
return REMOTE_USER_CONFIG_ID;
307
}
308
309
if (scope === LocalMcpServerScope.Workspace) {
310
const workspace = this.workspaceContextService.getWorkspace();
311
if (workspace.configuration && this.uriIdentityService.extUri.isEqual(workspace.configuration, server.mcpResource)) {
312
return WORKSPACE_CONFIG_ID;
313
}
314
315
const workspaceFolders = workspace.folders;
316
for (let index = 0; index < workspaceFolders.length; index++) {
317
const workspaceFolder = workspaceFolders[index];
318
if (this.uriIdentityService.extUri.isEqual(this.uriIdentityService.extUri.joinPath(workspaceFolder.uri, WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), server.mcpResource)) {
319
return `${WORKSPACE_FOLDER_CONFIG_ID_PREFIX}${index}`;
320
}
321
}
322
}
323
return 'unknown';
324
}
325
326
async install(server: IInstallableMcpServer, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer> {
327
options = options ?? {};
328
329
if (options.target === ConfigurationTarget.WORKSPACE || isWorkspaceFolder(options.target)) {
330
const mcpResource = options.target === ConfigurationTarget.WORKSPACE ? this.workspaceContextService.getWorkspace().configuration : options.target.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]);
331
if (!mcpResource) {
332
throw new Error(`Illegal target: ${options.target}`);
333
}
334
options.mcpResource = mcpResource;
335
const result = await this.workspaceMcpManagementService.install(server, options);
336
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);
337
}
338
339
if (options.target === ConfigurationTarget.USER_REMOTE) {
340
if (!this.remoteMcpManagementService) {
341
throw new Error(`Illegal target: ${options.target}`);
342
}
343
options.mcpResource = await this.getRemoteMcpResource(options.mcpResource);
344
const result = await this.remoteMcpManagementService.install(server, options);
345
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);
346
}
347
348
if (options.target && options.target !== ConfigurationTarget.USER && options.target !== ConfigurationTarget.USER_LOCAL) {
349
throw new Error(`Illegal target: ${options.target}`);
350
}
351
352
options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;
353
const result = await this.mcpManagementService.install(server, options);
354
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);
355
}
356
357
async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<IWorkbenchLocalMcpServer> {
358
options = options ?? {};
359
if (!options.mcpResource) {
360
options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;
361
}
362
const result = await this.mcpManagementService.installFromGallery(server, options);
363
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);
364
}
365
366
async updateMetadata(local: IWorkbenchLocalMcpServer, server: IGalleryMcpServer, profileLocation: URI): Promise<IWorkbenchLocalMcpServer> {
367
if (local.scope === LocalMcpServerScope.Workspace) {
368
const result = await this.workspaceMcpManagementService.updateMetadata(local, server, profileLocation);
369
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);
370
}
371
372
if (local.scope === LocalMcpServerScope.RemoteUser) {
373
if (!this.remoteMcpManagementService) {
374
throw new Error(`Illegal target: ${local.scope}`);
375
}
376
const result = await this.remoteMcpManagementService.updateMetadata(local, server, profileLocation);
377
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);
378
}
379
380
const result = await this.mcpManagementService.updateMetadata(local, server, profileLocation);
381
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);
382
}
383
384
async uninstall(server: IWorkbenchLocalMcpServer): Promise<void> {
385
if (server.scope === LocalMcpServerScope.Workspace) {
386
return this.workspaceMcpManagementService.uninstall(server);
387
}
388
389
if (server.scope === LocalMcpServerScope.RemoteUser) {
390
if (!this.remoteMcpManagementService) {
391
throw new Error(`Illegal target: ${server.scope}`);
392
}
393
return this.remoteMcpManagementService.uninstall(server);
394
}
395
396
return this.mcpManagementService.uninstall(server, { mcpResource: this.userDataProfileService.currentProfile.mcpResource });
397
}
398
399
private async getRemoteMcpResource(mcpResource?: URI): Promise<URI | undefined> {
400
if (!mcpResource && this.userDataProfileService.currentProfile.isDefault) {
401
return undefined;
402
}
403
mcpResource = mcpResource ?? this.userDataProfileService.currentProfile.mcpResource;
404
let profile = this.userDataProfilesService.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));
405
if (profile) {
406
profile = await this.remoteUserDataProfilesService.getRemoteProfile(profile);
407
} else {
408
profile = (await this.remoteUserDataProfilesService.getRemoteProfiles()).find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));
409
}
410
return profile?.mcpResource;
411
}
412
}
413
414
class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagementService {
415
416
constructor(
417
mcpResource: URI,
418
target: McpResourceTarget,
419
@IMcpGalleryService mcpGalleryService: IMcpGalleryService,
420
@IFileService fileService: IFileService,
421
@IUriIdentityService uriIdentityService: IUriIdentityService,
422
@ILogService logService: ILogService,
423
@IMcpResourceScannerService mcpResourceScannerService: IMcpResourceScannerService,
424
) {
425
super(mcpResource, target, mcpGalleryService, fileService, uriIdentityService, logService, mcpResourceScannerService);
426
}
427
428
override installFromGallery(): Promise<ILocalMcpServer> {
429
throw new Error('Not supported');
430
}
431
432
override updateMetadata(): Promise<ILocalMcpServer> {
433
throw new Error('Not supported');
434
}
435
436
protected override installFromUri(): Promise<ILocalMcpServer> {
437
throw new Error('Not supported');
438
}
439
440
protected override async getLocalServerInfo(): Promise<ILocalMcpServerInfo | undefined> {
441
return undefined;
442
}
443
}
444
445
class WorkspaceMcpManagementService extends AbstractMcpManagementService implements IMcpManagementService {
446
447
private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());
448
readonly onInstallMcpServer = this._onInstallMcpServer.event;
449
450
private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
451
readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;
452
453
private readonly _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
454
readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;
455
456
private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());
457
readonly onUninstallMcpServer = this._onUninstallMcpServer.event;
458
459
private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());
460
readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;
461
462
private allMcpServers: ILocalMcpServer[] = [];
463
464
private workspaceConfiguration?: URI | null;
465
private readonly workspaceMcpManagementServices = new ResourceMap<{ service: WorkspaceMcpResourceManagementService } & IDisposable>();
466
467
constructor(
468
@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,
469
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
470
@ILogService private readonly logService: ILogService,
471
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
472
@IInstantiationService private readonly instantiationService: IInstantiationService,
473
) {
474
super(allowedMcpServersService);
475
this.initialize();
476
}
477
478
private async initialize(): Promise<void> {
479
try {
480
await this.onDidChangeWorkbenchState();
481
await this.onDidChangeWorkspaceFolders({ added: this.workspaceContextService.getWorkspace().folders, removed: [], changed: [] });
482
this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this.onDidChangeWorkspaceFolders(e)));
483
this._register(this.workspaceContextService.onDidChangeWorkbenchState(e => this.onDidChangeWorkbenchState()));
484
} catch (error) {
485
this.logService.error('Failed to initialize workspace folders', error);
486
}
487
}
488
489
private async onDidChangeWorkbenchState(): Promise<void> {
490
if (this.workspaceConfiguration) {
491
await this.removeWorkspaceService(this.workspaceConfiguration);
492
}
493
this.workspaceConfiguration = this.workspaceContextService.getWorkspace().configuration;
494
if (this.workspaceConfiguration) {
495
await this.addWorkspaceService(this.workspaceConfiguration, ConfigurationTarget.WORKSPACE);
496
}
497
}
498
499
private async onDidChangeWorkspaceFolders(e: IWorkspaceFoldersChangeEvent): Promise<void> {
500
try {
501
await Promise.allSettled(e.removed.map(folder => this.removeWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]))));
502
} catch (error) {
503
this.logService.error(error);
504
}
505
try {
506
await Promise.allSettled(e.added.map(folder => this.addWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), ConfigurationTarget.WORKSPACE_FOLDER)));
507
} catch (error) {
508
this.logService.error(error);
509
}
510
}
511
512
private async addWorkspaceService(mcpResource: URI, target: McpResourceTarget): Promise<void> {
513
if (this.workspaceMcpManagementServices.has(mcpResource)) {
514
return;
515
}
516
517
const disposables = new DisposableStore();
518
const service = disposables.add(this.instantiationService.createInstance(WorkspaceMcpResourceManagementService, mcpResource, target));
519
520
try {
521
const installedServers = await service.getInstalled();
522
this.allMcpServers.push(...installedServers);
523
if (installedServers.length > 0) {
524
const installResults: InstallMcpServerResult[] = installedServers.map(server => ({
525
name: server.name,
526
local: server,
527
mcpResource: server.mcpResource
528
}));
529
this._onDidInstallMcpServers.fire(installResults);
530
}
531
} catch (error) {
532
this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);
533
}
534
535
disposables.add(service.onInstallMcpServer(e => this._onInstallMcpServer.fire(e)));
536
disposables.add(service.onDidInstallMcpServers(e => {
537
for (const { local } of e) {
538
if (local) {
539
this.allMcpServers.push(local);
540
}
541
}
542
this._onDidInstallMcpServers.fire(e);
543
}));
544
disposables.add(service.onDidUpdateMcpServers(e => {
545
for (const { local, mcpResource } of e) {
546
if (local) {
547
const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, mcpResource) && server.name === local.name);
548
if (index !== -1) {
549
this.allMcpServers.splice(index, 1, local);
550
}
551
}
552
}
553
this._onDidUpdateMcpServers.fire(e);
554
}));
555
disposables.add(service.onUninstallMcpServer(e => this._onUninstallMcpServer.fire(e)));
556
disposables.add(service.onDidUninstallMcpServer(e => {
557
const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, e.mcpResource) && server.name === e.name);
558
if (index !== -1) {
559
this.allMcpServers.splice(index, 1);
560
this._onDidUninstallMcpServer.fire(e);
561
}
562
}));
563
this.workspaceMcpManagementServices.set(mcpResource, { service, dispose: () => disposables.dispose() });
564
}
565
566
private async removeWorkspaceService(mcpResource: URI): Promise<void> {
567
const serviceItem = this.workspaceMcpManagementServices.get(mcpResource);
568
if (serviceItem) {
569
try {
570
const installedServers = await serviceItem.service.getInstalled();
571
this.allMcpServers = this.allMcpServers.filter(server => !installedServers.some(uninstalled => this.uriIdentityService.extUri.isEqual(uninstalled.mcpResource, server.mcpResource)));
572
for (const server of installedServers) {
573
this._onDidUninstallMcpServer.fire({
574
name: server.name,
575
mcpResource: server.mcpResource
576
});
577
}
578
} catch (error) {
579
this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);
580
}
581
this.workspaceMcpManagementServices.delete(mcpResource);
582
serviceItem.dispose();
583
}
584
}
585
586
async getInstalled(): Promise<ILocalMcpServer[]> {
587
return this.allMcpServers;
588
}
589
590
async install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {
591
if (!options?.mcpResource) {
592
throw new Error('MCP resource is required');
593
}
594
595
const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(options?.mcpResource);
596
if (!mcpManagementServiceItem) {
597
throw new Error(`No MCP management service found for resource: ${options?.mcpResource.toString()}`);
598
}
599
600
return mcpManagementServiceItem.service.install(server, options);
601
}
602
603
async uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise<void> {
604
const mcpResource = server.mcpResource;
605
606
const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(mcpResource);
607
if (!mcpManagementServiceItem) {
608
throw new Error(`No MCP management service found for resource: ${mcpResource.toString()}`);
609
}
610
611
return mcpManagementServiceItem.service.uninstall(server, options);
612
}
613
614
installFromGallery(): Promise<ILocalMcpServer> {
615
throw new Error('Not supported');
616
}
617
618
updateMetadata(): Promise<ILocalMcpServer> {
619
throw new Error('Not supported');
620
}
621
622
override dispose(): void {
623
this.workspaceMcpManagementServices.forEach(service => service.dispose());
624
this.workspaceMcpManagementServices.clear();
625
super.dispose();
626
}
627
}
628
629